Skip to content

Enable silcombine to propagate concrete type of existentials in ossa #78354

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 2 commits into from
Jan 2, 2025
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
3 changes: 3 additions & 0 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,9 @@ SILPassPipelinePlan::getPerformancePassPipeline(const SILOptions &Options) {
// importing this module.
P.addSerializeSILPass();

if (P.getOptions().EnableOSSAModules && SILPrintFinalOSSAModule) {
addModulePrinterPipeline(P, "SIL Print Final OSSA Module");
}
// Strip any transparent functions that still have ownership.
P.addOwnershipModelEliminator();

Expand Down
7 changes: 0 additions & 7 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1320,10 +1320,6 @@ SILInstruction *SILCombiner::createApplyWithConcreteType(
SILInstruction *
SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply,
WitnessMethodInst *WMI) {
// We do not perform this optimization in OSSA. In OSSA, we will have opaque
// values we will redo this.
if (WMI->getFunction()->hasOwnership())
return nullptr;

// Check if it is legal to perform the propagation.
if (WMI->getConformance().isConcrete())
Expand Down Expand Up @@ -1403,9 +1399,6 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply,
/// ==> apply %f<C : P>(%ref)
SILInstruction *
SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite Apply) {
if (Apply.getFunction()->hasOwnership())
return nullptr;

// This optimization requires a generic argument.
if (!Apply.hasSubstitutions())
return nullptr;
Expand Down
5 changes: 0 additions & 5 deletions lib/SILOptimizer/Utils/Existential.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr,
if (SingleWrite)
return nullptr;
SingleWrite = store;
// When we support OSSA here, we need to insert a new copy of the value
// before `store` (and make sure that the copy is destroyed when
// replacing the apply operand).
assert(store->getOwnershipQualifier() ==
StoreOwnershipQualifier::Unqualified);
}
continue;
}
Expand Down
1 change: 1 addition & 0 deletions test/SILOptimizer/sil_combine1.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %target-swift-frontend %s -O -Xllvm -sil-print-types -emit-sil | %FileCheck %s
// RUN: %target-swift-frontend %s -O -Xllvm -sil-print-types -enable-ossa-modules -emit-sil | %FileCheck %s

func curry<T1, T2, T3, T4>(_ f: @escaping (T1, T2, T3) -> T4) -> (T1) -> (T2) -> (T3) -> T4 {
return { x in { y in { z in f(x, y, z) } } }
Expand Down
140 changes: 140 additions & 0 deletions test/SILOptimizer/sil_combine_concrete_existential_ossa.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// RUN: %target-swift-frontend -O -enable-ossa-modules -Xllvm -sil-print-types -emit-sil -sil-verify-all -Xllvm -sil-disable-pass=function-signature-opts %s | %FileCheck %s
// RUN: %target-swift-frontend -O -enable-ossa-modules -Xllvm -sil-print-types -emit-sil -Xllvm -sil-verify-force-analysis-around-pass=devirtualizer -Xllvm -sil-disable-pass=function-signature-opts %s | %FileCheck %s

// REQUIRES: swift_in_compiler

//===----------------------------------------------------------------------===//
// testReturnSelf: Call to a protocol extension method with
// an existential self that can be type-propagated.
// sil-combine should bailout since it does not propagate
// type substitutions on the return value.
//
// rdar://40555427
// https://github.com/apple/swift/issues/50312
// 'SILCombiner::propagateConcreteTypeOfInitExistential' fails to fully
// propagate type substitutions.
//===----------------------------------------------------------------------===//
public protocol P: class {}

extension P {
public func returnSelf() -> Self {
return self
}
}

final class C: P {}
// CHECK-LABEL: sil @$s37sil_combine_concrete_existential_ossa14testReturnSelfAA1P_pyF : $@convention(thin) () -> @owned any P {
// CHECK: [[EI:%.*]] = end_init_let_ref %0
// CHECK: [[E1:%.*]] = init_existential_ref [[EI]] : $C : $C, $any P
// CHECK: [[O1:%.*]] = open_existential_ref [[E1]] : $any P to $@opened("{{.*}}", any P) Self
// CHECK: [[F1:%.*]] = function_ref @$s37sil_combine_concrete_existential_ossa1PPAAE10returnSelfxyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0
// CHECK: [[C1:%.*]] = apply [[F1]]<@opened("{{.*}}", any P) Self>([[O1]]) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0
// CHECK: [[E2:%.*]] = init_existential_ref [[C1]] : $@opened("{{.*}}", any P) Self : $@opened("{{.*}}", any P) Self, $any P
// CHECK: [[O2:%.*]] = open_existential_ref [[E2]] : $any P to $@opened("{{.*}}", any P) Self
// CHECK: apply [[F1]]<@opened("{{.*}}", any P) Self>([[O2]]) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@guaranteed τ_0_0) -> @owned τ_0_0
// CHECK-LABEL: } // end sil function '$s37sil_combine_concrete_existential_ossa14testReturnSelfAA1P_pyF'
public func testReturnSelf() -> P {
let p: P = C()
return p.returnSelf().returnSelf()
}

//===----------------------------------------------------------------------===//
// testWitnessReturnOptionalSelf: Call to a witness method with an existential
// self that can be type-propagated. sil-combine should bailout since it does
// not propagate type substitutions on the return value, and it must walk the
// Optional type to find Self in the return type.
//
// Although sil-combine will not replace the self operand, it will still
// rewrite the witness_method. The devirtualizer then handles the first call.
//===----------------------------------------------------------------------===//
public protocol PP: class {
func returnOptionalSelf() -> Self?
}

final class CC: PP {
init() {}
func returnOptionalSelf() -> Self? {
return self
}
}

// CHECK-LABEL: sil @$s37sil_combine_concrete_existential_ossa29testWitnessReturnOptionalSelfAA2PP_pSgyF : $@convention(thin) () -> @owned Optional<any PP> {
// CHECK: [[EI:%.*]] = end_init_let_ref %0
// CHECK: [[E1:%.*]] = init_existential_ref [[EI]] : $CC : $CC, $any PP
// CHECK: [[O1:%.*]] = open_existential_ref [[E1]] : $any PP to $@opened("{{.*}}", any PP) Self
// CHECK: [[E2:%.*]] = init_existential_ref %{{.*}} : $@opened("{{.*}}", any PP) Self : $@opened("{{.*}}", any PP) Self, $any PP
// CHECK: [[O2:%.*]] = open_existential_ref [[E2]] : $any PP to $@opened("{{.*}}", any PP) Self
// CHECK: [[U1:%.*]] = unchecked_ref_cast [[EI]] : $CC to $@opened("{{.*}}", any PP) Self
// CHECK: [[E3:%.*]] = init_existential_ref [[U1]] : $@opened("{{.*}}", any PP) Self : $@opened("{{.*}}", any PP) Self, $any PP
// CHECK: [[E:%.*]] = enum $Optional<any PP>, #Optional.some!enumelt, [[E3]] : $any PP
// CHECK: return [[E]] : $Optional<any PP>
// CHECK-LABEL: } // end sil function '$s37sil_combine_concrete_existential_ossa29testWitnessReturnOptionalSelfAA2PP_pSgyF'
public func testWitnessReturnOptionalSelf() -> PP? {
let p: PP = CC()
return p.returnOptionalSelf()?.returnOptionalSelf()
}

//===----------------------------------------------------------------------===//
// testWitnessReturnOptionalIndirectSelf: Call to a witness method with an
// existential self that can be type-propagated. sil-combine should bailout
// since it does not propagate type substitutions on non-self arguments. It must
// walk the Optional type to find Self in the non-self argument.
//
// Although sil-combine will not replace the self operand, it will still
// rewrite the witness_method. The devirtualizer then handles the first call.
//===----------------------------------------------------------------------===//
protocol PPP {
func returnsOptionalIndirect() -> Self?
}

struct S: PPP {
func returnsOptionalIndirect() -> S? {
return self
}
}

struct SS: PPP {
func returnsOptionalIndirect() -> SS? {
return self
}
}

// The first apply has been devirtualized and inlined. The second remains unspecialized.
// CHECK-LABEL: sil @$s37sil_combine_concrete_existential_ossa37testWitnessReturnOptionalIndirectSelfyyF : $@convention(thin) () -> () {
// CHECK: [[O1:%.*]] = open_existential_addr immutable_access %{{.*}} : $*any PPP to $*@opened("{{.*}}", any PPP) Self
// CHECK: switch_enum_addr %{{.*}} : $*Optional<@opened("{{.*}}", any PPP) Self>, case #Optional.some!enumelt: bb{{.*}}, case #Optional.none!enumelt: bb{{.*}}
// CHECK: [[O2:%.*]] = open_existential_addr immutable_access %{{.*}} : $*any PPP to $*@opened("{{.*}}", any PPP) Self
// CHECK: [[W:%.*]] = witness_method $@opened("{{.*}}", any PPP) Self, #PPP.returnsOptionalIndirect : <Self where Self : PPP> (Self) -> () -> Self?, [[O1]] : $*@opened("{{.*}}", any PPP) Self : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0>
// CHECK: apply [[W]]<@opened("{{.*}}", any PPP) Self>(%{{.*}}, [[O2]]) : $@convention(witness_method: PPP) <τ_0_0 where τ_0_0 : PPP> (@in_guaranteed τ_0_0) -> @out Optional<τ_0_0>
// CHECK-LABEL: } // end sil function '$s37sil_combine_concrete_existential_ossa37testWitnessReturnOptionalIndirectSelfyyF'
public func testWitnessReturnOptionalIndirectSelf() {
let p: PPP = S()
_ = p.returnsOptionalIndirect()?.returnsOptionalIndirect()
}

//===----------------------------------------------------------------------===//
// testExtensionProtocolComposition: Call to a witness method with an
// existential self that can be type-propagated. Handle an existential with
// multiple conformances.
//
// Previously crashed with in SILCombiner::propagateConcreteTypeOfInitExistential
// with assertion failed: (proto == Conformance.getRequirement()).
// ===----------------------------------------------------------------------===//
public protocol Q {}

extension P where Self : Q {
public func witnessComposition() {}
}

public class C_PQ: P & Q {}

// testExtensionProtocolComposition(c:)
// CHECK-LABEL: sil {{.*}}@$s37sil_combine_concrete_existential_ossa32testExtensionProtocolComposition1cyAA4C_PQC_tF : $@convention(thin) (@guaranteed C_PQ) -> () {
// CHECK-NOT: init_existential_ref
// CHECK-NOT: function_ref
// CHECK-NOT: apply
// CHECK: } // end sil function '$s37sil_combine_concrete_existential_ossa32testExtensionProtocolComposition1cyAA4C_PQC_tF'
public func testExtensionProtocolComposition(c: C_PQ) {
let pp: P & Q = c
pp.witnessComposition()
}
26 changes: 12 additions & 14 deletions test/SILOptimizer/sil_combine_ossa.sil
Original file line number Diff line number Diff line change
Expand Up @@ -2845,13 +2845,11 @@ bb0(%0 : @owned $Klass):
return %3 : $Builtin.NativeObject
}

// CHECK-LABEL: sil [ossa] @collapse_existential_pack_unpack_unchecked_ref_cast_owned_fail_3 :
// CHECK: bb0(
// CHECK: init_existential_ref
// CHECK: open_existential_ref
// CHECK: unchecked_ref_cast
// CHECK: } // end sil function 'collapse_existential_pack_unpack_unchecked_ref_cast_owned_fail_3'
sil [ossa] @collapse_existential_pack_unpack_unchecked_ref_cast_owned_fail_3 : $@convention(thin) (@owned Klass) -> @owned Builtin.NativeObject {
// CHECK-LABEL: sil [ossa] @collapse_existential_pack_unpack_unchecked_ref_cast_owned3 :
// CHECK-NOT: init_existential_ref
// CHECK-NOT: open_existential_ref
// CHECK: } // end sil function 'collapse_existential_pack_unpack_unchecked_ref_cast_owned3'
sil [ossa] @collapse_existential_pack_unpack_unchecked_ref_cast_owned3 : $@convention(thin) (@owned Klass) -> @owned Builtin.NativeObject {
bb0(%0 : @owned $Klass):
%1 = init_existential_ref %0 : $Klass : $Klass, $AnyObject
%2 = open_existential_ref %1 : $AnyObject to $@opened("7CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self
Expand Down Expand Up @@ -4022,13 +4020,13 @@ bb0(%0 : @owned $VV):
sil [ossa] @any_to_object : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> @owned AnyObject

// CHECK-LABEL: sil [ossa] @dont_crash_when_propagating_existentials
// XHECK: [[EM:%[0-9]+]] = init_existential_metatype %0
// XHECK: [[S:%[0-9]+]] = alloc_stack $Any
// XHECK: [[M:%[0-9]+]] = open_existential_metatype [[EM]]
// XHECK: [[E:%[0-9]+]] = init_existential_addr [[S]]
// XHECK: store [[M]] to [trivial] [[E]]
// XHECK: apply {{%[0-9]+}}<(@opened("5F99B72C-EC40-11EA-9534-8C8590A6A134", AnyObject) Self).Type>([[E]])
// XHECK: } // end sil function 'dont_crash_when_propagating_existentials'
// CHECK: [[EM:%[0-9]+]] = init_existential_metatype %0
// CHECK: [[S:%[0-9]+]] = alloc_stack $Any
// CHECK: [[M:%[0-9]+]] = open_existential_metatype [[EM]]
// CHECK: [[E:%[0-9]+]] = init_existential_addr [[S]]
// CHECK: store [[M]] to [trivial] [[E]]
// CHECK: apply {{%[0-9]+}}<(@opened("5F99B72C-EC40-11EA-9534-8C8590A6A134", AnyObject) Self).Type>([[E]])
// CHECK: } // end sil function 'dont_crash_when_propagating_existentials'
sil [ossa] @dont_crash_when_propagating_existentials : $@convention(thin) () -> @owned AnyObject {
bb0:
%0 = metatype $@thick C.Type
Expand Down