mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This adds a mostly flow-insensitive analysis that runs before the dominator-based transformations. The analysis is simple and efficient because it only needs to track data flow of currently in-scope accesses. The original dominator tree walk remains simple, but it now checks the flow insensitive analysis information to determine general correctness. This is now correct in the presence of all kinds of nested static and dynamic nested accesses, call sites, coroutines, etc. This is a better compromise than: (a) disabling the pass and taking a major performance loss. (b) converting the pass itself to full-fledged data flow driven optimization, which would be more optimal because it could remove accesses when nesting is involved, but would be much more expensive and complicated, and there's no indication that it's useful. The new approach is also simpler than adding more complexity to independently handle to each of many issues: - Nested reads followed by a modify without a false conflict. - Reads nested within a function call without a false conflict. - Conflicts nested within a function call without dropping enforcement. - Accesses within a generalized accessor. - Conservative treatment of invalid storage locations. - Conservative treatment of unknown apply callee. - General analysis invalidation. Some of these issues also needed to be considered in the LoopDominatingAccess sub-pass. Rather than fix that sub-pass, I just integrated it into the main pass. This is a simplification, is more efficient, and also handles nested loops without creating more redundant accesses. It is also generalized to: - hoist non-uniquely identified accesses. - Avoid unnecessarily promoting accesses inside the loop. With this approach we can remove the scary warnings and caveats in the comments. While doing this I also took the opportunity to eliminate quadratic behavior, make the domtree walk non-recursive, and eliminate cutoff thresholds. Note that simple nested dynamic reads to identical storage could very easily be removed via separate logic, but it does not fit with the dominator-based algorithm. For example, during the analysis phase, we could simply mark the "fully nested" read scopes, then convert them to [static] right after the analysis, removing them from the result map. I didn't do this because I don't know if it happens in practice.
557 lines
19 KiB
Plaintext
557 lines
19 KiB
Plaintext
// RUN: %target-sil-opt -access-enforcement-dom %s -enable-sil-verify-all | %FileCheck %s
|
||
//
|
||
// Test the AccessEnforcementDom pass in isolation. This ensures that
|
||
// no upstream passes have removed SIL-level access markers that are
|
||
// required to ensure the pass is not overly optimistic.
|
||
|
||
sil_stage canonical
|
||
|
||
import Builtin
|
||
import Swift
|
||
import SwiftShims
|
||
|
||
struct X {
|
||
@_hasStorage var i: Int64 { get set }
|
||
init(i: Int64)
|
||
init()
|
||
}
|
||
|
||
var globalX: X
|
||
|
||
var globalOtherX: X
|
||
|
||
sil_global hidden @globalX : $X
|
||
|
||
sil_global hidden @globalOtherX : $X
|
||
|
||
sil hidden @Xinit : $@convention(method) (@thin X.Type) -> X {
|
||
bb0(%0 : $@thin X.Type):
|
||
%1 = alloc_stack $X, var, name "self"
|
||
%2 = integer_literal $Builtin.Int64, 7
|
||
%3 = struct $Int64 (%2 : $Builtin.Int64)
|
||
%4 = struct_element_addr %1 : $*X, #X.i
|
||
store %3 to %4 : $*Int64
|
||
%6 = struct $X (%3 : $Int64)
|
||
dealloc_stack %1 : $*X
|
||
return %6 : $X
|
||
}
|
||
|
||
// public func testDomSimpleRead() {
|
||
// Checks 3 scopes, two of which are dominated and access the same storage
|
||
//
|
||
// CHECK-LABEL: sil @testDomSimpleRead : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testDomSimpleRead'
|
||
sil @testDomSimpleRead : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%1 = begin_access [read] [dynamic] %0 : $*X
|
||
%2 = load %1 : $*X
|
||
end_access %1 : $*X
|
||
%4 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%7 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%8 = load %7 : $*X
|
||
end_access %7 : $*X
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testDomSimpleWrite() {
|
||
// Checks 3 scopes, two of which are dominated and access the same storage
|
||
//
|
||
// CHECK-LABEL: sil @testDomSimpleWrite : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK: [[BEGIN:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK: store {{.*}} to [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testDomSimpleWrite'
|
||
sil @testDomSimpleWrite : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%1 = begin_access [modify] [dynamic] %0 : $*X
|
||
%2 = load %1 : $*X
|
||
end_access %1 : $*X
|
||
%4 = metatype $@thin X.Type
|
||
// function_ref X.init()
|
||
%5 = function_ref @Xinit : $@convention(method) (@thin X.Type) -> X
|
||
%6 = apply %5(%4) : $@convention(method) (@thin X.Type) -> X
|
||
%7 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
|
||
store %6 to %7 : $*X
|
||
end_access %7 : $*X
|
||
%10 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%11 = load %10 : $*X
|
||
end_access %10 : $*X
|
||
%12 = tuple ()
|
||
return %12 : $()
|
||
}
|
||
|
||
// public func testDomAcrossBBs() {
|
||
// Checks static-setting of scopes across basic blocks
|
||
//
|
||
// CHECK-LABEL: sil @testDomAcrossBBs : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: br bb1
|
||
// CHECK: br bb2
|
||
// CHECK: bb2:
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testDomAcrossBBs'
|
||
sil @testDomAcrossBBs : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%1 = begin_access [modify] [dynamic] %0 : $*X
|
||
%2 = load %1 : $*X
|
||
end_access %1 : $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
br bb2
|
||
|
||
bb2:
|
||
%4 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%7 = tuple ()
|
||
return %7 : $()
|
||
}
|
||
|
||
// public func testDomAcrossInnerLoop() {
|
||
// Checksstatic-setting of scopes across an inner loop
|
||
//
|
||
// CHECK-LABEL: sil @testDomAcrossInnerLoop : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: br bb1
|
||
// CHECK: cond_br {{.*}}, bb1, bb2
|
||
// CHECK: bb2:
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testDomAcrossInnerLoop'
|
||
sil @testDomAcrossInnerLoop : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%1 = begin_access [modify] [dynamic] %0 : $*X
|
||
%2 = load %1 : $*X
|
||
end_access %1 : $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
%cond = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond, bb1, bb2
|
||
|
||
bb2:
|
||
%4 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%7 = tuple ()
|
||
return %7 : $()
|
||
}
|
||
|
||
// public func testIrreducibleGraph() {
|
||
// Checks domination in an irreducible control flow
|
||
//
|
||
// CHECK-LABEL: sil @testIrreducibleGraph : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: br bb1
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb3
|
||
// CHECK: [[BEGIN3:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN3]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN3]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb3, bb4
|
||
// CHECK: [[BEGIN4:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN4]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN4]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb1
|
||
// CHECK: [[BEGIN5:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN5]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN5]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testIrreducibleGraph'
|
||
sil @testIrreducibleGraph : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%1 = begin_access [read] [dynamic] %0 : $*X
|
||
%2 = load %1 : $*X
|
||
end_access %1 : $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
%4 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%cond1 = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond1, bb2, bb3
|
||
|
||
bb2:
|
||
%6 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%7 = load %6 : $*X
|
||
end_access %6 : $*X
|
||
%cond2 = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond2, bb3, bb4
|
||
|
||
bb3:
|
||
%8 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%9 = load %8 : $*X
|
||
end_access %8 : $*X
|
||
%cond3 = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond3, bb2, bb1
|
||
|
||
bb4:
|
||
%10 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%11 = load %10 : $*X
|
||
end_access %10 : $*X
|
||
%12 = tuple ()
|
||
return %12 : $()
|
||
}
|
||
|
||
// public func testIrreducibleGraph2() {
|
||
// Checks detection of irreducible control flow / bail for *some* of them
|
||
//
|
||
// CHECK-LABEL: sil @testIrreducibleGraph2 : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: br bb1
|
||
// CHECK: cond_br {{.*}}, bb2, bb3
|
||
// CHECK: bb2:
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: br bb3
|
||
// CHECK: bb3:
|
||
// CHECK: [[BEGIN3:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN3]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN3]] : $*X
|
||
// CHECK: br bb4
|
||
// CHECK: [[BEGIN4:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN4]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN4]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb5
|
||
// CHECK: [[BEGIN5:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN5]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN5]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testIrreducibleGraph2'
|
||
sil @testIrreducibleGraph2 : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
%cond1 = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond1, bb2, bb3
|
||
|
||
bb2:
|
||
%6 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%7 = load %6 : $*X
|
||
end_access %6 : $*X
|
||
br bb3
|
||
|
||
bb3:
|
||
%8 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%9 = load %8 : $*X
|
||
end_access %8 : $*X
|
||
br bb4
|
||
|
||
bb4:
|
||
%10 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%11 = load %10 : $*X
|
||
end_access %10 : $*X
|
||
%cond2 = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond2, bb2, bb5
|
||
|
||
bb5:
|
||
%13 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%14 = load %13 : $*X
|
||
end_access %13 : $*X
|
||
%16 = tuple ()
|
||
return %16 : $()
|
||
}
|
||
|
||
// public func testDomUnpaired() {
|
||
// Checks 3 scopes, two of which are dominated and access the same storage
|
||
// However, The second access is unpaired - we can’t optimized
|
||
//
|
||
// CHECK-LABEL: sil @testDomUnpaired : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NEXT: [[ALLOC:%.*]] = alloc_stack $Builtin.UnsafeValueBuffer
|
||
// CHECK-NEXT: begin_unpaired_access [read] [dynamic] [[GLOBAL]] : $*X, [[ALLOC]] : $*Builtin.UnsafeValueBuffer
|
||
// CHECK-NEXT: end_unpaired_access [dynamic] [[ALLOC]] : $*Builtin.UnsafeValueBuffer
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK-NOT: begin_access
|
||
// CHECK-LABEL: } // end sil function 'testDomUnpaired'
|
||
sil @testDomUnpaired : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%1 = begin_access [read] [dynamic] %0 : $*X
|
||
%2 = load %1 : $*X
|
||
end_access %1 : $*X
|
||
%buffer = alloc_stack $Builtin.UnsafeValueBuffer
|
||
begin_unpaired_access [read] [dynamic] %0 : $*X, %buffer : $*Builtin.UnsafeValueBuffer
|
||
end_unpaired_access [dynamic] %buffer : $*Builtin.UnsafeValueBuffer
|
||
%7 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%8 = load %7 : $*X
|
||
end_access %7 : $*X
|
||
dealloc_stack %buffer : $*Builtin.UnsafeValueBuffer
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testLoopDominatingAccessAdderSimple() {
|
||
// Checks creation of new scope in loop preheader
|
||
//
|
||
// CHECK-LABEL: sil @testLoopDominatingAccessAdderSimple : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK: bb1:
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK: bb3:
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb4
|
||
// CHECK-LABEL: } // end sil function 'testLoopDominatingAccessAdderSimple'
|
||
sil @testLoopDominatingAccessAdderSimple : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
br bb2
|
||
|
||
bb2:
|
||
br bb3
|
||
|
||
bb3:
|
||
%4 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%cond = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond, bb2, bb4
|
||
|
||
bb4:
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testLoopDominatingAccessAdderBailSimple() {
|
||
// Checks bailing on the creation of new scope in loop preheader if the access has a nested conflict
|
||
//
|
||
// CHECK-LABEL: sil @testLoopDominatingAccessAdderBailSimple : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK: bb1:
|
||
// CHECK-NEXT: br bb2
|
||
// CHECK: bb3:
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb4
|
||
// CHECK-LABEL: } // end sil function 'testLoopDominatingAccessAdderBailSimple'
|
||
sil @testLoopDominatingAccessAdderBailSimple : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
br bb2
|
||
|
||
bb2:
|
||
br bb3
|
||
|
||
bb3:
|
||
%4 = begin_access [read] [dynamic] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%cond = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond, bb2, bb4
|
||
|
||
bb4:
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testLoopDominatingAccessAdderMulti() {
|
||
// Checks creation of a single new scope in loop preheader if we have multi-access in loop
|
||
//
|
||
// CHECK-LABEL: sil @testLoopDominatingAccessAdderMulti : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK: bb1:
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK: bb3:
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
||
// CHECK: [[BEGIN3:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN3]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN3]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb4
|
||
// CHECK-LABEL: } // end sil function 'testLoopDominatingAccessAdderMulti'
|
||
sil @testLoopDominatingAccessAdderMulti : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
br bb2
|
||
|
||
bb2:
|
||
br bb3
|
||
|
||
bb3:
|
||
%4 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%6 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%7 = load %6 : $*X
|
||
end_access %6 : $*X
|
||
%cond = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond, bb2, bb4
|
||
|
||
bb4:
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testLoopDominatingAccessAdderMultiChangeKind() {
|
||
// Checks creation of a single new scope in loop preheader with [modify]
|
||
// if one of the accesses changes the kind we first encounter
|
||
//
|
||
// CHECK-LABEL: sil @testLoopDominatingAccessAdderMultiChangeKind : $@convention(thin) () -> () {
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK: bb1:
|
||
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
||
// CHECK: bb3:
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN2]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
||
// CHECK: [[BEGIN3:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN3]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN3]] : $*X
|
||
// CHECK: [[BEGIN4:%.*]] = begin_access [read] [static] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-NEXT: load [[BEGIN4]] : $*X
|
||
// CHECK-NEXT: end_access [[BEGIN4]] : $*X
|
||
// CHECK: cond_br {{.*}}, bb2, bb4
|
||
// CHECK-LABEL: } // end sil function 'testLoopDominatingAccessAdderMultiChangeKind'
|
||
sil @testLoopDominatingAccessAdderMultiChangeKind : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
br bb1
|
||
|
||
bb1:
|
||
br bb2
|
||
|
||
bb2:
|
||
br bb3
|
||
|
||
bb3:
|
||
%4 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
end_access %4 : $*X
|
||
%6 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%7 = load %6 : $*X
|
||
end_access %6 : $*X
|
||
%8 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%9 = load %8 : $*X
|
||
end_access %8 : $*X
|
||
%cond = integer_literal $Builtin.Int1, 1
|
||
cond_br %cond, bb2, bb4
|
||
|
||
bb4:
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testBailOnNestedDomWrite() {
|
||
// Checks that we bail when the dominated begin comes before the dominating begin
|
||
//
|
||
// CHECK-LABEL: sil @testBailOnNestedDomWrite : $@convention(thin) () -> () {
|
||
// CHECK: bb0:
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-LABEL: } // end sil function 'testBailOnNestedDomWrite'
|
||
sil @testBailOnNestedDomWrite : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%4 = begin_access [modify] [dynamic] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
%7 = begin_access [modify] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%8 = load %7 : $*X
|
||
end_access %7 : $*X
|
||
end_access %4 : $*X
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|
||
|
||
// public func testOptOnNestedDomRead() {
|
||
//
|
||
// Bail on a nested access. The dominator-based algorithm does not
|
||
// remove nested access scopes because those could generally be
|
||
// conflicts. This one just happens to be a read-read, so it isn't a real
|
||
// conflict.
|
||
//
|
||
// FIXME: simple nested dynamic reads to identical storage could very
|
||
// easily be removed via separate logic, but it does not fit with the
|
||
// dominator-based algorithm. For example, during the analysis phase, we
|
||
// could simply mark the "fully nested" read scopes, then convert them to
|
||
// [static] right after the analysis, removing them from the result
|
||
// map. I didn't do this because I don't know if it happens in practice.
|
||
//
|
||
// CHECK-LABEL: sil @testOptOnNestedDomRead : $@convention(thin) () -> () {
|
||
// CHECK: bb0:
|
||
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
||
// CHECK: [[BEGIN:%.*]] = begin_access [read] [dynamic] [[GLOBAL]] : $*X
|
||
// CHECK: [[BEGIN2:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[GLOBAL]] : $*X
|
||
// CHECK-LABEL: } // end sil function 'testOptOnNestedDomRead'
|
||
sil @testOptOnNestedDomRead : $@convention(thin) () -> () {
|
||
bb0:
|
||
%0 = global_addr @globalX: $*X
|
||
%4 = begin_access [read] [dynamic] %0 : $*X
|
||
%5 = load %4 : $*X
|
||
%7 = begin_access [read] [dynamic] [no_nested_conflict] %0 : $*X
|
||
%8 = load %7 : $*X
|
||
end_access %7 : $*X
|
||
end_access %4 : $*X
|
||
%10 = tuple ()
|
||
return %10 : $()
|
||
}
|