Skip to content

Commit e5aaa68

Browse files
committed
[sil] Do not eliminate (apply (partial_apply)) if the partial_apply captures an @in parameter and handle @in_guaranteed appropriately.
Otherwise the code as written miscompiles code like: ``` @inline(never) func consumeSelf<T>(_ t : __owned T) { print("Consuming self!") print(t) } class Klass {} struct S<T> { let t: T? = (Klass() as! T) @inline(__always) __consuming func foo(_ t: T) { consumeSelf(self) } } public func test<T>(_ t: __owned T) { let k = S<T>() let f = k.foo for _ in 0..<1024 { f(t) } } test(Klass()) ``` As one can tell, without annotations it is hard to create an example like the above, but there is no reason why we couldn't emit more code like this from the frontend. If the parameter is guaranteed though, the current impl is fine for @in_guaranteed since in the loop, we do not need to retain or release the value. rdar://58885352
1 parent 956b9f2 commit e5aaa68

File tree

4 files changed

+355
-268
lines changed

4 files changed

+355
-268
lines changed

lib/SILOptimizer/Utils/PartialApplyCombiner.cpp

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,29 +90,38 @@ bool PartialApplyCombiner::allocateTemporaries() {
9090
for (unsigned i : indices(argList)) {
9191
SILValue arg = argList[i];
9292
SILParameterInfo param = paramList[i];
93+
// If we do not have an indirect parameter, we do not need to do any further
94+
// work here.
95+
if (!pai->getSubstCalleeConv().isSILIndirect(param))
96+
continue;
97+
98+
// If our indirect parameter is mutating, we can just skip it as well.
9399
if (param.isIndirectMutating())
94100
continue;
95101

96-
// Create a temporary and copy the argument into it, if:
97-
// - the argument stems from an alloc_stack
98-
// - the argument is consumed by the callee and is indirect
99-
// (e.g. it is an @in argument)
100-
if (isa<AllocStackInst>(arg) ||
101-
(param.isConsumed() &&
102-
pai->getSubstCalleeConv().isSILIndirect(param))) {
103-
// If the argument has a dependent type, then we can not create a
104-
// temporary for it at the beginning of the function, so we must bail.
105-
//
106-
// TODO: This is because we are inserting alloc_stack at the beginning/end
107-
// of functions where the dependent type may not exist yet.
108-
if (arg->getType().hasOpenedExistential())
109-
return false;
110-
111-
// If the temporary is non-trivial, we need to destroy it later.
112-
if (!arg->getType().isTrivial(*pai->getFunction()))
113-
needsDestroys = true;
114-
argsToHandle.push_back(std::make_pair(arg, i));
102+
// If we are consuming an indirect parameter. Bail! We do not support that
103+
// today!
104+
if (param.isConsumed()) {
105+
return false;
106+
}
107+
108+
// Otherwise, we must have a guaranteed parameter. If we don't, bail.
109+
if (!param.isGuaranteed()) {
110+
return false;
115111
}
112+
113+
// If the argument has a dependent type, then we can not create a
114+
// temporary for it at the beginning of the function, so we must bail.
115+
//
116+
// TODO: This is because we are inserting alloc_stack at the beginning/end
117+
// of functions where the dependent type may not exist yet.
118+
if (arg->getType().hasOpenedExistential())
119+
return false;
120+
121+
// If the temporary is non-trivial, we need to destroy it later.
122+
if (!arg->getType().isTrivial(*pai->getFunction()))
123+
needsDestroys = true;
124+
argsToHandle.push_back(std::make_pair(arg, i));
116125
}
117126

118127
if (needsDestroys) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %target-run-simple-swift(-Osize)
2+
3+
// REQUIRES: executable_test
4+
5+
@inline(never)
6+
func consumeSelf<T>(_ t : __owned T) {
7+
print("Consuming self!")
8+
print(t)
9+
}
10+
11+
class Klass {}
12+
struct S<T> {
13+
let t: T? = (Klass() as! T)
14+
15+
@inline(__always)
16+
__consuming func foo(_ t: T) {
17+
consumeSelf(self)
18+
}
19+
}
20+
21+
public func test<T>(_ t: __owned T) {
22+
let k = S<T>()
23+
let f = k.foo
24+
25+
for _ in 0..<1024 {
26+
f(t)
27+
}
28+
}
29+
30+
test(Klass())

test/SILOptimizer/sil_combine.sil

Lines changed: 0 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -2905,231 +2905,6 @@ bb0:
29052905
return %2 : $Builtin.Int1
29062906
}
29072907

2908-
class CC1 {
2909-
deinit
2910-
init()
2911-
}
2912-
2913-
class CC2 {
2914-
deinit
2915-
init()
2916-
}
2917-
2918-
class CC3 {
2919-
deinit
2920-
init()
2921-
}
2922-
2923-
class CC4 {
2924-
deinit
2925-
init()
2926-
}
2927-
2928-
2929-
// Function that takes different kinds of arguments: @in, @guaranteed, @owned,
2930-
sil @closure_with_in_guaranteed_owned_in_args : $@convention(method) (@in CC2, @guaranteed CC1, @owned CC3, @in CC4) -> Optional<Int>
2931-
2932-
// Test the peephole performing apply{partial_apply(x, y, z)}(a) -> apply(a, x, y, z)
2933-
// We need to check the following:
2934-
// - all arguments of a partial_apply, which are either results of a stack_alloc or consumed indirect arguments
2935-
// should be copied into temporaries. This should happen just before that partial_apply instruction.
2936-
// - The temporaries are allocated at the beginning of the function and deallocated at the end.
2937-
// - Before each apply of the partial_apply, we retain values of any arguments which are of non-address type.
2938-
// This is required because they could be consumed (i.e. released by the callee).
2939-
// - After each apply of the partial_apply, we release values of any arguments which are non-consumed by the callee (e.g. @guaranteed ones)
2940-
2941-
// CHECK-LABEL: sil @test_apply_of_partial_apply
2942-
2943-
// CHECK: bb0{{.*}}:
2944-
// A new temporary should have been created for each alloc_stack argument passed to partial_apply
2945-
// CHECK: [[TMP:%[0-9]+]] = alloc_stack $CC4
2946-
// CHECK: [[CLOSURE:%[0-9]+]] = function_ref @closure_with_in_guaranteed_owned_in_args
2947-
// Copy the original value of the argument into a temporary
2948-
// CHECK: copy_addr {{.*}} to [initialization] [[TMP]] : $*CC4
2949-
// CHECK-NOT: partial_apply
2950-
2951-
// CHECK: bb1:
2952-
// CHECK-NOT: partial_apply
2953-
// Check that the peephole inserted a retain of the closure's guaranteed argument
2954-
// CHECK: strong_retain %{{[0-9]+}} : $CC1
2955-
// Check that the peephole inserted a retain of the closure's owned argument
2956-
// CHECK: strong_retain %{{[0-9]+}} : $CC3
2957-
// CHECK: apply [[CLOSURE]]
2958-
// Check that the peephole inserted a release the closure's guaranteed argument
2959-
// CHECK: strong_release %{{[0-9]+}} : $CC1
2960-
// CHECK-NOT: partial_apply
2961-
// Retain the guaranteed argument
2962-
// CHECK: strong_retain %{{[0-9]+}} : $CC1
2963-
// Retain the owned argument
2964-
// CHECK: strong_retain %{{[0-9]+}} : $CC3
2965-
// CHECK: apply [[CLOSURE]]({{.*}}, [[TMP]])
2966-
// Check that the peephole inserted a release the guaranteed argument
2967-
// CHECK: strong_release %{{[0-9]+}} : $CC1
2968-
// Release the @owned CC4 argument of the function
2969-
// CHECK: load {{%[0-9]+}} : $*CC4
2970-
// CHECK: strong_release {{%[0-9]+}} : $CC4
2971-
// CHECK: br bb3
2972-
2973-
// CHECK: bb2:
2974-
// CHECK-NOT: partial_apply
2975-
// Check that the peephole inserted a retain of the closure's guaranteed argument
2976-
// CHECK: strong_retain %{{[0-9]+}} : $CC1
2977-
// Check that the peephole inserted a retain of the closure's owned argument
2978-
// CHECK: strong_retain %{{[0-9]+}} : $CC3
2979-
// CHECK: apply [[CLOSURE]]({{.*}}, [[TMP]])
2980-
// Check that the peephole inserted a release the closure's guaranteed argument
2981-
// CHECK: strong_release %{{[0-9]+}} : $CC1
2982-
// Release the @owned CC4 argument of the function
2983-
// CHECK: load {{%[0-9]+}} : $*CC4
2984-
// CHECK: strong_release {{%[0-9]+}} : $CC4
2985-
// CHECK: br bb3
2986-
2987-
// bb3:
2988-
// dealloc_stack [[TMP]] : $CC4
2989-
2990-
sil @test_apply_of_partial_apply : $@convention(thin) (@in Optional<CC2>, @guaranteed CC1, @guaranteed CC3, @guaranteed CC4, @guaranteed CC2) -> Optional<Int> {
2991-
bb0(%0 : $*Optional<CC2>, %1 : $CC1, %2 : $CC3, %3 : $CC4, %4 : $CC2):
2992-
2993-
%5 = function_ref @closure_with_in_guaranteed_owned_in_args : $@convention(method) (@in CC2, @guaranteed CC1, @owned CC3, @in CC4) -> Optional<Int>
2994-
%6 = alloc_stack $CC3
2995-
store %2 to %6 : $*CC3
2996-
%8 = load %6 : $*CC3
2997-
%9 = alloc_stack $CC4
2998-
store %3 to %9 : $*CC4
2999-
%11 = load %9 : $*CC4
3000-
strong_retain %1 : $CC1
3001-
%12 = partial_apply %5(%1, %8, %9) : $@convention(method) (@in CC2, @guaranteed CC1, @owned CC3, @in CC4) -> Optional<Int>
3002-
dealloc_stack %9 : $*CC4
3003-
dealloc_stack %6 : $*CC3
3004-
%15 = convert_function %12 : $@callee_owned (@in CC2) -> Optional<Int> to $@callee_owned (@in CC2) -> (Optional<Int>, @error Error)
3005-
%16 = alloc_stack $Optional<Int>
3006-
%17 = alloc_stack $Optional<CC2>
3007-
copy_addr %0 to [initialization] %17 : $*Optional<CC2>
3008-
switch_enum_addr %17 : $*Optional<CC2>, case #Optional.some!enumelt.1: bb1, case #Optional.none!enumelt: bb2
3009-
3010-
bb1:
3011-
%21 = unchecked_take_enum_data_addr %17 : $*Optional<CC2>, #Optional.some!enumelt.1
3012-
3013-
%22 = alloc_stack $CC2
3014-
copy_addr [take] %21 to [initialization] %22 : $*CC2
3015-
%24 = alloc_stack $CC2
3016-
copy_addr %22 to [initialization] %24 : $*CC2
3017-
destroy_addr %22 : $*CC2
3018-
strong_retain %15 : $@callee_owned (@in CC2) -> (Optional<Int>, @error Error)
3019-
%28 = apply %12(%24) : $@callee_owned (@in CC2) -> Optional<Int>
3020-
store %28 to %16 : $*Optional<Int>
3021-
dealloc_stack %24 : $*CC2
3022-
dealloc_stack %22 : $*CC2
3023-
3024-
%102 = alloc_stack $CC2
3025-
copy_addr [take] %21 to [initialization] %102 : $*CC2
3026-
%104 = alloc_stack $CC2
3027-
copy_addr %102 to [initialization] %104 : $*CC2
3028-
destroy_addr %102 : $*CC2
3029-
strong_retain %15 : $@callee_owned (@in CC2) -> (Optional<Int>, @error Error)
3030-
%108 = apply %12(%104) : $@callee_owned (@in CC2) -> Optional<Int>
3031-
store %108 to %16 : $*Optional<Int>
3032-
dealloc_stack %104 : $*CC2
3033-
dealloc_stack %102 : $*CC2
3034-
3035-
dealloc_stack %17 : $*Optional<CC2>
3036-
3037-
br bb3
3038-
3039-
bb2:
3040-
%39 = alloc_stack $CC2
3041-
store %4 to %39 : $*CC2
3042-
strong_retain %15 : $@callee_owned (@in CC2) -> (Optional<Int>, @error Error)
3043-
%42 = apply %12(%39) : $@callee_owned (@in CC2) -> Optional<Int>
3044-
store %42 to %16 : $*Optional<Int>
3045-
dealloc_stack %39 : $*CC2
3046-
dealloc_stack %17 : $*Optional<CC2>
3047-
br bb3
3048-
3049-
bb3:
3050-
destroy_addr %0 : $*Optional<CC2>
3051-
strong_release %15 : $@callee_owned (@in CC2) -> (Optional<Int>, @error Error)
3052-
%36 = load %16 : $*Optional<Int>
3053-
dealloc_stack %16 : $*Optional<Int>
3054-
return %36 : $Optional<Int>
3055-
}
3056-
3057-
// Test if we insert the right stack/dealloc-stack when converting apply{partial_apply}.
3058-
3059-
// CHECK-LABEL: sil @test_stack_insertion_for_partial_apply_apply
3060-
// CHECK: bb0({{.*}}):
3061-
// CHECK-NEXT: [[T:%[0-9]+]] = alloc_stack $Int
3062-
// CHECK-NEXT: alloc_stack $Bool
3063-
// CHECK-NEXT: [[S2:%[0-9]+]] = alloc_stack $Int
3064-
// CHECK: copy_addr [[S2]] to [initialization] [[T]]
3065-
// CHECK: dealloc_stack [[S2]] : $*Int
3066-
// CHECK: apply
3067-
// CHECK: bb1:
3068-
// CHECK-NOT: dealloc_stack
3069-
// CHECK: bb2:
3070-
// CHECK: dealloc_stack {{.*}} : $*Bool
3071-
// CHECK: dealloc_stack [[T]] : $*Int
3072-
// CHECK: return
3073-
sil @test_stack_insertion_for_partial_apply_apply : $@convention(thin) (Int, Double) -> () {
3074-
bb0(%0 : $Int, %1 : $Double):
3075-
%s1 = alloc_stack $Bool
3076-
%s2 = alloc_stack $Int
3077-
store %0 to %s2 : $*Int
3078-
%f1 = function_ref @callee : $@convention(thin) (Double, @in_guaranteed Int) -> ()
3079-
%pa = partial_apply %f1(%s2) : $@convention(thin) (Double, @in_guaranteed Int) -> ()
3080-
dealloc_stack %s2 : $*Int
3081-
%a1 = apply %pa(%1) : $@callee_owned (Double) -> ()
3082-
cond_br undef, bb1, bb2
3083-
3084-
bb1:
3085-
%f2 = function_ref @noreturn_func : $@convention(thin) () -> Never
3086-
%a2 = apply %f2() : $@convention(thin) () -> Never
3087-
unreachable
3088-
3089-
bb2:
3090-
dealloc_stack %s1 : $*Bool
3091-
%r = tuple ()
3092-
return %r : $()
3093-
}
3094-
3095-
// CHECK-LABEL: sil @test_generic_partial_apply_apply
3096-
// CHECK: bb0([[ARG0:%.*]] : $*T, [[ARG1:%.*]] : $*T):
3097-
// CHECK-NEXT: [[TMP:%.*]] = alloc_stack $T
3098-
// CHECK: [[FN:%.*]] = function_ref @generic_callee
3099-
// CHECK-NEXT: copy_addr [[ARG1]] to [initialization] [[TMP]] : $*T
3100-
// CHECK-NEXT: destroy_addr [[ARG1]]
3101-
// CHECK-NEXT: apply [[FN]]<T, T>([[ARG0]], [[TMP]])
3102-
// CHECK-NEXT: destroy_addr [[TMP]]
3103-
// CHECK-NEXT: tuple
3104-
// CHECK-NEXT: dealloc_stack [[TMP]]
3105-
// CHECK-NEXT: return
3106-
sil @test_generic_partial_apply_apply : $@convention(thin) <T> (@in T, @in T) -> () {
3107-
bb0(%0 : $*T, %1 : $*T):
3108-
%f1 = function_ref @generic_callee : $@convention(thin) <T, U> (@in T, @in U) -> ()
3109-
%pa = partial_apply %f1<T, T>(%1) : $@convention(thin) <T, U> (@in T, @in U) -> ()
3110-
%a1 = apply %pa(%0) : $@callee_owned (@in T) -> ()
3111-
%r = tuple ()
3112-
return %r : $()
3113-
}
3114-
3115-
// CHECK-LABEL: sil @test_existential_partial_apply_apply
3116-
// CHECK: bb0(%0 : $*FakeProtocol):
3117-
// CHECK-NEXT: [[OPEN:%.*]] = open_existential_addr immutable_access
3118-
// CHECK-NEXT: [[FN:%.*]] = witness_method
3119-
// CHECK-NEXT: apply [[FN]]<@opened("5DD6F3D0-808A-11E6-93A0-34363BD08DA0") FakeProtocol>([[OPEN]])
3120-
// CHECK-NEXT: tuple
3121-
// CHECK-NEXT: return
3122-
sil @test_existential_partial_apply_apply : $@convention(thin) (@in FakeProtocol) -> () {
3123-
bb0(%0: $*FakeProtocol):
3124-
%o = open_existential_addr immutable_access %0 : $*FakeProtocol to $*@opened("5DD6F3D0-808A-11E6-93A0-34363BD08DA0") FakeProtocol
3125-
%f1 = witness_method $@opened("5DD6F3D0-808A-11E6-93A0-34363BD08DA0") FakeProtocol, #FakeProtocol.requirement!1, %o : $*@opened("5DD6F3D0-808A-11E6-93A0-34363BD08DA0") FakeProtocol : $@convention(witness_method: FakeProtocol) <T where T : FakeProtocol> (@in_guaranteed T) -> ()
3126-
%pa = partial_apply %f1<@opened("5DD6F3D0-808A-11E6-93A0-34363BD08DA0") FakeProtocol>() : $@convention(witness_method: FakeProtocol) <T where T : FakeProtocol> (@in_guaranteed T) -> ()
3127-
%a1 = apply %pa(%o) : $@callee_owned (@in_guaranteed @opened("5DD6F3D0-808A-11E6-93A0-34363BD08DA0") FakeProtocol) -> ()
3128-
3129-
%r = tuple ()
3130-
return %r : $()
3131-
}
3132-
31332908
sil @callee : $@convention(thin) (Double, @in_guaranteed Int) -> ()
31342909
sil @generic_callee : $@convention(thin) <T, U> (@in T, @in U) -> ()
31352910
sil @noreturn_func : $@convention(thin) () -> Never

0 commit comments

Comments
 (0)