Skip to content

Commit 6d9f532

Browse files
author
Raj Barik
committed
Fix ExistentialSpecializer: Recursive function specialization
1 parent 82c33dc commit 6d9f532

File tree

4 files changed

+234
-4
lines changed

4 files changed

+234
-4
lines changed

lib/SILOptimizer/FunctionSignatureTransforms/ExistentialSpecializer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,14 @@ void ExistentialSpecializer::specializeExistentialArgsInAppliesWithinFunction(
280280

281281
auto *Callee = Apply.getReferencedFunction();
282282

283+
/// Handle recursion! Do not modify F right now.
284+
if (Callee == &F) {
285+
LLVM_DEBUG(llvm::dbgs() << "ExistentialSpecializer Pass: Bail! Due to "
286+
"recursion.\n";
287+
I->dump(););
288+
continue;
289+
}
290+
283291
/// Determine the arguments that can be specialized.
284292
llvm::SmallDenseMap<int, ExistentialTransformArgumentDescriptor>
285293
ExistentialArgDescriptor;

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -689,10 +689,18 @@ SILCombiner::buildConcreteOpenedExistentialInfoFromSoleConformingType(
689689
} else {
690690
auto ArgType = ArgOperand.get()->getType();
691691
auto SwiftArgType = ArgType.getASTType();
692-
if (!ArgType.isExistentialType() || ArgType.isAnyObject() ||
693-
SwiftArgType->isAny())
694-
return None;
695-
PD = dyn_cast<ProtocolDecl>(SwiftArgType->getAnyNominal());
692+
/// If the argtype is an opened existential conforming to a protocol type
693+
/// and that the protocol type has a sole conformance, then we can propagate
694+
/// concrete type for it as well.
695+
ArchetypeType *archetypeTy;
696+
if (SwiftArgType->isOpenedExistential() &&
697+
(archetypeTy = dyn_cast<ArchetypeType>(SwiftArgType)) &&
698+
(archetypeTy->getConformsTo().size() == 1)) {
699+
PD = archetypeTy->getConformsTo()[0];
700+
} else if (ArgType.isExistentialType() && !ArgType.isAnyObject() &&
701+
!SwiftArgType->isAny()) {
702+
PD = dyn_cast<ProtocolDecl>(SwiftArgType->getAnyNominal());
703+
}
696704
}
697705

698706
if (!PD)
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// RUN: %target-sil-opt -wmo -enable-sil-verify-all %s -enable-sil-existential-specializer -existential-specializer -inline -sil-combine -generic-specializer -devirtualizer
2+
3+
// This file tests existential specializer transformation followed by concrete type propagation and generic specialization leading to a devirtualization of a witness method call.
4+
5+
sil_stage canonical
6+
7+
import Builtin
8+
import Swift
9+
import SwiftShims
10+
11+
protocol RP {
12+
func getThres() -> Int32
13+
}
14+
15+
class RC : RP {
16+
@inline(never) func getThres() -> Int32
17+
}
18+
19+
sil hidden [noinline] @$s6simple2RCC8getThress5Int32VyF : $@convention(method) (@guaranteed RC) -> Int32 {
20+
bb0(%0 : $RC):
21+
%1 = integer_literal $Builtin.Int32, 10
22+
%2 = struct $Int32 (%1 : $Builtin.Int32)
23+
return %2 : $Int32
24+
}
25+
26+
// Note: The function_ref of "function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool" in the transformed code was originally a "function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool" taking an existential parameter and has been generic specialized after existential specialization.
27+
// CHECK-LABEL: sil hidden [noinline] @$s6simple12find_wrapperSbyF : $@convention(thin) () -> Bool {
28+
// CHECK: bb0:
29+
// CHECK: alloc_ref
30+
// CHECK: integer_literal
31+
// CHECK: struct
32+
// CHECK: alloc_stack
33+
// CHECK: store
34+
// CHECK: strong_retain
35+
// CHECK: function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool
36+
// CHECK: load
37+
// CHECK: apply
38+
// CHECK: destroy_addr
39+
// CHECK: dealloc_stack
40+
// CHECK: strong_release
41+
// CHECK: return
42+
// CHECK-LABEL: }
43+
sil hidden [noinline] @$s6simple12find_wrapperSbyF : $@convention(thin) () -> Bool {
44+
bb0:
45+
%0 = alloc_ref $RC
46+
%3 = integer_literal $Builtin.Int32, 0
47+
%4 = struct $Int32 (%3 : $Builtin.Int32)
48+
%5 = alloc_stack $RP
49+
%6 = init_existential_addr %5 : $*RP, $RC
50+
store %0 to %6 : $*RC
51+
%8 = function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
52+
strong_retain %0 : $RC
53+
%10 = apply %8(%4, %5) : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
54+
destroy_addr %5 : $*RP
55+
dealloc_stack %5 : $*RP
56+
strong_release %0 : $RC
57+
return %10 : $Bool
58+
}
59+
60+
sil private [transparent] [thunk] @$s6simple2RCCAA2RPA2aDP8getThress5Int32VyFTW : $@convention(witness_method: RP) (@in_guaranteed RC) -> Int32 {
61+
bb0(%0 : $*RC):
62+
%1 = load %0 : $*RC
63+
%2 = class_method %1 : $RC, #RC.getThres!1 : (RC) -> () -> Int32, $@convention(method) (@guaranteed RC) -> Int32
64+
%3 = apply %2(%1) : $@convention(method) (@guaranteed RC) -> Int32
65+
return %3 : $Int32
66+
}
67+
68+
69+
// Note 1: The function_ref of "function_ref @$s6simple2RCC8getThress5Int32VyF : $@convention(method) (@guaranteed RC) -> Int32" in the transformed code was originally a witness method "witness_method $@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP, #RP.getThres!1 : <Self where Self : RP> (Self) -> () -> Int32, %4 : $*@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP : $@convention(witness_method: RP) <τ_0_0 where τ_0_0 : RP> (@in_guaranteed τ_0_0) -> Int32" and has been devirtualized after existential specialization.
70+
// Note 2: The function_ref of "function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool" in the transformed code was originally a "function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool" taking an existential parameter and has been generic specialized after existential specialization.
71+
// CHECK-LABEL: sil shared [noinline] @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool {
72+
// CHECK: bb0
73+
// CHECK: alloc_stack
74+
// CHECK: store
75+
// CHECK: alloc_stack
76+
// CHECK: init_existential_addr
77+
// CHECK: copy_addr
78+
// CHECK: open_existential_addr
79+
// CHECK: unchecked_addr_cast
80+
// CHECK: load
81+
// CHECK: function_ref @$s6simple2RCC8getThress5Int32VyF : $@convention(method) (@guaranteed RC) -> Int32
82+
// CHECK: apply
83+
// CHECK: struct_extract
84+
// CHECK: struct_extract
85+
// CHECK: builtin
86+
// CHECK: cond_br
87+
// CHECK: bb1:
88+
// CHECK: integer_literal
89+
// CHECK: integer_literal
90+
// CHECK: builtin
91+
// CHECK: tuple_extract
92+
// CHECK: struct
93+
// CHECK: function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool
94+
// CHECK: load
95+
// CHECK: apply
96+
// CHECK: br
97+
// CHECK: bb2:
98+
// CHECK: integer_literal
99+
// CHECK: struct
100+
// CHECK: br
101+
// CHECK: bb3
102+
// CHECK: dealloc_stack
103+
// CHECK: dealloc_stack
104+
// CHECK: return
105+
// CHECK-LABEL: }
106+
sil hidden [noinline] @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool {
107+
bb0(%0 : $Int32, %1 : $*RP):
108+
%4 = open_existential_addr immutable_access %1 : $*RP to $*@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP
109+
%5 = witness_method $@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP, #RP.getThres!1 : <Self where Self : RP> (Self) -> () -> Int32, %4 : $*@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP : $@convention(witness_method: RP) <τ_0_0 where τ_0_0 : RP> (@in_guaranteed τ_0_0) -> Int32
110+
%6 = apply %5<@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP>(%4) : $@convention(witness_method: RP) <τ_0_0 where τ_0_0 : RP> (@in_guaranteed τ_0_0) -> Int32
111+
%7 = struct_extract %0 : $Int32, #Int32._value
112+
%8 = struct_extract %6 : $Int32, #Int32._value
113+
%9 = builtin "cmp_slt_Int32"(%7 : $Builtin.Int32, %8 : $Builtin.Int32) : $Builtin.Int1
114+
cond_br %9, bb2, bb1
115+
116+
bb1:
117+
%11 = integer_literal $Builtin.Int1, -1
118+
%12 = struct $Bool (%11 : $Builtin.Int1)
119+
br bb3(%12 : $Bool)
120+
121+
bb2:
122+
%14 = integer_literal $Builtin.Int32, 1
123+
%15 = integer_literal $Builtin.Int1, -1
124+
%16 = builtin "sadd_with_overflow_Int32"(%7 : $Builtin.Int32, %14 : $Builtin.Int32, %15 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1)
125+
%17 = tuple_extract %16 : $(Builtin.Int32, Builtin.Int1), 0
126+
%18 = struct $Int32 (%17 : $Builtin.Int32)
127+
%19 = function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
128+
%20 = apply %19(%18, %1) : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
129+
br bb3(%20 : $Bool)
130+
131+
bb3(%22 : $Bool):
132+
return %22 : $Bool
133+
}
134+
135+
sil_vtable RC {
136+
#RC.getThres!1: (RC) -> () -> Int32 : @$s6simple2RCC8getThress5Int32VyF
137+
}
138+
139+
sil_witness_table hidden RC: RP module simple {
140+
method #RP.getThres!1: <Self where Self : RP> (Self) -> () -> Int32 : @$s6simple2RCCAA2RPA2aDP8getThress5Int32VyFTW
141+
}

test/SILOptimizer/existential_transform.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,78 @@ struct MyStruct : Foo {
394394
return getName(u) == "MyStruct" ? 0 : 1
395395
}
396396

397+
protocol RP {
398+
var val:Int32 {get set}
399+
}
400+
class RC: RP {
401+
var val:Int32
402+
init(val:Int32) { self.val = val }
403+
}
404+
405+
// Note: The checks below must ensure that the function signature "@inline(never) func find(base:Int32, mult:Int32, Obj1: RP) -> Bool" has been turned into a protocol-constrained generic function via existential specialization, i.e., "function_ref @$s21existential_transform4find4base4mult4Obj1Sbs5Int32V_AgA2RP_ptFTf4nne_n : $@convention(thin) <τ_0_0 where τ_0_0 : RP> (Int32, Int32, @in_guaranteed τ_0_0) -> Bool". Same is true for the recursive function call for "return find (base: base, mult: mult+1, Obj1: Obj1)". Please refer to existential_specializer_soletype.sil test for SIL level testing. This test makes sure that nothing else breaks when we run end-to-end.
406+
// CHECK-LABEL: sil shared [noinline] @$s21existential_transform4find4base4mult4Obj1Sbs5Int32V_AgA2RP_ptFTf4nne_n : $@convention(thin) <τ_0_0 where τ_0_0 : RP> (Int32, Int32, @in_guaranteed τ_0_0) -> Bool {
407+
// CHECK: bb0
408+
// CHECK: alloc_stack $RP
409+
// CHECK: init_existential_addr
410+
// CHECK: copy_addr
411+
// CHECK: debug_value
412+
// CHECK: debug_value
413+
// CHECK: debug_value_addr
414+
// CHECK: struct_extract
415+
// CHECK: struct_extract
416+
// CHECK: integer_literal
417+
// CHECK: builtin
418+
// CHECK: tuple_extract
419+
// CHECK: tuple_extract
420+
// CHECK: cond_fail
421+
// CHECK: open_existential_addr
422+
// CHECK: witness_method
423+
// CHECK: apply
424+
// CHECK: struct_extract
425+
// CHECK: builtin
426+
// CHECK: cond_br
427+
// CHECK: bb1:
428+
// CHECK: integer_literal
429+
// CHECK: struct
430+
// CHECK: br
431+
// CHECK: bb2:
432+
// CHECK: open_existential_addr
433+
// CHECK: witness_method
434+
// CHECK: apply
435+
// CHECK: struct_extract
436+
// CHECK: builtin
437+
// CHECK: cond_br
438+
// CHECK: bb3
439+
// CHECK: dealloc_stack
440+
// CHECK: return
441+
// CHECK: bb4:
442+
// CHECK: struct
443+
// CHECK: br
444+
// CHECK: bb5:
445+
// CHECK: integer_literal
446+
// CHECK: builtin
447+
// CHECK: tuple_extract
448+
// CHECK: tuple_extract
449+
// CHECK: cond_fail
450+
// CHECK: struct
451+
// CHECK: function_ref @$s21existential_transform4find4base4mult4Obj1Sbs5Int32V_AgA2RP_ptFTf4nne_n : $@convention(thin) <τ_0_0 where τ_0_0 : RP> (Int32, Int32, @in_guaranteed τ_0_0) -> Bool
452+
// CHECK: open_existential_addr
453+
// CHECK: apply
454+
// CHECK: br
455+
// CHECK-LABEL: } // end sil function '$s21existential_transform4find4base4mult4Obj1Sbs5Int32V_AgA2RP_ptFTf4nne_n'
456+
@inline(never) func find(base:Int32, mult:Int32, Obj1: RP) -> Bool {
457+
if base * mult > Obj1.val {
458+
return false
459+
} else if base * mult == Obj1.val {
460+
return true
461+
} else {
462+
return find (base: base, mult: mult+1, Obj1: Obj1)
463+
}
464+
}
465+
@inline(never) func find_wrapper() -> Bool {
466+
let ab = RC(val: 100)
467+
return find(base: 3, mult: 1, Obj1: ab)
468+
}
397469
@_optimize(none) public func foo() -> Int {
398470
cp()
399471
ncp()
@@ -408,5 +480,6 @@ let y:Int = gcp(GC())
408480
var a:Array<GC> = [GC()]
409481
let z:Int = gcp_arch(GC(), &a)
410482
let zz:Int32 = getName_wrapper()
483+
let _ = find_wrapper()
411484
return x + y + z + Int(zz)
412485
}

0 commit comments

Comments
 (0)