mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
to work with aggregates containing unknown values. Such aggregates can be generated when an instruction is skipped during constant evaluation and its results are used to create a struct.
387 lines
14 KiB
Plaintext
387 lines
14 KiB
Plaintext
// RUN: %target-sil-opt -test-constant-evaluator %s 2>&1 | %FileCheck %s
|
|
|
|
/// Tests for the skip functionality of the constant evaluator which is
|
|
/// available in the stepwise evaluation mode. The evaluator will be run on
|
|
/// every function defined below whose name starts with `interpret` prefix and
|
|
/// will output the constant value returned by the function or diagnostics
|
|
/// if the evaluation fails. In addition, the evaluator will "skip" evaluating
|
|
/// any call to a function whose name starts with `skip`. Skipping a function
|
|
/// call will conservatively make all operands modified by the call as
|
|
/// unknown symbolic values.
|
|
|
|
sil_stage canonical
|
|
|
|
import Builtin
|
|
import Swift
|
|
|
|
sil @skipNoopFunction : $@convention(thin) (Int32) -> ()
|
|
|
|
// CHECK-LABEL: @interpretCallNoopFunction
|
|
sil @interpretCallNoopFunction : $@convention(thin) () -> Builtin.Int32 {
|
|
bb0:
|
|
%1 = integer_literal $Builtin.Int32, 13
|
|
%2 = struct $Int32 (%1 : $Builtin.Int32)
|
|
%5 = function_ref @skipNoopFunction : $@convention(thin) (Int32) -> ()
|
|
%6 = apply %5(%2) : $@convention(thin) (Int32) -> ()
|
|
%8 = struct_extract %2 : $Int32, #Int32._value
|
|
return %8 : $Builtin.Int32
|
|
} // CHECK: Returns int: 13
|
|
|
|
sil @skipGetInt : $@convention(thin) () -> Int32
|
|
|
|
// CHECK-LABEL: @interpretSkipFunctionReturningValue
|
|
sil hidden @interpretSkipFunctionReturningValue : $@convention(thin) () -> Builtin.Int32 {
|
|
bb0:
|
|
%1 = function_ref @skipGetInt : $@convention(thin) () -> Int32
|
|
%2 = apply %1() : $@convention(thin) () -> Int32
|
|
%3 = struct_extract %2 : $Int32, #Int32._value
|
|
return %3 : $Builtin.Int32
|
|
} // CHECK: Returns unknown
|
|
|
|
sil @skipIncrement : $@convention(thin) (@inout Int32) -> ()
|
|
|
|
// CHECK-LABEL: @interpretSkipEffectfulFunction
|
|
sil hidden @interpretSkipEffectfulFunction : $@convention(thin) () -> Builtin.Int32 {
|
|
bb0:
|
|
%0 = alloc_stack $Int32, var, name "i"
|
|
%1 = integer_literal $Builtin.Int32, 13
|
|
%2 = struct $Int32 (%1 : $Builtin.Int32)
|
|
store %2 to %0 : $*Int32
|
|
%4 = begin_access [modify] [static] %0 : $*Int32
|
|
%5 = function_ref @skipIncrement : $@convention(thin) (@inout Int32) -> ()
|
|
%6 = apply %5(%4) : $@convention(thin) (@inout Int32) -> ()
|
|
end_access %4 : $*Int32
|
|
|
|
%7 = load %0 : $*Int32
|
|
%8 = struct_extract %7 : $Int32, #Int32._value
|
|
dealloc_stack %0 : $*Int32
|
|
return %8 : $Builtin.Int32
|
|
} // CHECK: Returns unknown
|
|
|
|
// CHECK-LABEL: @interpretSkipFunctionNotAffectingResult
|
|
sil hidden @interpretSkipFunctionNotAffectingResult : $@convention(thin) () -> Builtin.Int32 {
|
|
bb0:
|
|
%0 = alloc_stack $Int32, var, name "i"
|
|
%1 = integer_literal $Builtin.Int32, 32
|
|
%2 = struct $Int32 (%1 : $Builtin.Int32)
|
|
store %2 to %0 : $*Int32
|
|
%3 = load %0 : $*Int32
|
|
%4 = begin_access [modify] [static] %0 : $*Int32
|
|
%5 = function_ref @skipIncrement : $@convention(thin) (@inout Int32) -> ()
|
|
%6 = apply %5(%4) : $@convention(thin) (@inout Int32) -> ()
|
|
end_access %4 : $*Int32
|
|
%8 = struct_extract %3 : $Int32, #Int32._value
|
|
dealloc_stack %0 : $*Int32
|
|
return %8 : $Builtin.Int32
|
|
} // CHECK: Returns int: 32
|
|
|
|
// CHECK-LABEL: @interpretSkipFunctionInfluencingBranch
|
|
sil hidden @interpretSkipFunctionInfluencingBranch : $@convention(thin) () -> Builtin.Int32 {
|
|
bb0:
|
|
%0 = alloc_stack $Int32, var, name "i"
|
|
%1 = integer_literal $Builtin.Int32, 32
|
|
%2 = struct $Int32 (%1 : $Builtin.Int32)
|
|
store %2 to %0 : $*Int32
|
|
%4 = begin_access [modify] [static] %0 : $*Int32
|
|
%5 = function_ref @skipIncrement : $@convention(thin) (@inout Int32) -> ()
|
|
%6 = apply %5(%4) : $@convention(thin) (@inout Int32) -> ()
|
|
end_access %4 : $*Int32
|
|
|
|
%7 = load %0 : $*Int32
|
|
%8 = struct_extract %7 : $Int32, #Int32._value
|
|
%9 = integer_literal $Builtin.Int32, 0
|
|
%10 = builtin "cmp_slt_Int32"(%8 : $Builtin.Int32, %9 : $Builtin.Int32) : $Builtin.Int1
|
|
cond_br %10, bb2, bb3
|
|
// CHECK-LABEL: {{.*}}:94:{{.*}}: remark: branch depends on non-constant value produced by an unevaluated instructions
|
|
// CHECK: {{.*}}: note: result of an unevaluated instruction is not a constant
|
|
bb2:
|
|
br bb4
|
|
|
|
bb3:
|
|
br bb4
|
|
|
|
bb4:
|
|
dealloc_stack %0 : $*Int32
|
|
return %8 : $Builtin.Int32
|
|
}
|
|
|
|
// Test skipping struct inits and methods
|
|
|
|
struct S {
|
|
var first: Int64
|
|
var second: Int64
|
|
}
|
|
|
|
sil @skipInitStruct : $@convention(method) (@thin S.Type) -> S
|
|
|
|
sil @skipAddToFirstProperty : $@convention(method) (Int64, @inout S) -> ()
|
|
|
|
// CHECK-LABEL: @interpretMutatingMethod
|
|
sil hidden @interpretMutatingMethod : $@convention(thin) (Int64) -> Builtin.Int64 {
|
|
bb0(%0 : $Int64):
|
|
%2 = alloc_stack $S, var, name "s"
|
|
%3 = metatype $@thin S.Type
|
|
%4 = function_ref @skipInitStruct : $@convention(method) (@thin S.Type) -> S
|
|
%5 = apply %4(%3) : $@convention(method) (@thin S.Type) -> S
|
|
store %5 to %2 : $*S
|
|
%7 = begin_access [modify] [static] %2 : $*S
|
|
%8 = function_ref @skipAddToFirstProperty : $@convention(method) (Int64, @inout S) -> ()
|
|
%9 = apply %8(%0, %7) : $@convention(method) (Int64, @inout S) -> ()
|
|
end_access %7 : $*S
|
|
%11 = begin_access [read] [static] %2 : $*S
|
|
%12 = struct_element_addr %11 : $*S, #S.second
|
|
%13 = load %12 : $*Int64
|
|
end_access %11 : $*S
|
|
%14 = struct_extract %13 : $Int64, #Int64._value
|
|
dealloc_stack %2 : $*S
|
|
return %14 : $Builtin.Int64
|
|
} // CHECK: Returns unknown
|
|
|
|
sil hidden @initStruct : $@convention(method) (@thin S.Type) -> S {
|
|
bb0(%0 : $@thin S.Type):
|
|
%1 = alloc_stack $S, var, name "self"
|
|
%2 = integer_literal $Builtin.Int64, 11
|
|
%3 = struct $Int64 (%2 : $Builtin.Int64)
|
|
%4 = begin_access [modify] [static] %1 : $*S
|
|
%5 = struct_element_addr %4 : $*S, #S.first
|
|
store %3 to %5 : $*Int64
|
|
end_access %4 : $*S
|
|
%8 = integer_literal $Builtin.Int64, 102
|
|
%9 = struct $Int64 (%8 : $Builtin.Int64)
|
|
%10 = begin_access [modify] [static] %1 : $*S
|
|
%11 = struct_element_addr %10 : $*S, #S.second
|
|
store %9 to %11 : $*Int64
|
|
end_access %10 : $*S
|
|
%14 = struct $S (%3 : $Int64, %9 : $Int64)
|
|
dealloc_stack %1 : $*S
|
|
return %14 : $S
|
|
}
|
|
|
|
sil @skipGetInt64 : $@convention(thin) () -> Int64
|
|
|
|
// CHECK-LABEL: @interpretInlinePropertyMutation
|
|
sil hidden @interpretInlinePropertyMutation : $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%0 = alloc_stack $S, var, name "s"
|
|
|
|
// Initialize struct.
|
|
%1 = metatype $@thin S.Type
|
|
%2 = function_ref @initStruct : $@convention(method) (@thin S.Type) -> S
|
|
%3 = apply %2(%1) : $@convention(method) (@thin S.Type) -> S
|
|
store %3 to %0 : $*S
|
|
|
|
// The first property is assigned an unknown value.
|
|
%10 = begin_access [modify] [static] %0 : $*S
|
|
%11 = struct_element_addr %10 : $*S, #S.first
|
|
%12 = function_ref @skipGetInt64 : $@convention(thin) () -> Int64
|
|
%13 = apply %12() : $@convention(thin) () -> Int64
|
|
store %13 to %11 : $*Int64
|
|
end_access %10 : $*S
|
|
|
|
// Read the second property which must be a constant.
|
|
%23 = load %0 : $*S
|
|
%24 = struct_extract %23 : $S, #S.second
|
|
%25 = struct_extract %24 : $Int64, #Int64._value
|
|
dealloc_stack %0 : $*S
|
|
return %25 : $Builtin.Int64
|
|
} // CHECK: Returns int: 102
|
|
|
|
// Test struct initialization where some properties are unknown due to skipped
|
|
// instructions.
|
|
|
|
struct Inner {
|
|
var x: Int64
|
|
}
|
|
|
|
struct Outer {
|
|
var first: Int64
|
|
var second: Inner
|
|
}
|
|
|
|
sil @skipInitInner : $@convention(thin) () -> Inner
|
|
|
|
// CHECK-LABEL: @interpretStructWithUnknownArgs
|
|
sil @interpretStructWithUnknownArgs: $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%1 = integer_literal $Builtin.Int64, 1005
|
|
%2 = struct $Int64 (%1 : $Builtin.Int64)
|
|
%3 = function_ref @skipInitInner : $@convention(thin) () -> Inner
|
|
%4 = apply %3() : $@convention(thin) () -> Inner
|
|
%5 = struct $Outer (%2 : $Int64, %4 : $Inner)
|
|
|
|
%6 = struct_extract %5 : $Outer, #Outer.first
|
|
%7 = struct_extract %6 : $Int64, #Int64._value
|
|
return %7 : $Builtin.Int64
|
|
} // CHECK: Returns int: 1005
|
|
|
|
// CHECK-LABEL: @interpretPiecewiseStructConstruction
|
|
sil @interpretPiecewiseStructConstruction: $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%0 = alloc_stack $Outer, var, name "outer"
|
|
%1 = integer_literal $Builtin.Int64, 101
|
|
%2 = struct $Int64 (%1 : $Builtin.Int64)
|
|
%5 = struct_element_addr %0 : $*Outer, #Outer.first
|
|
store %2 to %5 : $*Int64
|
|
|
|
%6 = function_ref @skipInitInner : $@convention(thin) () -> Inner
|
|
%7 = apply %6() : $@convention(thin) () -> Inner
|
|
%8 = struct_element_addr %0 : $*Outer, #Outer.second
|
|
store %7 to %8 : $*Inner
|
|
|
|
%9 = load %0 : $*Outer
|
|
%10 = struct_extract %9 : $Outer, #Outer.first
|
|
%11 = struct_extract %10 : $Int64, #Int64._value
|
|
dealloc_stack %0 : $*Outer
|
|
return %11 : $Builtin.Int64
|
|
} // CHECK: Returns int: 101
|
|
|
|
protocol InnerProto {
|
|
var x: Int64 { get }
|
|
}
|
|
|
|
struct OuterWithProto {
|
|
var first: Int64
|
|
var second: InnerProto
|
|
}
|
|
|
|
sil @skipConstructInnerProto : $@convention(thin) () -> @out InnerProto
|
|
|
|
// CHECK-LABEL: @interpretStructConstructionWithCopyAddr
|
|
sil @interpretStructConstructionWithCopyAddr: $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%1 = alloc_stack $OuterWithProto
|
|
%2 = integer_literal $Builtin.Int64, 201
|
|
%3 = struct $Int64 (%2 : $Builtin.Int64)
|
|
%5 = struct_element_addr %1 : $*OuterWithProto, #OuterWithProto.first
|
|
store %3 to %5 : $*Int64
|
|
%8 = alloc_stack $InnerProto
|
|
%9 = function_ref @skipConstructInnerProto : $@convention(thin) () -> @out InnerProto
|
|
%10 = apply %9(%8) : $@convention(thin) () -> @out InnerProto
|
|
%12 = struct_element_addr %1 : $*OuterWithProto, #OuterWithProto.second
|
|
copy_addr [take] %8 to [init] %12 : $*InnerProto
|
|
dealloc_stack %8 : $*InnerProto
|
|
%19 = struct_element_addr %1 : $*OuterWithProto, #OuterWithProto.first
|
|
%20 = load %19 : $*Int64
|
|
destroy_addr %1 : $*OuterWithProto
|
|
dealloc_stack %1 : $*OuterWithProto
|
|
%21 = struct_extract %20 : $Int64, #Int64._value
|
|
return %21 : $Builtin.Int64
|
|
} // CHECK: Returns int: 201
|
|
|
|
// Test the interpreter on an interleaving of skippable and interpretable
|
|
// instructions that mimics the implementation of os log.
|
|
|
|
sil @skipAppendInner : $@convention(method) (@inout Inner) -> ()
|
|
|
|
sil hidden @increment : $@convention(thin) (@inout Int64) -> () {
|
|
bb0(%0 : $*Int64):
|
|
%2 = integer_literal $Builtin.Int64, 1
|
|
%3 = begin_access [modify] [static] %0 : $*Int64
|
|
%4 = struct_element_addr %3 : $*Int64, #Int64._value
|
|
%5 = load %4 : $*Builtin.Int64
|
|
%6 = integer_literal $Builtin.Int1, -1
|
|
%7 = builtin "sadd_with_overflow_Int64"(%5 : $Builtin.Int64, %2 : $Builtin.Int64, %6 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1)
|
|
%8 = tuple_extract %7 : $(Builtin.Int64, Builtin.Int1), 0
|
|
%9 = tuple_extract %7 : $(Builtin.Int64, Builtin.Int1), 1
|
|
cond_fail %9 : $Builtin.Int1
|
|
%11 = struct $Int64 (%8 : $Builtin.Int64)
|
|
store %11 to %3 : $*Int64
|
|
%13 = tuple ()
|
|
end_access %3 : $*Int64
|
|
%15 = tuple ()
|
|
return %15 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: @interpretInterleavedSkipAndNonSkip
|
|
sil @interpretInterleavedSkipAndNonSkip : $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%0 = alloc_stack $Outer, var, name "outer"
|
|
%1 = integer_literal $Builtin.Int64, 101
|
|
%2 = struct $Int64 (%1 : $Builtin.Int64)
|
|
%5 = struct_element_addr %0 : $*Outer, #Outer.first
|
|
store %2 to %5 : $*Int64
|
|
%6 = function_ref @skipInitInner : $@convention(thin) () -> Inner
|
|
%7 = apply %6() : $@convention(thin) () -> Inner
|
|
%8 = struct_element_addr %0 : $*Outer, #Outer.second
|
|
store %7 to %8 : $*Inner
|
|
|
|
// Mutate the first component and second component in an interleaved way.
|
|
%9 = struct_element_addr %0: $*Outer, #Outer.first
|
|
%10 = function_ref @increment : $@convention(thin) (@inout Int64) -> ()
|
|
%11 = apply %10(%9) : $@convention(thin) (@inout Int64) -> ()
|
|
|
|
%12 = struct_element_addr %0: $*Outer, #Outer.second
|
|
%13 = function_ref @skipAppendInner : $@convention(method) (@inout Inner) -> ()
|
|
%14 = apply %13(%12) : $@convention(method) (@inout Inner) -> ()
|
|
|
|
// Mutate the components once more.
|
|
%15 = struct_element_addr %0: $*Outer, #Outer.first
|
|
%16 = function_ref @increment : $@convention(thin) (@inout Int64) -> ()
|
|
%17 = apply %16(%15) : $@convention(thin) (@inout Int64) -> ()
|
|
|
|
%18 = struct_element_addr %0: $*Outer, #Outer.second
|
|
%19 = function_ref @skipAppendInner : $@convention(method) (@inout Inner) -> ()
|
|
%20 = apply %19(%18) : $@convention(method) (@inout Inner) -> ()
|
|
|
|
// Return the first property which should be a constant.
|
|
%30 = load %0 : $*Outer
|
|
%31 = struct_extract %30 : $Outer, #Outer.first
|
|
%32 = struct_extract %31 : $Int64, #Int64._value
|
|
dealloc_stack %0 : $*Outer
|
|
return %32 : $Builtin.Int64
|
|
} // CHECK: Returns int: 103
|
|
|
|
// Test whether the constant evaluator can handle reading and writing into
|
|
// structs containing unknown symbolic values created by skipping functions.
|
|
|
|
struct UnknownStruct {
|
|
let value: Int64
|
|
}
|
|
|
|
struct StructWithSkippedElements {
|
|
let knownValue: Int64
|
|
let unknownValue: UnknownStruct
|
|
}
|
|
|
|
sil @skipUnknownStructCreate : $@convention(thin) () -> UnknownStruct
|
|
|
|
// CHECK-LABEL: @interpretStructsWithSkippedElements
|
|
sil @interpretStructsWithSkippedElements : $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%0 = integer_literal $Builtin.Int64, 301
|
|
%1 = struct $Int64 (%0 : $Builtin.Int64)
|
|
%2 = function_ref @skipUnknownStructCreate : $@convention(thin) () -> UnknownStruct
|
|
%3 = apply %2() : $@convention(thin) () -> UnknownStruct
|
|
%4 = struct $StructWithSkippedElements (%1 : $Int64, %3 : $UnknownStruct)
|
|
%5 = alloc_stack $StructWithSkippedElements, var, name "s"
|
|
store %4 to %5 : $*StructWithSkippedElements
|
|
|
|
%6 = struct_element_addr %5 : $*StructWithSkippedElements, #StructWithSkippedElements.unknownValue
|
|
%7 = struct_element_addr %6 : $*UnknownStruct, #UnknownStruct.value
|
|
%8 = load %7 : $*Int64
|
|
%9 = struct_extract %8 : $Int64, #Int64._value
|
|
dealloc_stack %5 : $*StructWithSkippedElements
|
|
return %9 : $Builtin.Int64
|
|
} // CHECK: Returns unknown
|
|
|
|
// CHECK-LABEL: @interpretStructsWithSkippedElementsRW
|
|
sil @interpretStructsWithSkippedElementsRW : $@convention(thin) () -> Builtin.Int64 {
|
|
bb0:
|
|
%0 = integer_literal $Builtin.Int64, 301
|
|
%1 = struct $Int64 (%0 : $Builtin.Int64)
|
|
%2 = function_ref @skipUnknownStructCreate : $@convention(thin) () -> UnknownStruct
|
|
%3 = apply %2() : $@convention(thin) () -> UnknownStruct
|
|
%4 = struct $StructWithSkippedElements (%1 : $Int64, %3 : $UnknownStruct)
|
|
%5 = alloc_stack $StructWithSkippedElements, var, name "s"
|
|
store %4 to %5 : $*StructWithSkippedElements
|
|
|
|
%6 = struct_element_addr %5 : $*StructWithSkippedElements, #StructWithSkippedElements.unknownValue
|
|
%7 = struct_element_addr %6 : $*UnknownStruct, #UnknownStruct.value
|
|
store %1 to %7 : $*Int64
|
|
%8 = struct_element_addr %5 : $*StructWithSkippedElements, #StructWithSkippedElements.knownValue
|
|
%9 = load %8 : $*Int64
|
|
%10 = struct_extract %9 : $Int64, #Int64._value
|
|
dealloc_stack %5 : $*StructWithSkippedElements
|
|
return %10 : $Builtin.Int64
|
|
} // CHECK: Returns int: 301
|