Skip to content

Fix ExistentialSpecializer: Recursive function specialization #22247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,14 @@ void ExistentialSpecializer::specializeExistentialArgsInAppliesWithinFunction(

auto *Callee = Apply.getReferencedFunction();

/// Handle recursion! Do not modify F right now.
if (Callee == &F) {
LLVM_DEBUG(llvm::dbgs() << "ExistentialSpecializer Pass: Bail! Due to "
"recursion.\n";
I->dump(););
continue;
}

/// Determine the arguments that can be specialized.
llvm::SmallDenseMap<int, ExistentialTransformArgumentDescriptor>
ExistentialArgDescriptor;
Expand Down
16 changes: 12 additions & 4 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,10 +689,18 @@ SILCombiner::buildConcreteOpenedExistentialInfoFromSoleConformingType(
} else {
auto ArgType = ArgOperand.get()->getType();
auto SwiftArgType = ArgType.getASTType();
if (!ArgType.isExistentialType() || ArgType.isAnyObject() ||
SwiftArgType->isAny())
return None;
PD = dyn_cast<ProtocolDecl>(SwiftArgType->getAnyNominal());
/// If the argtype is an opened existential conforming to a protocol type
/// and that the protocol type has a sole conformance, then we can propagate
/// concrete type for it as well.
ArchetypeType *archetypeTy;
if (SwiftArgType->isOpenedExistential() &&
(archetypeTy = dyn_cast<ArchetypeType>(SwiftArgType)) &&
(archetypeTy->getConformsTo().size() == 1)) {
PD = archetypeTy->getConformsTo()[0];
} else if (ArgType.isExistentialType() && !ArgType.isAnyObject() &&
!SwiftArgType->isAny()) {
PD = dyn_cast<ProtocolDecl>(SwiftArgType->getAnyNominal());
}
}

if (!PD)
Expand Down
141 changes: 141 additions & 0 deletions test/SILOptimizer/existential_specializer_soletype.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// RUN: %target-sil-opt -wmo -enable-sil-verify-all %s -enable-sil-existential-specializer -existential-specializer -inline -sil-combine -generic-specializer -devirtualizer

// This file tests existential specializer transformation followed by concrete type propagation and generic specialization leading to a devirtualization of a witness method call.

sil_stage canonical

import Builtin
import Swift
import SwiftShims

protocol RP {
func getThres() -> Int32
}

class RC : RP {
@inline(never) func getThres() -> Int32
}

sil hidden [noinline] @$s6simple2RCC8getThress5Int32VyF : $@convention(method) (@guaranteed RC) -> Int32 {
bb0(%0 : $RC):
%1 = integer_literal $Builtin.Int32, 10
%2 = struct $Int32 (%1 : $Builtin.Int32)
return %2 : $Int32
}

// 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.
// CHECK-LABEL: sil hidden [noinline] @$s6simple12find_wrapperSbyF : $@convention(thin) () -> Bool {
// CHECK: bb0:
// CHECK: alloc_ref
// CHECK: integer_literal
// CHECK: struct
// CHECK: alloc_stack
// CHECK: store
// CHECK: strong_retain
// CHECK: function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool
// CHECK: load
// CHECK: apply
// CHECK: destroy_addr
// CHECK: dealloc_stack
// CHECK: strong_release
// CHECK: return
// CHECK-LABEL: }
sil hidden [noinline] @$s6simple12find_wrapperSbyF : $@convention(thin) () -> Bool {
bb0:
%0 = alloc_ref $RC
%3 = integer_literal $Builtin.Int32, 0
%4 = struct $Int32 (%3 : $Builtin.Int32)
%5 = alloc_stack $RP
%6 = init_existential_addr %5 : $*RP, $RC
store %0 to %6 : $*RC
%8 = function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
strong_retain %0 : $RC
%10 = apply %8(%4, %5) : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
destroy_addr %5 : $*RP
dealloc_stack %5 : $*RP
strong_release %0 : $RC
return %10 : $Bool
}

sil private [transparent] [thunk] @$s6simple2RCCAA2RPA2aDP8getThress5Int32VyFTW : $@convention(witness_method: RP) (@in_guaranteed RC) -> Int32 {
bb0(%0 : $*RC):
%1 = load %0 : $*RC
%2 = class_method %1 : $RC, #RC.getThres!1 : (RC) -> () -> Int32, $@convention(method) (@guaranteed RC) -> Int32
%3 = apply %2(%1) : $@convention(method) (@guaranteed RC) -> Int32
return %3 : $Int32
}


// 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.
// 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.
// CHECK-LABEL: sil shared [noinline] @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool {
// CHECK: bb0
// CHECK: alloc_stack
// CHECK: store
// CHECK: alloc_stack
// CHECK: init_existential_addr
// CHECK: copy_addr
// CHECK: open_existential_addr
// CHECK: unchecked_addr_cast
// CHECK: load
// CHECK: function_ref @$s6simple2RCC8getThress5Int32VyF : $@convention(method) (@guaranteed RC) -> Int32
// CHECK: apply
// CHECK: struct_extract
// CHECK: struct_extract
// CHECK: builtin
// CHECK: cond_br
// CHECK: bb1:
// CHECK: integer_literal
// CHECK: integer_literal
// CHECK: builtin
// CHECK: tuple_extract
// CHECK: struct
// CHECK: function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptFTf4ne_n4main2RCC_Tg5 : $@convention(thin) (Int32, @guaranteed RC) -> Bool
// CHECK: load
// CHECK: apply
// CHECK: br
// CHECK: bb2:
// CHECK: integer_literal
// CHECK: struct
// CHECK: br
// CHECK: bb3
// CHECK: dealloc_stack
// CHECK: dealloc_stack
// CHECK: return
// CHECK-LABEL: }
sil hidden [noinline] @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool {
bb0(%0 : $Int32, %1 : $*RP):
%4 = open_existential_addr immutable_access %1 : $*RP to $*@opened("7AE0C0CA-28D3-11E9-B1D4-F21898973DF0") RP
%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
%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
%7 = struct_extract %0 : $Int32, #Int32._value
%8 = struct_extract %6 : $Int32, #Int32._value
%9 = builtin "cmp_slt_Int32"(%7 : $Builtin.Int32, %8 : $Builtin.Int32) : $Builtin.Int1
cond_br %9, bb2, bb1

bb1:
%11 = integer_literal $Builtin.Int1, -1
%12 = struct $Bool (%11 : $Builtin.Int1)
br bb3(%12 : $Bool)

bb2:
%14 = integer_literal $Builtin.Int32, 1
%15 = integer_literal $Builtin.Int1, -1
%16 = builtin "sadd_with_overflow_Int32"(%7 : $Builtin.Int32, %14 : $Builtin.Int32, %15 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1)
%17 = tuple_extract %16 : $(Builtin.Int32, Builtin.Int1), 0
%18 = struct $Int32 (%17 : $Builtin.Int32)
%19 = function_ref @$s6simple4find5count4Obj1Sbs5Int32V_AA2RP_ptF : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
%20 = apply %19(%18, %1) : $@convention(thin) (Int32, @in_guaranteed RP) -> Bool
br bb3(%20 : $Bool)

bb3(%22 : $Bool):
return %22 : $Bool
}

sil_vtable RC {
#RC.getThres!1: (RC) -> () -> Int32 : @$s6simple2RCC8getThress5Int32VyF
}

sil_witness_table hidden RC: RP module simple {
method #RP.getThres!1: <Self where Self : RP> (Self) -> () -> Int32 : @$s6simple2RCCAA2RPA2aDP8getThress5Int32VyFTW
}
73 changes: 73 additions & 0 deletions test/SILOptimizer/existential_transform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,78 @@ struct MyStruct : Foo {
return getName(u) == "MyStruct" ? 0 : 1
}

protocol RP {
var val:Int32 {get set}
}
class RC: RP {
var val:Int32
init(val:Int32) { self.val = val }
}

// 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.
// 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 {
// CHECK: bb0
// CHECK: alloc_stack $RP
// CHECK: init_existential_addr
// CHECK: copy_addr
// CHECK: debug_value
// CHECK: debug_value
// CHECK: debug_value_addr
// CHECK: struct_extract
// CHECK: struct_extract
// CHECK: integer_literal
// CHECK: builtin
// CHECK: tuple_extract
// CHECK: tuple_extract
// CHECK: cond_fail
// CHECK: open_existential_addr
// CHECK: witness_method
// CHECK: apply
// CHECK: struct_extract
// CHECK: builtin
// CHECK: cond_br
// CHECK: bb1:
// CHECK: integer_literal
// CHECK: struct
// CHECK: br
// CHECK: bb2:
// CHECK: open_existential_addr
// CHECK: witness_method
// CHECK: apply
// CHECK: struct_extract
// CHECK: builtin
// CHECK: cond_br
// CHECK: bb3
// CHECK: dealloc_stack
// CHECK: return
// CHECK: bb4:
// CHECK: struct
// CHECK: br
// CHECK: bb5:
// CHECK: integer_literal
// CHECK: builtin
// CHECK: tuple_extract
// CHECK: tuple_extract
// CHECK: cond_fail
// CHECK: struct
// CHECK: function_ref @$s21existential_transform4find4base4mult4Obj1Sbs5Int32V_AgA2RP_ptFTf4nne_n : $@convention(thin) <τ_0_0 where τ_0_0 : RP> (Int32, Int32, @in_guaranteed τ_0_0) -> Bool
// CHECK: open_existential_addr
// CHECK: apply
// CHECK: br
// CHECK-LABEL: } // end sil function '$s21existential_transform4find4base4mult4Obj1Sbs5Int32V_AgA2RP_ptFTf4nne_n'
@inline(never) func find(base:Int32, mult:Int32, Obj1: RP) -> Bool {
if base * mult > Obj1.val {
return false
} else if base * mult == Obj1.val {
return true
} else {
return find (base: base, mult: mult+1, Obj1: Obj1)
}
}
@inline(never) func find_wrapper() -> Bool {
let ab = RC(val: 100)
return find(base: 3, mult: 1, Obj1: ab)
}
@_optimize(none) public func foo() -> Int {
cp()
ncp()
Expand All @@ -408,5 +480,6 @@ let y:Int = gcp(GC())
var a:Array<GC> = [GC()]
let z:Int = gcp_arch(GC(), &a)
let zz:Int32 = getName_wrapper()
let _ = find_wrapper()
return x + y + z + Int(zz)
}