From 52c1903e54e286538eab2449cc34d7bfeca5841b Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Mon, 5 Nov 2018 08:15:47 -0800 Subject: [PATCH] Add SIL support for [dynamic_replacement_for: ] functions --- include/swift/AST/DiagnosticsParse.def | 2 + include/swift/SIL/SILFunction.h | 38 ++++++++++ include/swift/Serialization/ModuleFormat.h | 2 +- lib/ParseSIL/ParseSIL.cpp | 74 +++++++++++++++---- lib/SIL/SILFunction.cpp | 26 +++++++ lib/SIL/SILModule.cpp | 5 +- lib/SIL/SILPrinter.cpp | 12 +++ lib/SIL/SILVerifier.cpp | 3 + lib/Serialization/DeserializeSIL.cpp | 24 +++++- lib/Serialization/SILFormat.h | 1 + lib/Serialization/SerializeSIL.cpp | 11 ++- test/SIL/Parser/basic.sil | 9 ++- .../Serialization/dynamically_replaceable.sil | 4 +- 13 files changed, 188 insertions(+), 23 deletions(-) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 2e7c09db452..3332a6b454c 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -625,6 +625,8 @@ ERROR(expected_sil_rbrace,none, "expected '}' at the end of a sil body", ()) ERROR(expected_sil_function_type, none, "sil function expected to have SIL function type", ()) +ERROR(sil_dynamically_replaced_func_not_found,none, + "dynamically replaced function not found %0", (Identifier)) // SIL Stage ERROR(expected_sil_stage_name, none, diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index 5811e6cd10c..9157fb407a3 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -149,6 +149,12 @@ private: /// disabled. SILProfiler *Profiler = nullptr; + /// The function this function is meant to replace. Null if this is not a + /// @_dynamicReplacement(for:) function. + SILFunction *ReplacedFunction = nullptr; + + Identifier ObjCReplacementFor; + /// The function's bare attribute. Bare means that the function is SIL-only /// and does not require debug info. unsigned Bare : 1; @@ -313,6 +319,38 @@ public: SILProfiler *getProfiler() const { return Profiler; } + SILFunction *getDynamicallyReplacedFunction() const { + return ReplacedFunction; + } + void setDynamicallyReplacedFunction(SILFunction *f) { + assert(ReplacedFunction == nullptr && "already set"); + assert(!hasObjCReplacement()); + + if (f == nullptr) + return; + ReplacedFunction = f; + ReplacedFunction->incrementRefCount(); + } + + /// This function should only be called when SILFunctions are bulk deleted. + void dropDynamicallyReplacedFunction() { + if (!ReplacedFunction) + return; + ReplacedFunction->decrementRefCount(); + ReplacedFunction = nullptr; + } + + bool hasObjCReplacement() const { + return !ObjCReplacementFor.empty(); + } + + Identifier getObjCReplacement() const { + return ObjCReplacementFor; + } + + void setObjCReplacement(AbstractFunctionDecl *replacedDecl); + void setObjCReplacement(Identifier replacedDecl); + void setProfiler(SILProfiler *InheritedProfiler) { assert(!Profiler && "Function already has a profiler"); Profiler = InheritedProfiler; diff --git a/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index c78b05777c2..fc58640c048 100644 --- a/include/swift/Serialization/ModuleFormat.h +++ b/include/swift/Serialization/ModuleFormat.h @@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 460; // Last change: Add dynamic_function_ref +const uint16_t SWIFTMODULE_VERSION_MINOR = 461; // Last change: Add dynamic_replacement_for using DeclIDField = BCFixed<31>; diff --git a/lib/ParseSIL/ParseSIL.cpp b/lib/ParseSIL/ParseSIL.cpp index f9355cdd9c5..2f040a9541e 100644 --- a/lib/ParseSIL/ParseSIL.cpp +++ b/lib/ParseSIL/ParseSIL.cpp @@ -903,6 +903,8 @@ static bool parseDeclSILOptional(bool *isTransparent, bool *isCanonical, IsThunk_t *isThunk, IsDynamicallyReplaceable_t *isDynamic, + SILFunction **dynamicallyReplacedFunction, + Identifier *objCReplacementFor, bool *isGlobalInit, Inline_t *inlineStrategy, OptimizationMode *optimizationMode, @@ -911,7 +913,8 @@ static bool parseDeclSILOptional(bool *isTransparent, SmallVectorImpl *Semantics, SmallVectorImpl *SpecAttrs, ValueDecl **ClangDecl, - EffectsKind *MRK, SILParser &SP) { + EffectsKind *MRK, SILParser &SP, + SILModule &M) { while (SP.P.consumeIf(tok::l_square)) { if (isLet && SP.P.Tok.is(tok::kw_let)) { *isLet = true; @@ -963,7 +966,41 @@ static bool parseDeclSILOptional(bool *isTransparent, *MRK = EffectsKind::ReadWrite; else if (MRK && SP.P.Tok.getText() == "releasenone") *MRK = EffectsKind::ReleaseNone; - else if (Semantics && SP.P.Tok.getText() == "_semantics") { + else if (dynamicallyReplacedFunction && SP.P.Tok.getText() == "dynamic_replacement_for") { + SP.P.consumeToken(tok::identifier); + if (SP.P.Tok.getKind() != tok::string_literal) { + SP.P.diagnose(SP.P.Tok, diag::expected_in_attribute_list); + return true; + } + // Drop the double quotes. + StringRef replacedFunc = SP.P.Tok.getText().drop_front().drop_back(); + SILFunction *Func = M.lookUpFunction(replacedFunc.str()); + if (!Func) { + Identifier Id = SP.P.Context.getIdentifier(replacedFunc); + SP.P.diagnose(SP.P.Tok, diag::sil_dynamically_replaced_func_not_found, + Id); + return true; + } + *dynamicallyReplacedFunction = Func; + SP.P.consumeToken(tok::string_literal); + + SP.P.parseToken(tok::r_square, diag::expected_in_attribute_list); + continue; + } else if (objCReplacementFor && + SP.P.Tok.getText() == "objc_replacement_for") { + SP.P.consumeToken(tok::identifier); + if (SP.P.Tok.getKind() != tok::string_literal) { + SP.P.diagnose(SP.P.Tok, diag::expected_in_attribute_list); + return true; + } + // Drop the double quotes. + StringRef replacedFunc = SP.P.Tok.getText().drop_front().drop_back(); + *objCReplacementFor = SP.P.Context.getIdentifier(replacedFunc); + SP.P.consumeToken(tok::string_literal); + + SP.P.parseToken(tok::r_square, diag::expected_in_attribute_list); + continue; + } else if (Semantics && SP.P.Tok.getText() == "_semantics") { SP.P.consumeToken(tok::identifier); if (SP.P.Tok.getKind() != tok::string_literal) { SP.P.diagnose(SP.P.Tok, diag::expected_in_attribute_list); @@ -977,8 +1014,7 @@ static bool parseDeclSILOptional(bool *isTransparent, SP.P.parseToken(tok::r_square, diag::expected_in_attribute_list); continue; - } - else if (SpecAttrs && SP.P.Tok.getText() == "_specialize") { + } else if (SpecAttrs && SP.P.Tok.getText() == "_specialize") { SourceLoc AtLoc = SP.P.Tok.getLoc(); SourceLoc Loc(AtLoc); @@ -5239,13 +5275,15 @@ bool SILParserTUState::parseDeclSIL(Parser &P) { SmallVector SpecAttrs; ValueDecl *ClangDecl = nullptr; EffectsKind MRK = EffectsKind::Unspecified; + SILFunction *DynamicallyReplacedFunction = nullptr; + Identifier objCReplacementFor; if (parseSILLinkage(FnLinkage, P) || parseDeclSILOptional(&isTransparent, &isSerialized, &isCanonical, - &isThunk, &isDynamic, &isGlobalInit, - &inlineStrategy, &optimizationMode, nullptr, - &isWeakLinked, &isWithoutActuallyEscapingThunk, - &Semantics, &SpecAttrs, - &ClangDecl, &MRK, FunctionState) || + &isThunk, &isDynamic, &DynamicallyReplacedFunction, + &objCReplacementFor, &isGlobalInit, &inlineStrategy, + &optimizationMode, nullptr, &isWeakLinked, + &isWithoutActuallyEscapingThunk, &Semantics, + &SpecAttrs, &ClangDecl, &MRK, FunctionState, M) || P.parseToken(tok::at_sign, diag::expected_sil_function_name) || P.parseIdentifier(FnName, FnNameLoc, diag::expected_sil_function_name) || P.parseToken(tok::colon, diag::expected_sil_type)) @@ -5271,6 +5309,10 @@ bool SILParserTUState::parseDeclSIL(Parser &P) { FunctionState.F->setWasDeserializedCanonical(isCanonical); FunctionState.F->setThunk(IsThunk_t(isThunk)); FunctionState.F->setIsDynamic(isDynamic); + FunctionState.F->setDynamicallyReplacedFunction( + DynamicallyReplacedFunction); + if (!objCReplacementFor.empty()) + FunctionState.F->setObjCReplacement(objCReplacementFor); FunctionState.F->setGlobalInit(isGlobalInit); FunctionState.F->setWeakLinked(isWeakLinked); FunctionState.F->setWithoutActuallyEscapingThunk( @@ -5453,8 +5495,9 @@ bool SILParserTUState::parseSILGlobal(Parser &P) { SILParser State(P); if (parseSILLinkage(GlobalLinkage, P) || parseDeclSILOptional(nullptr, &isSerialized, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, &isLet, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, State) || + nullptr, nullptr, nullptr, nullptr, nullptr, &isLet, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + State, M) || P.parseToken(tok::at_sign, diag::expected_sil_value_name) || P.parseIdentifier(GlobalName, NameLoc, diag::expected_sil_value_name) || P.parseToken(tok::colon, diag::expected_sil_type)) @@ -5502,7 +5545,8 @@ bool SILParserTUState::parseSILProperty(Parser &P) { IsSerialized_t Serialized = IsNotSerialized; if (parseDeclSILOptional(nullptr, &Serialized, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, SP)) + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + SP, M)) return true; ValueDecl *VD; @@ -5570,7 +5614,8 @@ bool SILParserTUState::parseSILVTable(Parser &P) { IsSerialized_t Serialized = IsNotSerialized; if (parseDeclSILOptional(nullptr, &Serialized, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, VTableState)) + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + VTableState, M)) return true; // Parse the class name. @@ -6092,7 +6137,8 @@ bool SILParserTUState::parseSILWitnessTable(Parser &P) { IsSerialized_t isSerialized = IsNotSerialized; if (parseDeclSILOptional(nullptr, &isSerialized, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, WitnessState)) + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + WitnessState, M)) return true; Scope S(&P, ScopeKind::TopLevel); diff --git a/lib/SIL/SILFunction.cpp b/lib/SIL/SILFunction.cpp index b673e98aa06..067d0b1354a 100644 --- a/lib/SIL/SILFunction.cpp +++ b/lib/SIL/SILFunction.cpp @@ -128,6 +128,11 @@ SILFunction::~SILFunction() { // an allocator that may recycle freed memory. dropAllReferences(); + if (ReplacedFunction) { + ReplacedFunction->decrementRefCount(); + ReplacedFunction = nullptr; + } + auto &M = getModule(); for (auto &BB : *this) { for (auto I = BB.begin(), E = BB.end(); I != E;) { @@ -489,6 +494,9 @@ SILFunction::isPossiblyUsedExternally() const { if (linkage == SILLinkage::Hidden && hasCReferences()) return true; + if (ReplacedFunction) + return true; + return swift::isPossiblyUsedExternally(linkage, getModule().isWholeModule()); } @@ -517,6 +525,24 @@ bool SILFunction::shouldVerifyOwnership() const { return !hasSemanticsAttr("verify.ownership.sil.never"); } +static Identifier getIdentifierForObjCSelector(ObjCSelector selector, ASTContext &Ctxt) { + SmallVector buffer; + auto str = selector.getString(buffer); + return Ctxt.getIdentifier(str); +} + +void SILFunction::setObjCReplacement(AbstractFunctionDecl *replacedFunc) { + assert(ReplacedFunction == nullptr && ObjCReplacementFor.empty()); + assert(replacedFunc != nullptr); + ObjCReplacementFor = getIdentifierForObjCSelector( + replacedFunc->getObjCSelector(), getASTContext()); +} + +void SILFunction::setObjCReplacement(Identifier replacedFunc) { + assert(ReplacedFunction == nullptr && ObjCReplacementFor.empty()); + ObjCReplacementFor = replacedFunc; +} + // See swift/Basic/Statistic.h for declaration: this enables tracing // SILFunctions, is defined here to avoid too much layering violation / circular // linkage dependency. diff --git a/lib/SIL/SILModule.cpp b/lib/SIL/SILModule.cpp index ad2870c215b..40a72e7d705 100644 --- a/lib/SIL/SILModule.cpp +++ b/lib/SIL/SILModule.cpp @@ -111,8 +111,10 @@ SILModule::~SILModule() { // need to worry about sil_witness_tables since witness tables reference each // other via protocol conformances and sil_vtables don't reference each other // at all. - for (SILFunction &F : *this) + for (SILFunction &F : *this) { F.dropAllReferences(); + F.dropDynamicallyReplacedFunction(); + } } std::unique_ptr @@ -436,6 +438,7 @@ void SILModule::eraseFunction(SILFunction *F) { // This opens dead-function-removal opportunities for called functions. // (References are not needed anymore.) F->dropAllReferences(); + F->dropDynamicallyReplacedFunction(); } void SILModule::invalidateFunctionInSILCache(SILFunction *F) { diff --git a/lib/SIL/SILPrinter.cpp b/lib/SIL/SILPrinter.cpp index bd6cc892503..dad73ea50d5 100644 --- a/lib/SIL/SILPrinter.cpp +++ b/lib/SIL/SILPrinter.cpp @@ -2319,6 +2319,18 @@ void SILFunction::print(SILPrintContext &PrintCtx) const { else if (getEffectsKind() == EffectsKind::ReleaseNone) OS << "[releasenone] "; + if (auto *replacedFun = getDynamicallyReplacedFunction()) { + OS << "[dynamic_replacement_for \""; + OS << replacedFun->getName(); + OS << "\"] "; + } + + if (hasObjCReplacement()) { + OS << "[objc_replacement_for \""; + OS << getObjCReplacement().str(); + OS << "\"] "; + } + for (auto &Attr : getSemanticsAttrs()) OS << "[_semantics \"" << Attr << "\"] "; diff --git a/lib/SIL/SILVerifier.cpp b/lib/SIL/SILVerifier.cpp index 47e49a1f4ca..da599343e02 100644 --- a/lib/SIL/SILVerifier.cpp +++ b/lib/SIL/SILVerifier.cpp @@ -1406,6 +1406,9 @@ public: require(!RefF->isDynamicallyReplaceable(), "function_ref cannot reference a [dynamically_replaceable] function"); else if (isa(FRI)) { require(!RefF->isDynamicallyReplaceable(), "previous_function_ref cannot reference a [dynamically_replaceable] function"); + require(RefF->getDynamicallyReplacedFunction(), + "previous_function_ref must reference a " + "[dynamic_replacement_for:...] function"); } else if (isa(FRI)) require(RefF->isDynamicallyReplaceable(), "dynamic_function_ref cannot reference a [dynamically_replaceable] function"); diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 016da76b288..72fde0d1588 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -463,6 +463,7 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn, DeclID clangNodeOwnerID; TypeID funcTyID; + IdentifierID replacedFunctionID; GenericEnvironmentID genericEnvID; unsigned rawLinkage, isTransparent, isSerialized, isThunk, isWithoutactuallyEscapingThunk, isGlobal, inlineStrategy, @@ -473,8 +474,8 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn, scratch, rawLinkage, isTransparent, isSerialized, isThunk, isWithoutactuallyEscapingThunk, isGlobal, inlineStrategy, optimizationMode, effect, numSpecAttrs, hasQualifiedOwnership, - isWeakLinked, isDynamic, funcTyID, genericEnvID, clangNodeOwnerID, - SemanticsIDs); + isWeakLinked, isDynamic, funcTyID, replacedFunctionID, genericEnvID, + clangNodeOwnerID, SemanticsIDs); if (funcTyID == 0) { LLVM_DEBUG(llvm::dbgs() << "SILFunction typeID is 0.\n"); @@ -497,6 +498,17 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn, return nullptr; } + SILFunction *replacedFunction = nullptr; + Identifier replacedObjectiveCFunc; + if (replacedFunctionID && + ty.getAs()->getExtInfo().getRepresentation() != + SILFunctionTypeRepresentation::ObjCMethod) { + replacedFunction = + getFuncForReference(MF->getIdentifier(replacedFunctionID).str()); + } else if (replacedFunctionID) { + replacedObjectiveCFunc = MF->getIdentifier(replacedFunctionID); + } + auto linkage = fromStableSILLinkage(rawLinkage); if (!linkage) { LLVM_DEBUG(llvm::dbgs() << "invalid linkage code " << rawLinkage @@ -571,6 +583,10 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn, fn->setOptimizationMode(OptimizationMode(optimizationMode)); fn->setWeakLinked(isWeakLinked); fn->setIsDynamic(IsDynamicallyReplaceable_t(isDynamic)); + if (replacedFunction) + fn->setDynamicallyReplacedFunction(replacedFunction); + if (!replacedObjectiveCFunc.empty()) + fn->setObjCReplacement(replacedObjectiveCFunc); if (clangNodeOwner) fn->setClangNodeOwner(clangNodeOwner); for (auto ID : SemanticsIDs) { @@ -2498,6 +2514,7 @@ bool SILDeserializer::hasSILFunction(StringRef Name, // linkage to avoid re-reading it from the bitcode each time? DeclID clangOwnerID; TypeID funcTyID; + IdentifierID replacedFunctionID; GenericEnvironmentID genericEnvID; unsigned rawLinkage, isTransparent, isSerialized, isThunk, isWithoutactuallyEscapingThunk, isGlobal, inlineStrategy, @@ -2508,7 +2525,8 @@ bool SILDeserializer::hasSILFunction(StringRef Name, scratch, rawLinkage, isTransparent, isSerialized, isThunk, isWithoutactuallyEscapingThunk, isGlobal, inlineStrategy, optimizationMode, effect, numSpecAttrs, hasQualifiedOwnership, - isWeakLinked, isDynamic, funcTyID, genericEnvID, clangOwnerID, SemanticsIDs); + isWeakLinked, isDynamic, funcTyID, replacedFunctionID, genericEnvID, + clangOwnerID, SemanticsIDs); auto linkage = fromStableSILLinkage(rawLinkage); if (!linkage) { LLVM_DEBUG(llvm::dbgs() << "invalid linkage code " << rawLinkage diff --git a/lib/Serialization/SILFormat.h b/lib/Serialization/SILFormat.h index 215c25ff0c0..8340a47118a 100644 --- a/lib/Serialization/SILFormat.h +++ b/lib/Serialization/SILFormat.h @@ -292,6 +292,7 @@ namespace sil_block { BCFixed<1>, // must be weakly referenced BCFixed<1>, // is dynamically replacable TypeIDField, // SILFunctionType + DeclIDField, // SILFunction name or 0 (replaced function) GenericEnvironmentIDField, DeclIDField, // ClangNode owner BCArray // Semantics Attribute diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index 91f92b470f6..16e363e3472 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -385,6 +385,15 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) { if (F.hasClangNode()) clangNodeOwnerID = S.addDeclRef(F.getClangNodeOwner()); + IdentifierID replacedFunctionID = 0; + if (auto *fun = F.getDynamicallyReplacedFunction()) { + addReferencedSILFunction(fun, true); + replacedFunctionID = S.addUniquedStringRef(fun->getName()); + } + else if (F.hasObjCReplacement()) { + replacedFunctionID = + S.addUniquedStringRef(F.getObjCReplacement().str()); + } unsigned numSpecAttrs = NoBody ? 0 : F.getSpecializeAttrs().size(); SILFunctionLayout::emitRecord( Out, ScratchRecord, abbrCode, toStableSILLinkage(Linkage), @@ -394,7 +403,7 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) { (unsigned)F.getOptimizationMode(), (unsigned)F.getEffectsKind(), (unsigned)numSpecAttrs, (unsigned)F.hasQualifiedOwnership(), F.isWeakLinked(), (unsigned)F.isDynamicallyReplaceable(), FnID, - genericEnvID, clangNodeOwnerID, SemanticsIDs); + replacedFunctionID, genericEnvID, clangNodeOwnerID, SemanticsIDs); if (NoBody) return; diff --git a/test/SIL/Parser/basic.sil b/test/SIL/Parser/basic.sil index d0e7cf10951..9dd1ec1dd1c 100644 --- a/test/SIL/Parser/basic.sil +++ b/test/SIL/Parser/basic.sil @@ -1684,7 +1684,14 @@ sil [dynamically_replacable] @test_dynamically_replaceable : $@convention(thin) bb0: %0 = tuple () return %0 : $() -} +} + +// CHECK-LABEL: sil [dynamic_replacement_for "test_dynamically_replaceable"] @test_dynamic_replacement_for +sil [dynamic_replacement_for "test_dynamically_replaceable"] @test_dynamic_replacement_for : $@convention(thin) () -> () { +bb0: + %0 = tuple () + return %0 : $() +} struct EmptyStruct {} diff --git a/test/SIL/Serialization/dynamically_replaceable.sil b/test/SIL/Serialization/dynamically_replaceable.sil index 1b974c22dcc..4ddde1fa787 100644 --- a/test/SIL/Serialization/dynamically_replaceable.sil +++ b/test/SIL/Serialization/dynamically_replaceable.sil @@ -10,9 +10,9 @@ bb0: return %0 : $() } -// CHECK-DAG-LABEL: sil [serialized] [canonical] @test_dynamic_replacement_for +// CHECK-DAG-LABEL: sil [serialized] [dynamic_replacement_for "test_dynamically_replaceable"] [canonical] @test_dynamic_replacement_for // CHECK: prev_dynamic_function_ref @test_dynamic_replacement_for -sil [serialized] @test_dynamic_replacement_for : $@convention(thin) () -> () { +sil [serialized] [dynamic_replacement_for "test_dynamically_replaceable"] @test_dynamic_replacement_for : $@convention(thin) () -> () { bb0: %0 = prev_dynamic_function_ref @test_dynamic_replacement_for : $@convention(thin) () -> () %1 = apply %0() : $@convention(thin) () -> ()