From 92b0c5f5d0d5b0d9b8793da54ff8530ff7dddfcd Mon Sep 17 00:00:00 2001 From: Kuba Mracek Date: Sat, 25 Jan 2025 09:09:47 -0800 Subject: [PATCH 1/3] [embedded] Support _findStringSwitchCaseWithCache in Embedded Swift --- .../MandatoryPerformanceOptimizations.swift | 10 +++++ lib/SILOptimizer/PassManager/PassManager.cpp | 6 ++- test/embedded/string-switch2.swift | 37 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/embedded/string-switch2.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift index 76b4eb5291306..7fb9634e042a8 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift @@ -80,6 +80,16 @@ fileprivate struct PathFunctionTuple: Hashable { private func optimize(function: Function, _ context: FunctionPassContext, _ moduleContext: ModulePassContext, _ worklist: inout FunctionWorklist) { var alreadyInlinedFunctions: Set = Set() + + // ObjectOutliner replaces calls to findStringSwitchCase with _findStringSwitchCaseWithCache, but this happens as a late SIL optimization, + // which is a problem for Embedded Swift, because _findStringSwitchCaseWithCache will then reference non-specialized code. Solve this by + // eagerly linking and specializing _findStringSwitchCaseWithCache whenever findStringSwitchCase is found in the module. + if context.options.enableEmbeddedSwift { + if function.hasSemanticsAttribute("findStringSwitchCase"), + let f = context.lookupStdlibFunction(name: "_findStringSwitchCaseWithCache") { + worklist.pushIfNotVisited(f) + } + } var changed = true while changed { diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index 64e1e3d37ede5..a46bc21d791e5 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -1929,7 +1929,11 @@ OptionalBridgedFunction BridgedPassContext::lookupStdlibFunction(BridgedStringRe SILDeclRef declRef(decl, SILDeclRef::Kind::Func); SILOptFunctionBuilder funcBuilder(*invocation->getTransform()); - return {funcBuilder.getOrCreateFunction(SILLocation(decl), declRef, NotForDefinition)}; + SILFunction *function = funcBuilder.getOrCreateFunction(SILLocation(decl), declRef, NotForDefinition); + if (mod->getOptions().EmbeddedSwift) { + mod->linkFunction(function, SILModule::LinkingMode::LinkAll); + } + return {function}; } OptionalBridgedFunction BridgedPassContext::lookUpNominalDeinitFunction(BridgedDeclObj nominal) const { diff --git a/test/embedded/string-switch2.swift b/test/embedded/string-switch2.swift new file mode 100644 index 0000000000000..1d88535522fab --- /dev/null +++ b/test/embedded/string-switch2.swift @@ -0,0 +1,37 @@ +// RUN: %target-run-simple-swift(-enable-experimental-feature Embedded -wmo -O -Xlinker %swift_obj_root/lib/swift/embedded/%target-cpu-apple-macos/libswiftUnicodeDataTables.a) | %FileCheck %s + +// REQUIRES: swift_in_compiler +// REQUIRES: executable_test +// REQUIRES: optimized_stdlib +// REQUIRES: swift_stdlib_no_asserts +// REQUIRES: OS=macosx +// REQUIRES: swift_feature_Embedded + +enum MyEnum: String { + case case1 + case case2 + case case3 + case case4 + case case5 + case case6 + case case7 + case case8 + case case9 + case case10 + case case11 + case case12 + case case13 + case case14 + case case15 + case case16 + case case17 + case case18 + case case19 +} + +var e = MyEnum.case1 +print(e.rawValue) +e = MyEnum.case2 +print(e.rawValue) +// CHECK: case1 +// CHECK: case2 From e1d22b5076d4e9d4d1ac33730e1f62a80fb3bf7c Mon Sep 17 00:00:00 2001 From: Kuba Mracek Date: Tue, 28 Jan 2025 10:03:14 -0800 Subject: [PATCH 2/3] [embedded] Avoid changing lookupStdlibFunction, load findStringSwitchCaseWithCache in MPO instead --- .../ModulePasses/MandatoryPerformanceOptimizations.swift | 3 ++- lib/SILOptimizer/IPO/DeadFunctionElimination.cpp | 8 ++++++++ lib/SILOptimizer/PassManager/PassManager.cpp | 6 +----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift index 7fb9634e042a8..0e397471e9528 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift @@ -86,7 +86,8 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ modu // eagerly linking and specializing _findStringSwitchCaseWithCache whenever findStringSwitchCase is found in the module. if context.options.enableEmbeddedSwift { if function.hasSemanticsAttribute("findStringSwitchCase"), - let f = context.lookupStdlibFunction(name: "_findStringSwitchCaseWithCache") { + let f = context.lookupStdlibFunction(name: "_findStringSwitchCaseWithCache"), + context.loadFunction(function: f, loadCalleesRecursively: true) { worklist.pushIfNotVisited(f) } } diff --git a/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp b/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp index 9ee5b7ee7b15f..1ad39abb74e3f 100644 --- a/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp +++ b/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp @@ -124,6 +124,14 @@ class DeadFunctionAndGlobalElimination { if (F->getRepresentation() == SILFunctionTypeRepresentation::ObjCMethod) return true; + // To support ObjectOutliner's replacing of calls to findStringSwitchCase + // with _findStringSwitchCaseWithCache. In Embedded Swift, we have to load + // the body of this function early and specialize it, so that ObjectOutliner + // can reference it later. To make this work we have to avoid DFE'ing it. + // Linker's dead-stripping will eventually remove this if actually unused. + if (F->hasSemanticsAttr("findStringSwitchCaseWithCache")) + return true; + return false; } diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index a46bc21d791e5..64e1e3d37ede5 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -1929,11 +1929,7 @@ OptionalBridgedFunction BridgedPassContext::lookupStdlibFunction(BridgedStringRe SILDeclRef declRef(decl, SILDeclRef::Kind::Func); SILOptFunctionBuilder funcBuilder(*invocation->getTransform()); - SILFunction *function = funcBuilder.getOrCreateFunction(SILLocation(decl), declRef, NotForDefinition); - if (mod->getOptions().EmbeddedSwift) { - mod->linkFunction(function, SILModule::LinkingMode::LinkAll); - } - return {function}; + return {funcBuilder.getOrCreateFunction(SILLocation(decl), declRef, NotForDefinition)}; } OptionalBridgedFunction BridgedPassContext::lookUpNominalDeinitFunction(BridgedDeclObj nominal) const { From 500f8ad8939606160c7843ad6c801bb1b961854e Mon Sep 17 00:00:00 2001 From: Kuba Mracek Date: Tue, 28 Jan 2025 10:07:54 -0800 Subject: [PATCH 3/3] [embedded] Only treat findStringSwitchCaseWithCache as anchor in early DFE, let late DFE collect it if unused --- .../IPO/DeadFunctionElimination.cpp | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp b/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp index 1ad39abb74e3f..e45dfe2c89285 100644 --- a/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp +++ b/lib/SILOptimizer/IPO/DeadFunctionElimination.cpp @@ -90,6 +90,7 @@ class DeadFunctionAndGlobalElimination { llvm::SmallPtrSet AliveFunctionsAndTables; bool keepExternalWitnessTablesAlive; + bool keepStringSwitchIntrinsicAlive; /// Checks is a function is alive, e.g. because it is visible externally. bool isAnchorFunction(SILFunction *F) { @@ -127,9 +128,10 @@ class DeadFunctionAndGlobalElimination { // To support ObjectOutliner's replacing of calls to findStringSwitchCase // with _findStringSwitchCaseWithCache. In Embedded Swift, we have to load // the body of this function early and specialize it, so that ObjectOutliner - // can reference it later. To make this work we have to avoid DFE'ing it. - // Linker's dead-stripping will eventually remove this if actually unused. - if (F->hasSemanticsAttr("findStringSwitchCaseWithCache")) + // can reference it later. To make this work we have to avoid DFE'ing it in + // the early DFE pass. Late DFE will take care of it if actually unused. + if (keepStringSwitchIntrinsicAlive && + F->hasSemanticsAttr("findStringSwitchCaseWithCache")) return true; return false; @@ -721,9 +723,11 @@ class DeadFunctionAndGlobalElimination { public: DeadFunctionAndGlobalElimination(SILModule *module, - bool keepExternalWitnessTablesAlive) : + bool keepExternalWitnessTablesAlive, + bool keepStringSwitchIntrinsicAlive) : Module(module), - keepExternalWitnessTablesAlive(keepExternalWitnessTablesAlive) {} + keepExternalWitnessTablesAlive(keepExternalWitnessTablesAlive), + keepStringSwitchIntrinsicAlive(keepStringSwitchIntrinsicAlive) {} /// The main entry point of the optimization. void eliminateFunctionsAndGlobals(SILModuleTransform *DFEPass) { @@ -807,8 +811,10 @@ class DeadFunctionAndGlobalEliminationPass : public SILModuleTransform { // can eliminate such functions. getModule()->invalidateSILLoaderCaches(); - DeadFunctionAndGlobalElimination deadFunctionElimination(getModule(), - /*keepExternalWitnessTablesAlive*/ !isLateDFE); + DeadFunctionAndGlobalElimination deadFunctionElimination( + getModule(), + /*keepExternalWitnessTablesAlive*/ !isLateDFE, + /*keepStringSwitchIntrinsicAlive*/ !isLateDFE); deadFunctionElimination.eliminateFunctionsAndGlobals(this); } };