From 5cc14abb82f5091ccb156350e5dd61a504a6f322 Mon Sep 17 00:00:00 2001 From: Roman Levenstein Date: Tue, 1 Dec 2015 14:49:29 -0800 Subject: [PATCH] [sil-devirtualizer] Improve devirtualization of witness_method instructions. Handle such cases like partial applications of witness methods and applications of witness methods with substitutions. Some of these uses-cases occur when there is a protocol defining an operator, a generic struct conforming to this protocol, and the operator conformance of this struct is expressed as a global function. --- include/swift/SIL/SILInstruction.h | 12 +++ include/swift/SILPasses/Utils/Devirtualize.h | 3 +- lib/IRGen/GenFunc.cpp | 4 +- lib/IRGen/IRGenSIL.cpp | 1 - lib/SILPasses/EarlySIL/MandatoryInlining.cpp | 4 +- lib/SILPasses/IPO/PerformanceInliner.cpp | 2 +- lib/SILPasses/SILCombiner/SILCombiner.h | 2 +- .../SILCombiner/SILCombinerMiscVisitors.cpp | 31 +++++++ lib/SILPasses/Utils/Devirtualize.cpp | 25 +++-- .../devirt_static_witness_method.sil | 93 +++++++++++++++++++ 10 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 test/SILPasses/devirt_static_witness_method.sil diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 265e8a5b17bf4..9b7d14ccc0b6a 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -652,6 +652,16 @@ class ApplyInstBase : public Base { return {getSubstitutionsStorage(), NumSubstitutions}; } + ArrayRef getSubstitutionsWithoutSelfSubstitution() const { + assert(getNumArguments() && "Should only be called when Callee has " + "at least a self parameter."); + assert(hasSubstitutions() && "Should only be called when Callee has " + "substitutions."); + if (getSubstCalleeType()->hasSelfParam()) + return getSubstitutions().slice(1); + return getSubstitutions(); + } + /// The arguments passed to this instruction. MutableArrayRef getArgumentOperands() { return Operands.getDynamicAsArray(); @@ -4266,6 +4276,8 @@ class ApplySite { return cast(Inst)->getSubstitutionsWithoutSelfSubstitution(); case ValueKind::TryApplyInst: return cast(Inst)->getSubstitutionsWithoutSelfSubstitution(); + case ValueKind::PartialApplyInst: + return cast(Inst)->getSubstitutionsWithoutSelfSubstitution(); default: llvm_unreachable("not implemented for this instruction!"); } diff --git a/include/swift/SILPasses/Utils/Devirtualize.h b/include/swift/SILPasses/Utils/Devirtualize.h index 5a09000a2e58f..a9aa941ca03e2 100644 --- a/include/swift/SILPasses/Utils/Devirtualize.h +++ b/include/swift/SILPasses/Utils/Devirtualize.h @@ -40,7 +40,7 @@ namespace swift { /// Two elements are required, because a result of the new devirtualized /// apply/try_apply instruction (second element) eventually needs to be /// casted to produce a properly typed value (first element). -typedef std::pair DevirtualizationResult; +typedef std::pair DevirtualizationResult; DevirtualizationResult tryDevirtualizeApply(FullApplySite AI); bool isClassWithUnboundGenericParameters(SILType C, SILModule &M); @@ -49,6 +49,7 @@ DevirtualizationResult devirtualizeClassMethod(FullApplySite AI, SILValue ClassInstance); DevirtualizationResult tryDevirtualizeClassMethod(FullApplySite AI, SILValue ClassInstance); +DevirtualizationResult tryDevirtualizeWitnessMethod(ApplySite AI); } #endif diff --git a/lib/IRGen/GenFunc.cpp b/lib/IRGen/GenFunc.cpp index 2859fabc84e24..0086becc3f11c 100644 --- a/lib/IRGen/GenFunc.cpp +++ b/lib/IRGen/GenFunc.cpp @@ -3591,7 +3591,9 @@ static llvm::Function *emitPartialApplicationForwarder(IRGenModule &IGM, } // Emit the polymorphic arguments. - assert(subs.empty() != hasPolymorphicParameters(origType) + assert((subs.empty() != hasPolymorphicParameters(origType) || + (subs.empty() && origType->getRepresentation() == + SILFunctionTypeRepresentation::WitnessMethod)) && "should have substitutions iff original function is generic"); WitnessMetadata witnessMetadata; if (hasPolymorphicParameters(origType)) { diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index a21c6605b336c..4d0ff6d025897 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -2104,7 +2104,6 @@ getPartialApplicationFunction(IRGenSILFunction &IGF, break; case SILFunctionTypeRepresentation::WitnessMethod: - assert(false && "partial_apply of witness functions not implemented"); break; case SILFunctionTypeRepresentation::Thick: diff --git a/lib/SILPasses/EarlySIL/MandatoryInlining.cpp b/lib/SILPasses/EarlySIL/MandatoryInlining.cpp index 9dda5c0e2cca4..afac382c9e0b4 100644 --- a/lib/SILPasses/EarlySIL/MandatoryInlining.cpp +++ b/lib/SILPasses/EarlySIL/MandatoryInlining.cpp @@ -320,14 +320,14 @@ runOnFunctionRecursively(SILFunction *F, FullApplySite AI, I = II->getIterator(); else I = NewInst->getParentBB()->begin(); - auto NewAI = NewInstPair.second; + auto NewAI = FullApplySite::isa(NewInstPair.second.getInstruction()); if (!NewAI) continue; InnerAI = NewAI; } - SILLocation Loc = InnerAI.getLoc(); + SILLocation Loc = InnerAI.getLoc(); SILValue CalleeValue = InnerAI.getCallee(); bool IsThick; PartialApplyInst *PAI; diff --git a/lib/SILPasses/IPO/PerformanceInliner.cpp b/lib/SILPasses/IPO/PerformanceInliner.cpp index d5e79228d0b20..d66ca1eaaafc8 100644 --- a/lib/SILPasses/IPO/PerformanceInliner.cpp +++ b/lib/SILPasses/IPO/PerformanceInliner.cpp @@ -826,7 +826,7 @@ FullApplySite SILPerformanceInliner::devirtualizeUpdatingCallGraph( if (!NewInstPair.second) return FullApplySite(); - auto NewAI = NewInstPair.second; + auto NewAI = FullApplySite::isa(NewInstPair.second.getInstruction()); CallGraphEditor(&CG).replaceApplyWithNew(Apply, NewAI); replaceDeadApply(Apply, NewInstPair.first); diff --git a/lib/SILPasses/SILCombiner/SILCombiner.h b/lib/SILPasses/SILCombiner/SILCombiner.h index c60df0a6043b9..91b2cabd34d16 100644 --- a/lib/SILPasses/SILCombiner/SILCombiner.h +++ b/lib/SILPasses/SILCombiner/SILCombiner.h @@ -243,7 +243,7 @@ class SILCombiner : SILInstruction *visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI); SILInstruction *visitEnumInst(EnumInst *EI); SILInstruction *visitConvertFunctionInst(ConvertFunctionInst *CFI); - + SILInstruction *visitWitnessMethodInst(WitnessMethodInst *WMI); /// Instruction visitor helpers. SILInstruction *optimizeBuiltinCanBeObjCClass(BuiltinInst *AI); diff --git a/lib/SILPasses/SILCombiner/SILCombinerMiscVisitors.cpp b/lib/SILPasses/SILCombiner/SILCombinerMiscVisitors.cpp index 5242da179d47f..d902b0b6c8720 100644 --- a/lib/SILPasses/SILCombiner/SILCombinerMiscVisitors.cpp +++ b/lib/SILPasses/SILCombiner/SILCombinerMiscVisitors.cpp @@ -23,6 +23,7 @@ #include "swift/SILAnalysis/CFG.h" #include "swift/SILAnalysis/ValueTracking.h" #include "swift/SILPasses/Utils/Local.h" +#include "swift/SILPasses/Utils/Devirtualize.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/DenseMap.h" @@ -1070,3 +1071,33 @@ visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI) { SILInstruction *SILCombiner::visitEnumInst(EnumInst *EI) { return nullptr; } + +SILInstruction *SILCombiner::visitWitnessMethodInst(WitnessMethodInst *WMI) { + // Try to devirtualize a witness_method if it is statically possible. + // Many cases are handled by the inliner/devirtualizer, but certain + // special cases are not covered there, e.g. partial_apply(witness_method) + SILFunction *F; + ArrayRef Subs; + SILWitnessTable *WT; + + std::tie(F, WT, Subs) = + WMI->getModule().lookUpFunctionInWitnessTable(WMI->getConformance(), + WMI->getMember()); + + if (!F) + return nullptr; + + for (auto U = WMI->use_begin(), E = WMI->use_end(); U != E;) { + auto User = U->getUser(); + ++U; + if (auto AI = ApplySite::isa(User)) { + auto Result = tryDevirtualizeWitnessMethod(AI); + if (Result.first) { + User->replaceAllUsesWith(Result.first); + eraseInstFromFunction(*User); + } + } + } + return nullptr; +} + diff --git a/lib/SILPasses/Utils/Devirtualize.cpp b/lib/SILPasses/Utils/Devirtualize.cpp index f668a856bf721..fb3bc6e2c46e3 100644 --- a/lib/SILPasses/Utils/Devirtualize.cpp +++ b/lib/SILPasses/Utils/Devirtualize.cpp @@ -503,7 +503,7 @@ DevirtualizationResult swift::tryDevirtualizeClassMethod(FullApplySite AI, /// Generate a new apply of a function_ref to replace an apply of a /// witness_method when we've determined the actual function we'll end /// up calling. -static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F, +static ApplySite devirtualizeWitnessMethod(ApplySite AI, SILFunction *F, ArrayRef Subs) { // We know the witness thunk and the corresponding set of substitutions // required to invoke the protocol method at this point. @@ -517,7 +517,10 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F, SmallVector NewSubstList(Subs.begin(), Subs.end()); // Add the non-self-derived substitutions from the original application. - for (auto &origSub : AI.getSubstitutionsWithoutSelfSubstitution()) + ArrayRef SubstList; + SubstList = AI.getSubstitutionsWithoutSelfSubstitution(); + + for (auto &origSub : SubstList) if (!origSub.getArchetype()->isSelfDerived()) NewSubstList.push_back(origSub); @@ -531,18 +534,17 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F, auto Arguments = SmallVector(); auto ParamTypes = SubstCalleeCanType->getParameterSILTypes(); - // Type of the current parameter being processed - auto ParamType = ParamTypes.begin(); // Iterate over the non self arguments and add them to the // new argument list, upcasting when required. SILBuilderWithScope B(AI.getInstruction()); - for (SILValue A : AI.getArguments()) { - if (A.getType() != *ParamType) - A = B.createUpcast(AI.getLoc(), A, *ParamType); + for (unsigned ArgN = 0, ArgE = AI.getNumArguments(); ArgN != ArgE; ++ArgN) { + SILValue A = AI.getArgument(ArgN); + auto ParamType = ParamTypes[ParamTypes.size() - AI.getNumArguments() + ArgN]; + if (A.getType() != ParamType) + A = B.createUpcast(AI.getLoc(), A, ParamType); Arguments.push_back(A); - ++ParamType; } // Replace old apply instruction by a new apply instruction that invokes @@ -553,7 +555,7 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F, auto SubstCalleeSILType = SILType::getPrimitiveObjectType(SubstCalleeCanType); auto ResultSILType = SubstCalleeCanType->getSILResult(); - FullApplySite SAI; + ApplySite SAI; if (auto *A = dyn_cast(AI)) SAI = Builder.createApply(Loc, FRI, SubstCalleeSILType, @@ -563,6 +565,9 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F, SAI = Builder.createTryApply(Loc, FRI, SubstCalleeSILType, NewSubstList, Arguments, TAI->getNormalBB(), TAI->getErrorBB()); + if (auto *PAI = dyn_cast(AI)) + SAI = Builder.createPartialApply(Loc, FRI, SubstCalleeSILType, + NewSubstList, Arguments, PAI->getType()); NumWitnessDevirt++; return SAI; @@ -571,7 +576,7 @@ static FullApplySite devirtualizeWitnessMethod(FullApplySite AI, SILFunction *F, /// In the cases where we can statically determine the function that /// we'll call to, replace an apply of a witness_method with an apply /// of a function_ref, returning the new apply. -static DevirtualizationResult tryDevirtualizeWitnessMethod(FullApplySite AI) { +DevirtualizationResult swift::tryDevirtualizeWitnessMethod(ApplySite AI) { SILFunction *F; ArrayRef Subs; SILWitnessTable *WT; diff --git a/test/SILPasses/devirt_static_witness_method.sil b/test/SILPasses/devirt_static_witness_method.sil new file mode 100644 index 0000000000000..81b9bd9514fab --- /dev/null +++ b/test/SILPasses/devirt_static_witness_method.sil @@ -0,0 +1,93 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -sil-combine | FileCheck %s +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +protocol CanAdd { + func +(lhs: Self, rhs: Self) -> Self +} + +extension Int64 : CanAdd { +} + +struct S : CanAdd { +} + +func +(lhs: S, rhs: S) -> S + + +sil hidden [transparent] [thunk] @operator_plus_static_non_generic_witness_for_S : $@convention(thin) (@out S, @in S, @in S, @thick S.Type) -> () { +bb0(%0 : $*S, %1 : $*S, %2 : $*S, %3 : $@thick S.Type) : + %17 = tuple () + return %17 : $() +} + +sil hidden [transparent] [thunk] @operator_plus_static_non_generic_witness : $@convention(witness_method) (@out Int64, @in Int64, @in Int64, @thick Int64.Type) -> () { +bb0(%0 : $*Int64, %1 : $*Int64, %2 : $*Int64, %3 : $@thick Int64.Type): + %4 = struct_element_addr %1 : $*Int64, #Int64._value + %5 = load %4 : $*Builtin.Int64 + %6 = struct_element_addr %2 : $*Int64, #Int64._value + %7 = load %6 : $*Builtin.Int64 + %8 = integer_literal $Builtin.Int1, -1 + %9 = builtin "sadd_with_overflow_Int64"(%5 : $Builtin.Int64, %7 : $Builtin.Int64, %8 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %10 = tuple_extract %9 : $(Builtin.Int64, Builtin.Int1), 0 + %11 = tuple_extract %9 : $(Builtin.Int64, Builtin.Int1), 1 + cond_fail %11 : $Builtin.Int1 + %15 = struct $Int64 (%10 : $Builtin.Int64) + store %15 to %0 : $*Int64 + %17 = tuple () + return %17 : $() +} + +// Test that this partial application of a function reference referring to static non generic witness does not crash the IRGen. +// Such code may be produced e.g. when users refer to global operators defined on builtin types. +// CHECK-LABEL: sil hidden @test_partial_apply_of_static_witness +sil hidden @test_partial_apply_of_static_witness : $@convention(thin) () -> @callee_owned (@out Int64, @in Int64, @in Int64) -> () { +bb0: + %1 = metatype $@thick Int64.Type + %2 = function_ref @operator_plus_static_non_generic_witness : $@convention(witness_method) (@out Int64, @in Int64, @in Int64, @thick Int64.Type) -> () + %3 = partial_apply %2(%1) : $@convention(witness_method) (@out Int64, @in Int64, @in Int64, @thick Int64.Type) -> () + return %3 : $@callee_owned (@out Int64, @in Int64, @in Int64) -> () +} + +// Test that this partial application of witness_method can be devirtualized. +// CHECK-LABEL: sil hidden @test_devirt_of_partial_apply_of_witness_method +// CHECK-NOT: witness_method $S, #CanAdd."+"!1 +// CHECK: function_ref @operator_plus_static_non_generic_witness_for_S +// CHECK: partial_apply +// CHECK: return +sil hidden @test_devirt_of_partial_apply_of_witness_method : $@convention(thin) () -> @callee_owned (@out S, @in S, @in S) -> () { +bb0: + %1 = metatype $@thick S.Type + %2 = witness_method $S, #CanAdd."+"!1 : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> () + %3 = partial_apply %2>(%1) : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> () + return %3 : $@callee_owned (@out S, @in S, @in S) -> () +} + +// Test that this application of witness_method can be devirtualized. +// CHECK-LABEL: sil hidden @test_devirt_of_apply_of_witness_method +// CHECK-NOT: witness_method $S, #CanAdd."+"!1 +// CHECK: function_ref @operator_plus_static_non_generic_witness_for_S +// CHECK: apply +// CHECK: return +sil hidden @test_devirt_of_apply_of_witness_method : $@convention(thin) (@in S) -> S { +bb0(%0 : $*S): + %1 = alloc_stack $S + %5 = metatype $@thick S.Type + %6 = witness_method $S, #CanAdd."+"!1 : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> () + %7 = apply %6>(%1#1, %0, %0, %5) : $@convention(witness_method) <τ_0_0 where τ_0_0 : CanAdd> (@out τ_0_0, @in τ_0_0, @in τ_0_0, @thick τ_0_0.Type) -> () + %8 = load %1#1: $*S + dealloc_stack %1#0 : $*@local_storage S + return %8 : $S +} + +sil_witness_table hidden Int64: CanAdd module Test { + method #CanAdd."+"!1: @operator_plus_static_non_generic_witness +} + +sil_witness_table hidden S: CanAdd module Test { + method #CanAdd."+"!1: @operator_plus_static_non_generic_witness_for_S +} +