SIL Verifier: verify that inside a read-only access scope there are no stores to the memory location

This will e.g. catch violations like
```
  %1 = begin_access [read] %0
  store %2 to %0
  end_access %1
```

Also, fix all the sil tests which violate that.
This commit is contained in:
Erik Eckstein
2025-10-22 20:41:05 +02:00
parent 12b582a426
commit 4f1cbbd07d
11 changed files with 53 additions and 28 deletions

View File

@@ -154,6 +154,33 @@ extension LoadBorrowInst : VerifiableInstruction {
} }
} }
extension BeginAccessInst : VerifiableInstruction {
func verify(_ context: VerifierContext) {
if context.silStage == .raw {
return
}
// Catch violations like
// ```
// %1 = begin_access [read] %0
// store %2 to %0
// end_access %1
// ```
if address.type.isMoveOnly && enforcement == .static {
// This is a workaround for a bug in the move-only checker: rdar://151841926.
// The move-only checker sometimes inserts destroy_addr within read-only static access scopes.
// TODO: remove this once the bug is fixed.
return
}
if accessKind == .read {
var mutatingInstructions = MutatingUsesWalker(context)
defer { mutatingInstructions.deinitialize() }
mutatingInstructions.findMutatingUses(of: self.address)
mutatingInstructions.verifyNoMutatingUsesInLinearLiverange(of: self)
}
}
}
extension VectorBaseAddrInst : VerifiableInstruction { extension VectorBaseAddrInst : VerifiableInstruction {
func verify(_ context: VerifierContext) { func verify(_ context: VerifierContext) {
require(vector.type.isBuiltinFixedArray, require(vector.type.isBuiltinFixedArray,
@@ -210,9 +237,7 @@ private struct MutatingUsesWalker : AddressDefUseWalker {
} }
} }
private mutating func verifyNoMutatingUsesInLinearLiverange(of startInst: SingleValueInstruction) { mutating func verifyNoMutatingUsesInLinearLiverange(of startInst: SingleValueInstruction) {
assert(startInst is LoadBorrowInst || startInst is BorrowedFromInst)
var instWorklist = InstructionWorklist(context) var instWorklist = InstructionWorklist(context)
defer { instWorklist.deinitialize() } defer { instWorklist.deinitialize() }
@@ -223,7 +248,7 @@ private struct MutatingUsesWalker : AddressDefUseWalker {
while let inst = instWorklist.pop() { while let inst = instWorklist.pop() {
require(!mutatingInstructions.contains(inst), require(!mutatingInstructions.contains(inst),
"Load borrow invalidated by a local write", atInstruction: inst) "read-only scope invalidated by a local write", atInstruction: inst)
instWorklist.pushPredecessors(of: inst, ignoring: startInst) instWorklist.pushPredecessors(of: inst, ignoring: startInst)
} }
} }

View File

@@ -55,7 +55,7 @@ bb0(%0 : @owned $WrapperStruct):
// we run sil-opt with -dont-abort-on-memory-lifetime-error. // we run sil-opt with -dont-abort-on-memory-lifetime-error.
// CHECK-LABEL: Begin Error in function caller2 // CHECK-LABEL: Begin Error in function caller2
// CHECK: SIL verification failed: Load borrow invalidated by a local write // CHECK: SIL verification failed: read-only scope invalidated by a local write
// CHECK-LABEL: End Error in function caller2 // CHECK-LABEL: End Error in function caller2
sil [ossa] @caller2 : $@convention(thin) (@owned WrapperStruct) -> () { sil [ossa] @caller2 : $@convention(thin) (@owned WrapperStruct) -> () {
bb0(%0 : @owned $WrapperStruct): bb0(%0 : @owned $WrapperStruct):

View File

@@ -8,7 +8,7 @@ sil @use_guaranteed : $@convention(thin) (@guaranteed Klass) -> ()
// Write: store {{.*}} [assign] {{.*}} // Write: store {{.*}} [assign] {{.*}}
// CHECK: Begin Error in function test_write_reborrow // CHECK: Begin Error in function test_write_reborrow
// CHECK: SIL verification failed: Load borrow invalidated by a local write // CHECK: SIL verification failed: read-only scope invalidated by a local write
// CHECK: End Error in function test_write_reborrow // CHECK: End Error in function test_write_reborrow
sil [ossa] @test_write_reborrow : $@convention(thin) (@owned Klass, @owned Klass) -> () { sil [ossa] @test_write_reborrow : $@convention(thin) (@owned Klass, @owned Klass) -> () {
bb0(%0 : @owned $Klass, %1 : @owned $Klass): bb0(%0 : @owned $Klass, %1 : @owned $Klass):
@@ -30,7 +30,7 @@ bb2(%ldarg : @guaranteed $Klass):
} }
// CHECK: Begin Error in function test_multiple_loadborrows // CHECK: Begin Error in function test_multiple_loadborrows
// CHECK: SIL verification failed: Load borrow invalidated by a local write // CHECK: SIL verification failed: read-only scope invalidated by a local write
// CHECK: Verifying instruction: // CHECK: Verifying instruction:
// CHECK: -> destroy_addr // CHECK: -> destroy_addr
// CHECK: End Error in function test_multiple_loadborrows // CHECK: End Error in function test_multiple_loadborrows
@@ -84,7 +84,7 @@ struct MyStruct {
} }
// CHECK: Begin Error in function test_is_unique // CHECK: Begin Error in function test_is_unique
// CHECK: SIL verification failed: Load borrow invalidated by a local write // CHECK: SIL verification failed: read-only scope invalidated by a local write
// CHECK: -> %4 = is_unique %1 : $*ArrayIntBuffer // CHECK: -> %4 = is_unique %1 : $*ArrayIntBuffer
// CHECK: End Error in function test_is_unique // CHECK: End Error in function test_is_unique
sil [ossa] @test_is_unique : $@convention(thin) (@in MyArray<MyStruct>) -> () { sil [ossa] @test_is_unique : $@convention(thin) (@in MyArray<MyStruct>) -> () {

View File

@@ -825,7 +825,7 @@ bb0(%0 : @owned $T, %1 : @owned $Inner):
%4 = alloc_stack $Inner %4 = alloc_stack $Inner
store %1 to [init] %4 store %1 to [init] %4
%6 = mark_dependence %4 on %2 %6 = mark_dependence %4 on %2
%7 = begin_access [read] [dynamic] %6 %7 = begin_access [modify] [dynamic] %6
%8 = load [take] %7 %8 = load [take] %7
end_access %7 end_access %7
dealloc_stack %4 dealloc_stack %4
@@ -841,7 +841,7 @@ bb0(%0 : @owned $T, %1 : @owned $Inner):
%4 = alloc_stack $Inner %4 = alloc_stack $Inner
store %1 to [init] %4 store %1 to [init] %4
mark_dependence_addr %4 on %2 mark_dependence_addr %4 on %2
%7 = begin_access [read] [dynamic] %4 %7 = begin_access [modify] [dynamic] %4
%8 = load [take] %7 %8 = load [take] %7
end_access %7 end_access %7
dealloc_stack %4 dealloc_stack %4

View File

@@ -1673,12 +1673,12 @@ bb0:
// and the caller storage is an Argument at a different position. // and the caller storage is an Argument at a different position.
// //
// CHECK-LABEL: sil @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 { // CHECK-LABEL: sil @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 {
// CHECK: begin_access [read] [dynamic] [no_nested_conflict] // CHECK: begin_access [modify] [dynamic] [no_nested_conflict]
// CHECK-LABEL: } // end sil function 'testTransformArgumentCaller' // CHECK-LABEL: } // end sil function 'testTransformArgumentCaller'
sil @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 { sil @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 {
bb0(%0 : $Int64, %1 : ${ var Int64 }, %2 : ${ var Int64 }): bb0(%0 : $Int64, %1 : ${ var Int64 }, %2 : ${ var Int64 }):
%boxadr = project_box %1 : $ { var Int64 }, 0 %boxadr = project_box %1 : $ { var Int64 }, 0
%access = begin_access [read] [dynamic] [no_nested_conflict] %boxadr : $*Int64 %access = begin_access [modify] [dynamic] [no_nested_conflict] %boxadr : $*Int64
%f = function_ref @testTransformArgumentCallee : $@convention(thin) (@guaranteed { var Int64 }) -> Int64 %f = function_ref @testTransformArgumentCallee : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
%call = apply %f(%2) : $@convention(thin) (@guaranteed { var Int64 }) -> Int64 %call = apply %f(%2) : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
store %0 to %access : $*Int64 store %0 to %access : $*Int64

View File

@@ -1757,12 +1757,12 @@ bb0:
// and the caller storage is an Argument at a different position. // and the caller storage is an Argument at a different position.
// //
// CHECK-LABEL: sil [ossa] @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 { // CHECK-LABEL: sil [ossa] @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 {
// CHECK: begin_access [read] [dynamic] [no_nested_conflict] // CHECK: begin_access [modify] [dynamic] [no_nested_conflict]
// CHECK-LABEL: } // end sil function 'testTransformArgumentCaller' // CHECK-LABEL: } // end sil function 'testTransformArgumentCaller'
sil [ossa] @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 { sil [ossa] @testTransformArgumentCaller : $@convention(thin) (Int64, @guaranteed { var Int64 }, @guaranteed { var Int64 }) -> Int64 {
bb0(%0 : $Int64, %1 : @guaranteed ${ var Int64 }, %2 : @guaranteed ${ var Int64 }): bb0(%0 : $Int64, %1 : @guaranteed ${ var Int64 }, %2 : @guaranteed ${ var Int64 }):
%boxadr = project_box %1 : $ { var Int64 }, 0 %boxadr = project_box %1 : $ { var Int64 }, 0
%access = begin_access [read] [dynamic] [no_nested_conflict] %boxadr : $*Int64 %access = begin_access [modify] [dynamic] [no_nested_conflict] %boxadr : $*Int64
%f = function_ref @testTransformArgumentCallee : $@convention(thin) (@guaranteed { var Int64 }) -> Int64 %f = function_ref @testTransformArgumentCallee : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
%call = apply %f(%2) : $@convention(thin) (@guaranteed { var Int64 }) -> Int64 %call = apply %f(%2) : $@convention(thin) (@guaranteed { var Int64 }) -> Int64
store %0 to [trivial] %access : $*Int64 store %0 to [trivial] %access : $*Int64

View File

@@ -103,12 +103,12 @@ bb0(%0 : ${ var Int }, %1 : $Int):
} }
// CHECK-LABEL: @writeIdentifiedBox // CHECK-LABEL: @writeIdentifiedBox
// CHECK: [read] Box %1 = alloc_box ${ var Int } // CHECK: [modify] Box %1 = alloc_box ${ var Int }
sil @writeIdentifiedBox : $@convention(thin) (Int) -> () { sil @writeIdentifiedBox : $@convention(thin) (Int) -> () {
bb0(%0 : $Int): bb0(%0 : $Int):
%1 = alloc_box ${ var Int } %1 = alloc_box ${ var Int }
%2 = project_box %1 : ${ var Int }, 0 %2 = project_box %1 : ${ var Int }, 0
%3 = begin_access [read] [dynamic] %2 : $*Int %3 = begin_access [modify] [dynamic] %2 : $*Int
store %0 to %3 : $*Int store %0 to %3 : $*Int
end_access %3 : $*Int end_access %3 : $*Int
%6 = tuple () %6 = tuple ()
@@ -398,7 +398,7 @@ bb0(%0 : $C):
sil @writeIdentifiedNestedClass : $@convention(thin) (@guaranteed C, Int) -> () { sil @writeIdentifiedNestedClass : $@convention(thin) (@guaranteed C, Int) -> () {
bb0(%0 : $C, %1 : $Int): bb0(%0 : $C, %1 : $Int):
%2 = ref_element_addr %0 : $C, #C.property %2 = ref_element_addr %0 : $C, #C.property
%3 = begin_access [read] [dynamic] %2 : $*Int %3 = begin_access [modify] [dynamic] %2 : $*Int
%4 = begin_access [modify] [dynamic] %3 : $*Int %4 = begin_access [modify] [dynamic] %3 : $*Int
store %1 to %4 : $*Int store %1 to %4 : $*Int
end_access %4 : $*Int end_access %4 : $*Int

View File

@@ -103,12 +103,12 @@ bb0(%0 : @guaranteed ${ var Int }, %1 : $Int):
} }
// CHECK-LABEL: @writeIdentifiedBox // CHECK-LABEL: @writeIdentifiedBox
// CHECK: [read] Box %1 = alloc_box ${ var Int } // CHECK: [modify] Box %1 = alloc_box ${ var Int }
sil [ossa] @writeIdentifiedBox : $@convention(thin) (Int) -> () { sil [ossa] @writeIdentifiedBox : $@convention(thin) (Int) -> () {
bb0(%0 : $Int): bb0(%0 : $Int):
%1 = alloc_box ${ var Int } %1 = alloc_box ${ var Int }
%2 = project_box %1 : ${ var Int }, 0 %2 = project_box %1 : ${ var Int }, 0
%3 = begin_access [read] [dynamic] %2 : $*Int %3 = begin_access [modify] [dynamic] %2 : $*Int
store %0 to [trivial] %3 : $*Int store %0 to [trivial] %3 : $*Int
end_access %3 : $*Int end_access %3 : $*Int
destroy_value %1 : ${ var Int } destroy_value %1 : ${ var Int }
@@ -409,7 +409,7 @@ sil [ossa] @writeIdentifiedNestedClass : $@convention(thin) (@guaranteed C, Int)
bb0(%0 : @guaranteed $C, %1 : $Int): bb0(%0 : @guaranteed $C, %1 : $Int):
%borrow = begin_borrow %0 : $C %borrow = begin_borrow %0 : $C
%2 = ref_element_addr %borrow : $C, #C.property %2 = ref_element_addr %borrow : $C, #C.property
%3 = begin_access [read] [dynamic] %2 : $*Int %3 = begin_access [modify] [dynamic] %2 : $*Int
%4 = begin_access [modify] [dynamic] %3 : $*Int %4 = begin_access [modify] [dynamic] %3 : $*Int
store %1 to [trivial] %4 : $*Int store %1 to [trivial] %4 : $*Int
end_access %4 : $*Int end_access %4 : $*Int

View File

@@ -471,7 +471,7 @@ bb0:
// def // def
%def = apply %f() : $@convention(thin) () -> @owned AnyObject %def = apply %f() : $@convention(thin) () -> @owned AnyObject
%copy = copy_value %def : $AnyObject %copy = copy_value %def : $AnyObject
%access = begin_access [read] [dynamic] %adr : $*AnyObject %access = begin_access [modify] [dynamic] %adr : $*AnyObject
// use // use
store %def to [init] %adr : $*AnyObject store %def to [init] %adr : $*AnyObject
%obj = load [copy] %access : $*AnyObject %obj = load [copy] %access : $*AnyObject
@@ -519,7 +519,7 @@ bb0:
// def // def
%def = apply %f() : $@convention(thin) () -> @owned AnyObject %def = apply %f() : $@convention(thin) () -> @owned AnyObject
%copy = copy_value %def : $AnyObject %copy = copy_value %def : $AnyObject
%access = begin_access [read] [dynamic] %adr : $*AnyObject %access = begin_access [modify] [dynamic] %adr : $*AnyObject
// use // use
store %def to [init] %adr : $*AnyObject store %def to [init] %adr : $*AnyObject
br bb1 br bb1
@@ -561,7 +561,7 @@ sil [ossa] @testFullOverlapInDefBlock : $@convention(thin) () -> () {
bb0: bb0:
%box = alloc_box ${ var AnyObject }, var, name "x" %box = alloc_box ${ var AnyObject }, var, name "x"
%adr = project_box %box : ${ var AnyObject }, 0 %adr = project_box %box : ${ var AnyObject }, 0
%access = begin_access [read] [dynamic] %adr : $*AnyObject %access = begin_access [modify] [dynamic] %adr : $*AnyObject
%f = function_ref @getObject : $@convention(thin) () -> @owned AnyObject %f = function_ref @getObject : $@convention(thin) () -> @owned AnyObject
// def // def
%def = apply %f() : $@convention(thin) () -> @owned AnyObject %def = apply %f() : $@convention(thin) () -> @owned AnyObject
@@ -609,7 +609,7 @@ sil [ossa] @testFullOverlapBeforeDefBlock : $@convention(thin) () -> () {
bb0: bb0:
%box = alloc_box ${ var AnyObject }, var, name "x" %box = alloc_box ${ var AnyObject }, var, name "x"
%adr = project_box %box : ${ var AnyObject }, 0 %adr = project_box %box : ${ var AnyObject }, 0
%access = begin_access [read] [dynamic] %adr : $*AnyObject %access = begin_access [modify] [dynamic] %adr : $*AnyObject
br bb1 br bb1
bb1: bb1:
@@ -657,7 +657,7 @@ bb0:
// def // def
%def = apply %f() : $@convention(thin) () -> @owned AnyObject %def = apply %f() : $@convention(thin) () -> @owned AnyObject
%copy = copy_value %def : $AnyObject %copy = copy_value %def : $AnyObject
%access = begin_access [read] [dynamic] %adr : $*AnyObject %access = begin_access [modify] [dynamic] %adr : $*AnyObject
// use // use
store %def to [init] %adr : $*AnyObject store %def to [init] %adr : $*AnyObject
destroy_value %copy : $AnyObject destroy_value %copy : $AnyObject
@@ -692,7 +692,7 @@ bb0:
// def // def
%def = apply %f() : $@convention(thin) () -> @owned AnyObject %def = apply %f() : $@convention(thin) () -> @owned AnyObject
%copy = copy_value %def : $AnyObject %copy = copy_value %def : $AnyObject
%access = begin_access [read] [dynamic] %adr : $*AnyObject %access = begin_access [modify] [dynamic] %adr : $*AnyObject
// use // use
store %def to [init] %adr : $*AnyObject store %def to [init] %adr : $*AnyObject
br bb1 br bb1

View File

@@ -988,7 +988,7 @@ bb0(%0 : $*Int):
sil [ossa] @dontExtendAccessScopeOverBeginAccess : $@convention(thin) (@in Klass) -> () { sil [ossa] @dontExtendAccessScopeOverBeginAccess : $@convention(thin) (@in Klass) -> () {
bb0(%0 : $*Klass): bb0(%0 : $*Klass):
%stack = alloc_stack $Klass %stack = alloc_stack $Klass
%access = begin_access [read] [static] %0 : $*Klass %access = begin_access [modify] [static] %0 : $*Klass
copy_addr [take] %access to [init] %stack : $*Klass copy_addr [take] %access to [init] %stack : $*Klass
end_access %access : $*Klass end_access %access : $*Klass
%f = function_ref @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> () %f = function_ref @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> ()

View File

@@ -499,7 +499,7 @@ sil [ossa] @unhandled_user : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass): bb0(%0 : @owned $Klass):
%5 = alloc_stack $Klass %5 = alloc_stack $Klass
store %0 to [init] %5 : $*Klass store %0 to [init] %5 : $*Klass
%104 = begin_access [read] [static] %5 : $*Klass %104 = begin_access [modify] [static] %5 : $*Klass
%105 = load [take] %104 : $*Klass %105 = load [take] %104 : $*Klass
end_access %104 : $*Klass end_access %104 : $*Klass
dealloc_stack %5 : $*Klass dealloc_stack %5 : $*Klass