From 85aa1c12dbed1d49518f72018bf088361b6e135a Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Wed, 5 Apr 2023 13:29:39 +0200 Subject: [PATCH 01/23] handle debug_step in static initializers of globals and in the const expression evaluator --- lib/SIL/IR/SILGlobalVariable.cpp | 2 +- lib/SILOptimizer/Utils/ConstExpr.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/SIL/IR/SILGlobalVariable.cpp b/lib/SIL/IR/SILGlobalVariable.cpp index 23cb54817b2a9..f1a45d84a6dba 100644 --- a/lib/SIL/IR/SILGlobalVariable.cpp +++ b/lib/SIL/IR/SILGlobalVariable.cpp @@ -313,7 +313,7 @@ swift::getVariableOfStaticInitializer(SILFunction *InitFunc, // Make sure we have a single GlobalAddrInst and a single StoreInst. // And the StoreInst writes to the GlobalAddrInst. if (isa(&I) || isa(&I) - || isa(&I)) { + || isa(&I) || isa(&I)) { continue; } else if (auto *sga = dyn_cast(&I)) { if (SGA) diff --git a/lib/SILOptimizer/Utils/ConstExpr.cpp b/lib/SILOptimizer/Utils/ConstExpr.cpp index 3b86c6c0c897d..ff650024249af 100644 --- a/lib/SILOptimizer/Utils/ConstExpr.cpp +++ b/lib/SILOptimizer/Utils/ConstExpr.cpp @@ -1766,7 +1766,7 @@ ConstExprFunctionState::evaluateFlowSensitive(SILInstruction *inst) { isa(inst) || isa(inst) || isa(inst) || isa(inst) || isa(inst) || isa(inst) || - isa(inst) || + isa(inst) || isa(inst) || // Skip instrumentation isInstrumentation(inst)) return None; From 3560360ded58bf80a949e3d238958d76457493a9 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 11 Apr 2023 11:06:00 +0200 Subject: [PATCH 02/23] Handle `builtin.once` in BottomUpFunctionOrder --- lib/SILOptimizer/Analysis/FunctionOrder.cpp | 25 +++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/SILOptimizer/Analysis/FunctionOrder.cpp b/lib/SILOptimizer/Analysis/FunctionOrder.cpp index f10dcb6885270..47666baf5171c 100644 --- a/lib/SILOptimizer/Analysis/FunctionOrder.cpp +++ b/lib/SILOptimizer/Analysis/FunctionOrder.cpp @@ -39,15 +39,26 @@ void BottomUpFunctionOrder::DFS(SILFunction *Start) { // Visit all the instructions, looking for apply sites. for (auto &B : *Start) { for (auto &I : B) { - auto FAS = FullApplySite::isa(&I); - if (!FAS && !isa(&I) && !isa(&I) && - !isa(&I)) + CalleeList callees; + if (auto FAS = FullApplySite::isa(&I)) { + callees = BCA->getCalleeList(FAS); + } else if (isa(&I) || isa(&I) || + isa(&I)) { + callees = BCA->getDestructors(I.getOperand(0)->getType(), /*isExactType*/ false); + } else if (auto *bi = dyn_cast(&I)) { + switch (bi->getBuiltinInfo().ID) { + case BuiltinValueKind::Once: + case BuiltinValueKind::OnceWithContext: + callees = BCA->getCalleeListOfValue(bi->getArguments()[1]); + break; + default: + continue; + } + } else { continue; + } - auto Callees = FAS ? BCA->getCalleeList(FAS) - : BCA->getDestructors(I.getOperand(0)->getType(), - /*isExactType*/ false); - for (auto *CalleeFn : Callees) { + for (auto *CalleeFn : callees) { // If not yet visited, visit the callee. if (DFSNum.find(CalleeFn) == DFSNum.end()) { DFS(CalleeFn); From 8a8a895239980343ec6a6d16930889fec389e75d Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 11:17:55 +0200 Subject: [PATCH 03/23] alias analysis: compute more precise memory effects of `builtin "once"` * Check if the address in question is even visible from outside the function * Return the memory effects of the called function Also, add a new API `Instruction.memoryEffects`, which is internally used by `mayReadFromMemory` et al. --- .../Optimizer/Analysis/AliasAnalysis.swift | 55 ++++++++++++++----- .../Optimizer/Analysis/CalleeAnalysis.swift | 10 +++- .../Sources/SIL/Instruction.swift | 36 +++++------- lib/SILOptimizer/Analysis/MemoryBehavior.cpp | 31 ++--------- .../UtilityPasses/MemBehaviorDumper.cpp | 1 + test/SILOptimizer/mem-behavior.sil | 22 ++++++++ 6 files changed, 89 insertions(+), 66 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift b/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift index 384c6e2d7eb38..b265821a5ba9d 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift @@ -68,19 +68,17 @@ struct AliasAnalysis { let inst = bridgedInst.instruction let val = bridgedVal.value let path = AliasAnalysis.getPtrOrAddressPath(for: val) - if let apply = inst as? ApplySite { - let effect = getMemoryEffect(of: apply, for: val, path: path, context) - switch (effect.read, effect.write) { - case (false, false): return .None - case (true, false): return .MayRead - case (false, true): return .MayWrite - case (true, true): return .MayReadWrite + switch inst { + case let apply as ApplySite: + return getMemoryEffect(ofApply: apply, for: val, path: path, context).bridged + case let builtin as BuiltinInst: + return getMemoryEffect(ofBuiltin: builtin, for: val, path: path, context).bridged + default: + if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) { + return .MayReadWrite } + return .None } - if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) { - return .MayReadWrite - } - return .None }, // isObjReleasedFn @@ -121,7 +119,7 @@ struct AliasAnalysis { } } -private func getMemoryEffect(of apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory { +private func getMemoryEffect(ofApply apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory { let calleeAnalysis = context.calleeAnalysis let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: calleeAnalysis, isAddress: true) let memoryEffects: SideEffects.Memory @@ -132,7 +130,7 @@ private func getMemoryEffect(of apply: ApplySite, for address: Value, path: Smal memoryEffects = result.memory } else { // `address` has unknown escapes. So we have to take the global effects of the called function(s). - memoryEffects = calleeAnalysis.getSideEffects(of: apply).memory + memoryEffects = calleeAnalysis.getSideEffects(ofApply: apply).memory } // Do some magic for `let` variables. Function calls cannot modify let variables. // The only exception is that the let variable is directly passed to an indirect out of the @@ -144,6 +142,20 @@ private func getMemoryEffect(of apply: ApplySite, for address: Value, path: Smal return memoryEffects } +private func getMemoryEffect(ofBuiltin builtin: BuiltinInst, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory { + + switch builtin.id { + case .Once, .OnceWithContext: + if !address.at(path).isEscaping(using: AddressVisibleByBuiltinOnceVisitor(), context) { + return SideEffects.Memory() + } + let callee = builtin.operands[1].value + return context.calleeAnalysis.getSideEffects(ofCallee: callee).memory + default: + return builtin.memoryEffects + } +} + private func getOwnershipEffect(of apply: ApplySite, for value: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Ownership { let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: context.calleeAnalysis, isAddress: false) if let result = value.at(path).visit(using: visitor, context) { @@ -151,7 +163,7 @@ private func getOwnershipEffect(of apply: ApplySite, for value: Value, path: Sma return result.ownership } else { // `value` has unknown escapes. So we have to take the global effects of the called function(s). - return visitor.calleeAnalysis.getSideEffects(of: apply).ownership + return visitor.calleeAnalysis.getSideEffects(ofApply: apply).ownership } } @@ -180,6 +192,11 @@ private struct SideEffectsVisitor : EscapeVisitorWithResult { var followLoads: Bool { !isAddress } } +private struct AddressVisibleByBuiltinOnceVisitor : EscapeVisitor { + var followTrivialTypes: Bool { true } + var followLoads: Bool { false } +} + /// Lets `ProjectedValue.isEscaping` return true if the value is "escaping" to the `target` instruction. private struct EscapesToInstructionVisitor : EscapeVisitor { let target: Instruction @@ -229,3 +246,13 @@ private struct IsIndirectResultWalker: AddressDefUseWalker { } } +private extension SideEffects.Memory { + var bridged: swift.MemoryBehavior { + switch (read, write) { + case (false, false): return .None + case (true, false): return .MayRead + case (false, true): return .MayWrite + case (true, true): return .MayReadWrite + } + } +} diff --git a/SwiftCompilerSources/Sources/Optimizer/Analysis/CalleeAnalysis.swift b/SwiftCompilerSources/Sources/Optimizer/Analysis/CalleeAnalysis.swift index 38cef14bdd324..18f0640d4a958 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Analysis/CalleeAnalysis.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Analysis/CalleeAnalysis.swift @@ -25,7 +25,7 @@ public struct CalleeAnalysis { // getMemBehaviorFn { (bridgedApply: BridgedInstruction, observeRetains: Bool, bca: BridgedCalleeAnalysis) -> swift.MemoryBehavior in let apply = bridgedApply.instruction as! ApplySite - let e = bca.analysis.getSideEffects(of: apply) + let e = bca.analysis.getSideEffects(ofApply: apply) return e.getMemBehavior(observeRetains: observeRetains) } ) @@ -60,8 +60,12 @@ public struct CalleeAnalysis { } /// Returns the global (i.e. not argument specific) side effects of an apply. - public func getSideEffects(of apply: ApplySite) -> SideEffects.GlobalEffects { - guard let callees = getCallees(callee: apply.callee) else { + public func getSideEffects(ofApply apply: ApplySite) -> SideEffects.GlobalEffects { + return getSideEffects(ofCallee: apply.callee) + } + + public func getSideEffects(ofCallee callee: Value) -> SideEffects.GlobalEffects { + guard let callees = getCallees(callee: callee) else { return .worstEffects } diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index 74471ab4f4ab0..4133d29fbc0a4 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -73,32 +73,24 @@ public class Instruction : CustomStringConvertible, Hashable { return mayTrap || mayWriteToMemory } - final public var mayReadFromMemory: Bool { + final public var memoryEffects: SideEffects.Memory { switch bridged.getMemBehavior() { - case .MayRead, .MayReadWrite, .MayHaveSideEffects: - return true - default: - return false + case .None: + return SideEffects.Memory() + case .MayRead: + return SideEffects.Memory(read: true) + case .MayWrite: + return SideEffects.Memory(write: true) + case .MayReadWrite, .MayHaveSideEffects: + return SideEffects.Memory(read: true, write: true) + default: + fatalError("invalid memory behavior") } } - final public var mayWriteToMemory: Bool { - switch bridged.getMemBehavior() { - case .MayWrite, .MayReadWrite, .MayHaveSideEffects: - return true - default: - return false - } - } - - final public var mayReadOrWriteMemory: Bool { - switch bridged.getMemBehavior() { - case .MayRead, .MayWrite, .MayReadWrite, .MayHaveSideEffects: - return true - default: - return false - } - } + final public var mayReadFromMemory: Bool { memoryEffects.read } + final public var mayWriteToMemory: Bool { memoryEffects.write } + final public var mayReadOrWriteMemory: Bool { memoryEffects.read || memoryEffects.write } public final var mayRelease: Bool { return bridged.mayRelease() diff --git a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp index 2e129b8de2a9f..4296e66bfd654 100644 --- a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp +++ b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp @@ -314,34 +314,11 @@ MemBehavior MemoryBehaviorVisitor::visitMarkUnresolvedMoveAddrInst( } MemBehavior MemoryBehaviorVisitor::visitBuiltinInst(BuiltinInst *BI) { - // If our callee is not a builtin, be conservative and return may have side - // effects. - if (!BI) { - return MemBehavior::MayHaveSideEffects; - } - - // If the builtin is read none, it does not read or write memory. - if (!BI->mayReadOrWriteMemory()) { - LLVM_DEBUG(llvm::dbgs() << " Found apply of read none builtin. Returning" - " None.\n"); - return MemBehavior::None; - } - - // If the builtin is side effect free, then it can only read memory. - if (!BI->mayHaveSideEffects()) { - LLVM_DEBUG(llvm::dbgs() << " Found apply of side effect free builtin. " - "Returning MayRead.\n"); - return MemBehavior::MayRead; + MemBehavior mb = BI->getMemoryBehavior(); + if (mb != MemBehavior::None) { + return AA->getMemoryBehaviorOfInst(V, BI); } - - // FIXME: If the value (or any other values from the instruction that the - // value comes from) that we are tracking does not escape and we don't alias - // any of the arguments of the apply inst, we should be ok. - - // Otherwise be conservative and return that we may have side effects. - LLVM_DEBUG(llvm::dbgs() << " Found apply of side effect builtin. " - "Returning MayHaveSideEffects.\n"); - return MemBehavior::MayHaveSideEffects; + return MemBehavior::None; } MemBehavior MemoryBehaviorVisitor::visitTryApplyInst(TryApplyInst *AI) { diff --git a/lib/SILOptimizer/UtilityPasses/MemBehaviorDumper.cpp b/lib/SILOptimizer/UtilityPasses/MemBehaviorDumper.cpp index 1f805d7d5e05a..d8d5beae873e7 100644 --- a/lib/SILOptimizer/UtilityPasses/MemBehaviorDumper.cpp +++ b/lib/SILOptimizer/UtilityPasses/MemBehaviorDumper.cpp @@ -68,6 +68,7 @@ class MemBehaviorDumper : public SILModuleTransform { case SILInstructionKind::LoadInst: case SILInstructionKind::StoreInst: case SILInstructionKind::CopyAddrInst: + case SILInstructionKind::BuiltinInst: return true; default: return false; diff --git a/test/SILOptimizer/mem-behavior.sil b/test/SILOptimizer/mem-behavior.sil index 304cc83669c5d..976e4d3a3090b 100644 --- a/test/SILOptimizer/mem-behavior.sil +++ b/test/SILOptimizer/mem-behavior.sil @@ -980,3 +980,25 @@ bb0(%0 : $Builtin.Int32): return %r : $() } +sil_global @gi : $Int +sil @g_init : $@convention(c) () -> () + +// CHECK-LABEL: @test_builtin_once +// CHECK: PAIR #0. +// CHECK-NEXT: %4 = builtin "once"(%0 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() +// CHECK-NEXT: %1 = global_addr @gi : $*Int +// CHECK-NEXT: r=1,w=1 +// CHECK: PAIR #1. +// CHECK-NEXT: %4 = builtin "once"(%0 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() +// CHECK-NEXT: %2 = alloc_stack $Int +// CHECK-NEXT: r=0,w=0 +sil @test_builtin_once : $@convention(thin) (Builtin.RawPointer) -> () { +bb0(%0 : $Builtin.RawPointer): + %1 = global_addr @gi : $*Int + %2 = alloc_stack $Int + %3 = function_ref @g_init : $@convention(c) () -> () + %4 = builtin "once"(%0 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() + dealloc_stack %2 : $*Int + %6 = tuple () + return %6 : $() +} From a8c9aae1e0c98ba1e5e15445d200d5f5f479f1d1 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 11:34:35 +0200 Subject: [PATCH 04/23] Swift Optimizer: add simplification for `cond_fail` --- .../InstructionSimplification/CMakeLists.txt | 1 + .../SimplifyCondFail.swift | 29 ++++++++++++++ test/SILOptimizer/simplify_cond_fail.sil | 39 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyCondFail.swift create mode 100644 test/SILOptimizer/simplify_cond_fail.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt index f9ade116fb17a..70f198f0d7a57 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt @@ -12,6 +12,7 @@ swift_compiler_sources(Optimizer SimplifyBranch.swift SimplifyBuiltin.swift SimplifyCondBranch.swift + SimplifyCondFail.swift SimplifyGlobalValue.swift SimplifyStrongRetainRelease.swift SimplifyStructExtract.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyCondFail.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyCondFail.swift new file mode 100644 index 0000000000000..9a423cae55b5b --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyCondFail.swift @@ -0,0 +1,29 @@ +//===--- SimplifyCondFail.swift -------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +extension CondFailInst : OnoneSimplifyable { + func simplify(_ context: SimplifyContext) { + + /// Eliminates + /// ``` + /// %0 = integer_literal 0 + /// cond_fail %0, "message" + /// ``` + if let literal = condition as? IntegerLiteralInst, + literal.value.isZero() { + + context.erase(instruction: self) + } + } +} diff --git a/test/SILOptimizer/simplify_cond_fail.sil b/test/SILOptimizer/simplify_cond_fail.sil new file mode 100644 index 0000000000000..116105c18cca5 --- /dev/null +++ b/test/SILOptimizer/simplify_cond_fail.sil @@ -0,0 +1,39 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=cond_fail | %FileCheck %s + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +// CHECK-LABEL: sil @not_failing +// CHECK-NOT: cond_fail +// CHECK: } // end sil function 'not_failing' +sil @not_failing : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int1, 0 + cond_fail %0 : $Builtin.Int1, "not failing" + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil @failing +// CHECK: cond_fail +// CHECK: } // end sil function 'failing' +sil @failing : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int1, -1 + cond_fail %0 : $Builtin.Int1, "failing" + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil @unknown +// CHECK: cond_fail +// CHECK: } // end sil function 'unknown' +sil @unknown : $@convention(thin) (Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1): + cond_fail %0 : $Builtin.Int1, "unknown" + %r = tuple () + return %r : $() +} + From 0a051240231ef3b131db94a4696019192a4771c4 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 11:43:06 +0200 Subject: [PATCH 05/23] Swift Optimizer: add simplification of `debug_step` When compiling with optimizations, unconditionally remove `debug_step` --- .../InstructionSimplification/CMakeLists.txt | 1 + .../SimplifyDebugStep.swift | 22 +++++++++++++++++++ test/SILOptimizer/simplify_debug_step.sil | 19 ++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDebugStep.swift create mode 100644 test/SILOptimizer/simplify_debug_step.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt index 70f198f0d7a57..0ec22ea0a464e 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt @@ -13,6 +13,7 @@ swift_compiler_sources(Optimizer SimplifyBuiltin.swift SimplifyCondBranch.swift SimplifyCondFail.swift + SimplifyDebugStep.swift SimplifyGlobalValue.swift SimplifyStrongRetainRelease.swift SimplifyStructExtract.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDebugStep.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDebugStep.swift new file mode 100644 index 0000000000000..ba4712b3fcc1f --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDebugStep.swift @@ -0,0 +1,22 @@ +//===--- SimplifyDebugStep.swift ------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +extension DebugStepInst : Simplifyable { + func simplify(_ context: SimplifyContext) { + // When compiling with optimizations (note: it's not a OnoneSimplifyable transformation), + // unconditionally remove debug_step instructions. + context.erase(instruction: self) + } +} + diff --git a/test/SILOptimizer/simplify_debug_step.sil b/test/SILOptimizer/simplify_debug_step.sil new file mode 100644 index 0000000000000..df53acb6f4cca --- /dev/null +++ b/test/SILOptimizer/simplify_debug_step.sil @@ -0,0 +1,19 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=debug_step | %FileCheck %s --check-prefix=CHECK-ONONE --check-prefix=CHECK +// RUN: %target-sil-opt -enable-sil-verify-all %s -simplification -simplify-instruction=debug_step | %FileCheck %s --check-prefix=CHECK-O --check-prefix=CHECK + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +// CHECK-LABEL: sil @testit +// CHECK-O-NOT: debug_step +// CHECK-ONONE: debug_step +// CHECK: } // end sil function 'testit' +sil @testit : $@convention(thin) () -> () { +bb0: + debug_step + %r = tuple () + return %r : $() +} + From 5a3ab6ee2e62d7afb4a5552e1c2bc9c9cd81231f Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 11:52:44 +0200 Subject: [PATCH 06/23] Swift Optimizer: add simplifications for `destructure_struct` and `destructure_tuple` Eliminate the redundant instruction pair ``` %t = tuple (%0, %1, %2) (%3, %4, %5) = destructure_tuple %t ``` and replace the results %3, %4, %5 with %0, %1, %2, respectively. The same for structs. --- .../InstructionSimplification/CMakeLists.txt | 1 + .../SimplifyDestructure.swift | 65 +++++++++++++++++ .../simplify_destructure_struct.sil | 73 +++++++++++++++++++ .../simplify_destructure_tuple.sil | 67 +++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift create mode 100644 test/SILOptimizer/simplify_destructure_struct.sil create mode 100644 test/SILOptimizer/simplify_destructure_tuple.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt index 0ec22ea0a464e..9e353cc6887a9 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt @@ -14,6 +14,7 @@ swift_compiler_sources(Optimizer SimplifyCondBranch.swift SimplifyCondFail.swift SimplifyDebugStep.swift + SimplifyDestructure.swift SimplifyGlobalValue.swift SimplifyStrongRetainRelease.swift SimplifyStructExtract.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift new file mode 100644 index 0000000000000..134fa901baa14 --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift @@ -0,0 +1,65 @@ +//===--- SimplifyDestructure.swift ----------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +extension DestructureTupleInst : OnoneSimplifyable { + func simplify(_ context: SimplifyContext) { + + // Eliminate the redundant instruction pair + // ``` + // %t = tuple (%0, %1, %2) + // (%3, %4, %5) = destructure_tuple %t + // ``` + // and replace the results %3, %4, %5 with %0, %1, %2, respectively + // + if let tuple = self.tuple as? TupleInst { + tryReplaceConstructDestructPair(construct: tuple, destruct: self, context) + } + } +} + +extension DestructureStructInst : OnoneSimplifyable { + func simplify(_ context: SimplifyContext) { + + // Eliminate the redundant instruction pair + // ``` + // %s = struct (%0, %1, %2) + // (%3, %4, %5) = destructure_struct %s + // ``` + // and replace the results %3, %4, %5 with %0, %1, %2, respectively + // + if let str = self.struct as? StructInst { + tryReplaceConstructDestructPair(construct: str, destruct: self, context) + } + } +} + +private func tryReplaceConstructDestructPair(construct: SingleValueInstruction, + destruct: MultipleValueInstruction, + _ context: SimplifyContext) { + let singleUse = context.preserveDebugInfo ? construct.uses.singleUse : construct.uses.singleNonDebugUse + let canEraseFirst = singleUse?.instruction == destruct + + if !canEraseFirst && construct.parentFunction.hasOwnership && construct.ownership == .owned { + // We cannot add more uses to this tuple without inserting a copy. + return + } + + for (result, operand) in zip(destruct.results, construct.operands) { + result.uses.replaceAll(with: operand.value, context) + } + + if canEraseFirst { + context.erase(instructionIncludingDebugUses: destruct) + } +} diff --git a/test/SILOptimizer/simplify_destructure_struct.sil b/test/SILOptimizer/simplify_destructure_struct.sil new file mode 100644 index 0000000000000..d61cf49b1fe5c --- /dev/null +++ b/test/SILOptimizer/simplify_destructure_struct.sil @@ -0,0 +1,73 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=destructure_struct | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE +// RUN: %target-sil-opt -enable-sil-verify-all %s -simplification -simplify-instruction=destructure_struct | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +struct S { + @_hasStorage let a: String + @_hasStorage let b: Int +} + +// CHECK-LABEL: sil [ossa] @forward_owned : +// CHECK-NOT: destructure_tuple +// CHECK: fix_lifetime %1 +// CHECK: return %0 +// CHECK: } // end sil function 'forward_owned' +sil [ossa] @forward_owned : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = struct $S (%0 : $String, %1 : $Int) + (%3, %4) = destructure_struct %2 : $S + fix_lifetime %4 : $Int + return %3 : $String +} + +// CHECK-LABEL: sil [ossa] @forward_borrowed : +// CHECK-NOT: destructure_struct +// CHECK: fix_lifetime %0 +// CHECK: fix_lifetime %1 +// CHECK: } // end sil function 'forward_borrowed' +sil [ossa] @forward_borrowed : $@convention(thin) (@guaranteed String, Int) -> () { +bb0(%0 : @guaranteed $String, %1 : $Int): + %2 = struct $S (%0 : $String, %1 : $Int) + (%3, %4) = destructure_struct %2 : $S + fix_lifetime %3 : $String + fix_lifetime %4 : $Int + %7 = tuple () + return %7 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_forward_owned_with_uses : +// CHECK: (%5, %6) = destructure_struct %2 : $S +// CHECK: fix_lifetime %6 +// CHECK: return %5 +// CHECK: } // end sil function 'dont_forward_owned_with_uses' +sil [ossa] @dont_forward_owned_with_uses : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = struct $S (%0 : $String, %1 : $Int) + %3 = begin_borrow %2 : $S + end_borrow %3 : $S + (%5, %6) = destructure_struct %2 : $S + fix_lifetime %6 : $Int + return %5 : $String +} + +// CHECK-LABEL: sil [ossa] @forward_owned_with_debug_use : +// CHECK-ONONE: (%4, %5) = destructure_struct %2 : $S +// CHECK-ONONE: fix_lifetime %5 +// CHECK-ONONE: return %4 +// CHECK-O: fix_lifetime %1 +// CHECK-O: return %0 +// CHECK: } // end sil function 'forward_owned_with_debug_use' +sil [ossa] @forward_owned_with_debug_use : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = struct $S (%0 : $String, %1 : $Int) + debug_value %2 : $S, let, name "s" + (%4, %5) = destructure_struct %2 : $S + fix_lifetime %5 : $Int + return %4 : $String +} + + diff --git a/test/SILOptimizer/simplify_destructure_tuple.sil b/test/SILOptimizer/simplify_destructure_tuple.sil new file mode 100644 index 0000000000000..e9f93f73a6e84 --- /dev/null +++ b/test/SILOptimizer/simplify_destructure_tuple.sil @@ -0,0 +1,67 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=destructure_tuple | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE +// RUN: %target-sil-opt -enable-sil-verify-all %s -simplification -simplify-instruction=destructure_tuple | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +// CHECK-LABEL: sil [ossa] @forward_owned : +// CHECK-NOT: destructure_tuple +// CHECK: fix_lifetime %1 +// CHECK: return %0 +// CHECK: } // end sil function 'forward_owned' +sil [ossa] @forward_owned : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0 : $String, %1 : $Int) + (%3, %4) = destructure_tuple %2 : $(String, Int) + fix_lifetime %4 : $Int + return %3 : $String +} + +// CHECK-LABEL: sil [ossa] @forward_borrowed : +// CHECK-NOT: destructure_tuple +// CHECK: fix_lifetime %0 +// CHECK: fix_lifetime %1 +// CHECK: } // end sil function 'forward_borrowed' +sil [ossa] @forward_borrowed : $@convention(thin) (@guaranteed String, Int) -> () { +bb0(%0 : @guaranteed $String, %1 : $Int): + %2 = tuple (%0 : $String, %1 : $Int) + (%3, %4) = destructure_tuple %2 : $(String, Int) + fix_lifetime %3 : $String + fix_lifetime %4 : $Int + %7 = tuple () + return %7 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_forward_owned_with_uses : +// CHECK: (%5, %6) = destructure_tuple %2 : $(String, Int) +// CHECK: fix_lifetime %6 +// CHECK: return %5 +// CHECK: } // end sil function 'dont_forward_owned_with_uses' +sil [ossa] @dont_forward_owned_with_uses : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0 : $String, %1 : $Int) + %3 = begin_borrow %2 : $(String, Int) + end_borrow %3 : $(String, Int) + (%5, %6) = destructure_tuple %2 : $(String, Int) + fix_lifetime %6 : $Int + return %5 : $String +} + +// CHECK-LABEL: sil [ossa] @forward_owned_with_debug_use : +// CHECK-ONONE: (%4, %5) = destructure_tuple %2 : $(String, Int) +// CHECK-ONONE: fix_lifetime %5 +// CHECK-ONONE: return %4 +// CHECK-O: fix_lifetime %1 +// CHECK-O: return %0 +// CHECK: } // end sil function 'forward_owned_with_debug_use' +sil [ossa] @forward_owned_with_debug_use : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0 : $String, %1 : $Int) + debug_value %2 : $(String, Int), let, name "t" + (%4, %5) = destructure_tuple %2 : $(String, Int) + fix_lifetime %5 : $Int + return %4 : $String +} + From 88973a3cd574e80ed3e3681e56ce503fdb1b783e Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 11:56:34 +0200 Subject: [PATCH 07/23] Swift Optimizer: add simplification for `tuple_extract` Replace tuple_extract(tuple(x)) -> x --- .../InstructionSimplification/CMakeLists.txt | 1 + .../SimplifyTupleExtract.swift | 26 +++++++++++++ test/SILOptimizer/simplify_tuple_extract.sil | 39 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyTupleExtract.swift create mode 100644 test/SILOptimizer/simplify_tuple_extract.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt index 9e353cc6887a9..92b933fe36233 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt @@ -18,4 +18,5 @@ swift_compiler_sources(Optimizer SimplifyGlobalValue.swift SimplifyStrongRetainRelease.swift SimplifyStructExtract.swift + SimplifyTupleExtract.swift SimplifyUncheckedEnumData.swift) diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyTupleExtract.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyTupleExtract.swift new file mode 100644 index 0000000000000..ff4e1c58e905a --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyTupleExtract.swift @@ -0,0 +1,26 @@ +//===--- SimplifyTupleExtract.swift ---------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +extension TupleExtractInst : OnoneSimplifyable { + func simplify(_ context: SimplifyContext) { + + // Replace tuple_extract(tuple(x)) -> x + + guard let tupleInst = tuple as? TupleInst else { + return + } + context.tryReplaceRedundantInstructionPair(first: tupleInst, second: self, + with: tupleInst.operands[fieldIndex].value) + } +} diff --git a/test/SILOptimizer/simplify_tuple_extract.sil b/test/SILOptimizer/simplify_tuple_extract.sil new file mode 100644 index 0000000000000..352dd1f713842 --- /dev/null +++ b/test/SILOptimizer/simplify_tuple_extract.sil @@ -0,0 +1,39 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=tuple_extract | %FileCheck %s + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +// CHECK-LABEL: sil @tuple_extract_first_field +// CHECK: return %0 +// CHECK: } // end sil function 'tuple_extract_first_field' +sil @tuple_extract_first_field : $@convention(thin) (@owned String, @guaranteed String) -> @owned String { +bb0(%0 : $String, %1 : $String): + %2 = tuple (%0 : $String, %1 : $String) + %3 = tuple_extract %2 : $(String, String), 0 + return %3 : $String +} + +// CHECK-LABEL: sil @tuple_extract_second_field +// CHECK: return %1 +// CHECK: } // end sil function 'tuple_extract_second_field' +sil @tuple_extract_second_field : $@convention(thin) (@guaranteed String, @owned String) -> @owned String { +bb0(%0 : $String, %1 : $String): + %2 = tuple (%0 : $String, %1 : $String) + %3 = tuple_extract %2 : $(String, String), 1 + return %3 : $String +} + +// CHECK-LABEL: sil [ossa] @tuple_extract_ossa +// CHECK: %2 = copy_value %0 +// CHECK: return %2 +// CHECK: } // end sil function 'tuple_extract_ossa' +sil [ossa] @tuple_extract_ossa : $@convention(thin) (@guaranteed String, @guaranteed String) -> @owned String { +bb0(%0 : @guaranteed $String, %1 : @guaranteed $String): + %2 = tuple (%0 : $String, %1 : $String) + %3 = tuple_extract %2 : $(String, String), 0 + %4 = copy_value %3 : $String + return %4 : $String +} + From 9b51e69daca483c5ac4476cd302e8bcf94ff5c6b Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 12:08:10 +0200 Subject: [PATCH 08/23] Swift Optimizer: constant fold builtins in the simplification passes --- .../SimplifyBuiltin.swift | 5 +++-- .../Sources/Optimizer/PassManager/Context.swift | 6 ++++++ SwiftCompilerSources/Sources/SIL/Value.swift | 2 +- include/swift/SILOptimizer/OptimizerBridging.h | 3 +++ .../swift/SILOptimizer/Utils/ConstantFolding.h | 4 ++++ lib/SILOptimizer/PassManager/PassManager.cpp | 7 +++++++ lib/SILOptimizer/Utils/ConstantFolding.cpp | 4 ++-- test/SILOptimizer/simplify_builtin.sil | 17 +++++++++++++++++ 8 files changed, 43 insertions(+), 5 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift index d9e5f74865db1..81f37c930f0b5 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift @@ -23,8 +23,9 @@ extension BuiltinInst : OnoneSimplifyable { case .IsSameMetatype: optimizeIsSameMetatype(context) default: - // TODO: handle other builtin types - break + if let literal = constantFold(context) { + uses.replaceAll(with: literal, context) + } } } } diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index d7ac51a5dc223..601c06b5fe13b 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -283,6 +283,12 @@ extension Instruction { } } +extension BuiltinInst { + func constantFold(_ context: some MutatingContext) -> Value? { + context._bridged.constantFoldBuiltin(bridged).value + } +} + extension RefCountingInst { func setAtomicity(isAtomic: Bool, _ context: some MutatingContext) { context.notifyInstructionsChanged() diff --git a/SwiftCompilerSources/Sources/SIL/Value.swift b/SwiftCompilerSources/Sources/SIL/Value.swift index 3abbaa7c294d3..477c9019f2e5c 100644 --- a/SwiftCompilerSources/Sources/SIL/Value.swift +++ b/SwiftCompilerSources/Sources/SIL/Value.swift @@ -202,5 +202,5 @@ final class PlaceholderValue : Value { } extension OptionalBridgedValue { - var value: Value? { obj.getAs(AnyObject.self) as? Value } + public var value: Value? { obj.getAs(AnyObject.self) as? Value } } diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index d7c440607844d..c91595e9f7553 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -202,6 +202,9 @@ struct BridgedPassContext { bool tryDeleteDeadClosure(BridgedInstruction closure) const; + SWIFT_IMPORT_UNSAFE + OptionalBridgedValue constantFoldBuiltin(BridgedInstruction builtin) const; + SWIFT_IMPORT_UNSAFE BridgedValue getSILUndef(swift::SILType type) const { return {swift::SILUndef::get(type, *invocation->getFunction())}; diff --git a/include/swift/SILOptimizer/Utils/ConstantFolding.h b/include/swift/SILOptimizer/Utils/ConstantFolding.h index 0aadf1adb580c..8f925c3b8de98 100644 --- a/include/swift/SILOptimizer/Utils/ConstantFolding.h +++ b/include/swift/SILOptimizer/Utils/ConstantFolding.h @@ -52,6 +52,10 @@ APInt constantFoldDiv(APInt lhs, APInt rhs, bool &Overflow, BuiltinValueKind ID) /// The \p ID must be the ID of a trunc/sext/zext builtin. APInt constantFoldCast(APInt val, const BuiltinInfo &BI); +/// If `ResultsInError` is not none than errors are diagnosed and +/// `ResultsInError` is set to true in case of an error. +SILValue constantFoldBuiltin(BuiltinInst *BI, + Optional &ResultsInError); /// A utility class to do constant folding. class ConstantFolder { diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index b49497166b3e2..2000b284fcbc2 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -27,6 +27,7 @@ #include "swift/SILOptimizer/OptimizerBridging.h" #include "swift/SILOptimizer/PassManager/PrettyStackTrace.h" #include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/ConstantFolding.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/OptimizerStatsUtils.h" #include "swift/SILOptimizer/Utils/StackNesting.h" @@ -1420,6 +1421,12 @@ bool BridgedPassContext::tryDeleteDeadClosure(BridgedInstruction closure) const return ::tryDeleteDeadClosure(closure.getAs(), InstModCallbacks()); } +OptionalBridgedValue BridgedPassContext::constantFoldBuiltin(BridgedInstruction builtin) const { + auto bi = builtin.getAs(); + Optional resultsInError; + return {::constantFoldBuiltin(bi, resultsInError)}; +} + void BridgedPassContext::fixStackNesting(BridgedFunction function) const { switch (StackNesting::fixNesting(function.getFunction())) { case StackNesting::Changes::None: diff --git a/lib/SILOptimizer/Utils/ConstantFolding.cpp b/lib/SILOptimizer/Utils/ConstantFolding.cpp index 72de97f12159c..fb95da66ac573 100644 --- a/lib/SILOptimizer/Utils/ConstantFolding.cpp +++ b/lib/SILOptimizer/Utils/ConstantFolding.cpp @@ -566,7 +566,7 @@ static SILValue constantFoldBinary(BuiltinInst *BI, Optional &ResultsInError) { switch (ID) { default: - llvm_unreachable("Not all BUILTIN_BINARY_OPERATIONs are covered!"); + return nullptr; // Not supported yet (not easily computable for APInt). case BuiltinValueKind::ExactSDiv: @@ -1134,7 +1134,7 @@ static SILValue constantFoldIsConcrete(BuiltinInst *BI) { return inst; } -static SILValue constantFoldBuiltin(BuiltinInst *BI, +SILValue swift::constantFoldBuiltin(BuiltinInst *BI, Optional &ResultsInError) { const IntrinsicInfo &Intrinsic = BI->getIntrinsicInfo(); SILModule &M = BI->getModule(); diff --git a/test/SILOptimizer/simplify_builtin.sil b/test/SILOptimizer/simplify_builtin.sil index b2385d9552335..294dd8606a8da 100644 --- a/test/SILOptimizer/simplify_builtin.sil +++ b/test/SILOptimizer/simplify_builtin.sil @@ -24,6 +24,23 @@ class C1 { class C2 : C1 { } +// CHECK-LABEL: sil @constantFoldAdd +// CHECK: [[A:%.*]] = integer_literal $Builtin.Int64, 12 +// CHECK: [[C:%.*]] = integer_literal $Builtin.Int1, 0 +// CHECK: [[T:%.*]] = tuple ([[A]] : $Builtin.Int64, [[C]] : $Builtin.Int1) +// CHECK: [[R:%.*]] = tuple_extract [[T]] : $(Builtin.Int64, Builtin.Int1), 0 +// CHECK: return [[R]] +// CHECK: } // end sil function 'constantFoldAdd' +sil @constantFoldAdd : $@convention(thin) () -> Builtin.Int64 { +bb0: + %0 = integer_literal $Builtin.Int64, 10 + %1 = integer_literal $Builtin.Int64, 2 + %2 = integer_literal $Builtin.Int1, 1 + %3 = builtin "sadd_with_overflow_Int64"(%0 : $Builtin.Int64, %1 : $Builtin.Int64, %2 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %4 = tuple_extract %3 : $(Builtin.Int64, Builtin.Int1), 0 + return %4 : $Builtin.Int64 +} + // CHECK-LABEL: sil @isConcrete_true // CHECK: bb0(%0 : $@thin Int.Type): // CHECK: [[R:%.*]] = integer_literal $Builtin.Int1, -1 From c4096bc723c7c17b5b89be3bcb314dd2c050ecc1 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 14:05:57 +0200 Subject: [PATCH 09/23] Swift Optimizer: simplify `builtin "once"` If the callee is side effect-free we can remove the whole builtin "once". --- .../SimplifyBuiltin.swift | 25 ++++++++ test/SILOptimizer/simplify_builtin.sil | 58 +++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift index 81f37c930f0b5..17faaf0d6144c 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift @@ -22,6 +22,8 @@ extension BuiltinInst : OnoneSimplifyable { optimizeIsConcrete(allowArchetypes: false, context) case .IsSameMetatype: optimizeIsSameMetatype(context) + case .Once: + optimizeBuiltinOnce(context) default: if let literal = constantFold(context) { uses.replaceAll(with: literal, context) @@ -65,6 +67,29 @@ private extension BuiltinInst { uses.replaceAll(with: result, context) } + + func optimizeBuiltinOnce(_ context: SimplifyContext) { + guard let callee = calleeOfOnce, callee.isDefinition else { + return + } + // If the callee is side effect-free we can remove the whole builtin "once". + // We don't use the callee's memory effects but instead look at all callee instructions + // because memory effects are not computed in the Onone pipeline, yet. + // This is no problem because the callee (usually a global init function )is mostly very small, + // or contains the side-effect instruction `alloc_global` right at the beginning. + if callee.instructions.contains(where: { $0.mayReadOrWriteMemory || $0.hasUnspecifiedSideEffects }) { + return + } + context.erase(instruction: self) + } + + var calleeOfOnce: Function? { + let callee = operands[1].value + if let fri = callee as? FunctionRefInst { + return fri.referencedFunction + } + return nil + } } private func typesOfValuesAreEqual(_ lhs: Value, _ rhs: Value, in function: Function) -> Bool? { diff --git a/test/SILOptimizer/simplify_builtin.sil b/test/SILOptimizer/simplify_builtin.sil index 294dd8606a8da..4d7dbf01037c9 100644 --- a/test/SILOptimizer/simplify_builtin.sil +++ b/test/SILOptimizer/simplify_builtin.sil @@ -264,3 +264,61 @@ bb0: %4 = builtin "is_same_metatype"(%2 : $@thick Any.Type, %3 : $@thick Any.Type) : $Builtin.Int1 return %4 : $Builtin.Int1 } + +sil_global hidden [let] @g : $Int32 + +sil [global_init_once_fn] @side_effect_free_init : $@convention(c) () -> () { +bb0: + %1 = global_addr @g : $*Int32 + %2 = integer_literal $Builtin.Int32, 10 + %3 = struct $Int32 (%2 : $Builtin.Int32) + %6 = tuple () + return %6 : $() +} + +sil [global_init_once_fn] @init_with_side_effect : $@convention(c) () -> () { +bb0: + alloc_global @g + %1 = global_addr @g : $*Int32 + %2 = integer_literal $Builtin.Int32, 10 + %3 = struct $Int32 (%2 : $Builtin.Int32) + store %3 to %1 : $*Int32 + %6 = tuple () + return %6 : $() +} + +sil [global_init_once_fn] @unknown_init : $@convention(c) () -> () + +// CHECK-LABEL: sil @remove_builtin_once : +// CHECK-NOT: builtin +// CHECK: } // end sil function 'remove_builtin_once' +sil @remove_builtin_once : $@convention(thin) (Builtin.RawPointer) -> () { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @side_effect_free_init : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $() + %3 = tuple () + return %3 : $() +} + +// CHECK-LABEL: sil @dont_remove_builtin_once_side_effect : +// CHECK: builtin +// CHECK: } // end sil function 'dont_remove_builtin_once_side_effect' +sil @dont_remove_builtin_once_side_effect : $@convention(thin) (Builtin.RawPointer) -> () { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @init_with_side_effect : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $() + %3 = tuple () + return %3 : $() +} + +// CHECK-LABEL: sil @dont_remove_builtin_once_unknown : +// CHECK: builtin +// CHECK: } // end sil function 'dont_remove_builtin_once_unknown' +sil @dont_remove_builtin_once_unknown : $@convention(thin) (Builtin.RawPointer) -> () { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @unknown_init : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $() + %3 = tuple () + return %3 : $() +} + From e95c6425f2849f4cc480514219135c77d9e2f843 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 12:40:51 +0200 Subject: [PATCH 10/23] Swift SIL: add some APIs for global variables --- .../PassManager/ModulePassContext.swift | 24 +++++++++++ .../Optimizer/Utilities/OptUtils.swift | 13 ++++++ .../Sources/SIL/Function.swift | 12 ++++++ .../Sources/SIL/GlobalVariable.swift | 30 ++++++++++++- .../Sources/SIL/Instruction.swift | 10 ++++- .../Sources/SIL/Registration.swift | 1 + include/swift/SIL/SILBridging.h | 43 +++++++++++++++++++ .../swift/SILOptimizer/OptimizerBridging.h | 17 ++++++++ lib/SIL/Utils/SILBridging.cpp | 12 ++++++ 9 files changed, 159 insertions(+), 3 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift index 42b607a81f5a9..f8d8b5510dff9 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift @@ -35,6 +35,20 @@ struct ModulePassContext : Context { } } + struct GlobalVariableList : CollectionLikeSequence, IteratorProtocol { + private var currentGlobal: GlobalVariable? + + fileprivate init(first: GlobalVariable?) { currentGlobal = first } + + mutating func next() -> GlobalVariable? { + if let g = currentGlobal { + currentGlobal = BridgedPassContext.getNextGlobalInModule(g.bridged).globalVar + return g + } + return nil + } + } + struct VTableArray : BridgedRandomAccessCollection { fileprivate let bridged: BridgedPassContext.VTableArray @@ -79,6 +93,10 @@ struct ModulePassContext : Context { FunctionList(first: _bridged.getFirstFunctionInModule().function) } + var globalVariables: GlobalVariableList { + GlobalVariableList(first: _bridged.getFirstGlobalInModule().globalVar) + } + var vTables: VTableArray { VTableArray(bridged: _bridged.getVTables()) } @@ -101,3 +119,9 @@ struct ModulePassContext : Context { _bridged.endTransformFunction(); } } + +extension GlobalVariable { + func setIsLet(to value: Bool, _ context: ModulePassContext) { + bridged.setLet(value) + } +} diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift index 3733421d90b1c..a6e30f0170e8b 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift @@ -253,3 +253,16 @@ private struct EscapesToValueVisitor : EscapeVisitor { var followTrivialTypes: Bool { true } var followLoads: Bool { false } } + +extension Function { + var globalOfGlobalInitFunction: GlobalVariable? { + if isGlobalInitFunction, + let ret = returnInstruction, + let atp = ret.returnedValue as? AddressToPointerInst, + let ga = atp.address as? GlobalAddrInst { + return ga.global + } + return nil + } +} + diff --git a/SwiftCompilerSources/Sources/SIL/Function.swift b/SwiftCompilerSources/Sources/SIL/Function.swift index 9e86d62ba7c57..b5e76f7b0985f 100644 --- a/SwiftCompilerSources/Sources/SIL/Function.swift +++ b/SwiftCompilerSources/Sources/SIL/Function.swift @@ -122,6 +122,18 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash /// This means that the function terminates the program. public var isProgramTerminationPoint: Bool { hasSemanticsAttribute("programtermination_point") } + /// True if this is a `[global_init]` function. + /// + /// Such a function is typically a global addressor which calls the global's + /// initializer (`[global_init_once_fn]`) via a `builtin "once"`. + public var isGlobalInitFunction: Bool { bridged.isGlobalInitFunction() } + + /// True if this is a `[global_init_once_fn]` function. + /// + /// Such a function allocates a global and stores the global's init value. + /// It's called from a `[global_init]` function via a `builtin "once"`. + public var isGlobalInitOnceFunction: Bool { bridged.isGlobalInitOnceFunction() } + /// Kinds of effect attributes which can be defined for a Swift function. public enum EffectAttribute { /// No effect attribute is specified. diff --git a/SwiftCompilerSources/Sources/SIL/GlobalVariable.swift b/SwiftCompilerSources/Sources/SIL/GlobalVariable.swift index b894bd9a531a9..7edbc8523464e 100644 --- a/SwiftCompilerSources/Sources/SIL/GlobalVariable.swift +++ b/SwiftCompilerSources/Sources/SIL/GlobalVariable.swift @@ -27,7 +27,23 @@ final public class GlobalVariable : CustomStringConvertible, HasShortDescription public var isLet: Bool { bridged.isLet() } - // TODO: initializer instructions + /// True, if the linkage of the global variable indicates that it is visible outside the current + /// compilation unit and therefore not all of its uses are known. + /// + /// For example, `public` linkage. + public var isPossiblyUsedExternally: Bool { + return bridged.isPossiblyUsedExternally() + } + + public var staticInitValue: SingleValueInstruction? { + bridged.getStaticInitializerValue().instruction as? SingleValueInstruction + } + + /// True if the global's linkage and resilience expansion allow the global + /// to be initialized statically. + public var canBeInitializedStatically: Bool { + return bridged.canBeInitializedStatically() + } public static func ==(lhs: GlobalVariable, rhs: GlobalVariable) -> Bool { lhs === rhs @@ -37,7 +53,13 @@ final public class GlobalVariable : CustomStringConvertible, HasShortDescription hasher.combine(ObjectIdentifier(self)) } - var bridged: BridgedGlobalVar { BridgedGlobalVar(obj: SwiftObject(self)) } + public var bridged: BridgedGlobalVar { BridgedGlobalVar(obj: SwiftObject(self)) } +} + +extension Instruction { + public var isValidInStaticInitializerOfGlobal: Bool { + return BridgedGlobalVar.isValidStaticInitializer(bridged) + } } // Bridging utilities @@ -45,3 +67,7 @@ final public class GlobalVariable : CustomStringConvertible, HasShortDescription extension BridgedGlobalVar { var globalVar: GlobalVariable { obj.getAs(GlobalVariable.self) } } + +extension OptionalBridgedGlobalVar { + public var globalVar: GlobalVariable? { obj.getAs(GlobalVariable.self) } +} diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index 4133d29fbc0a4..adecf096d2434 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -141,7 +141,7 @@ extension BridgedInstruction { } extension OptionalBridgedInstruction { - var instruction: Instruction? { obj.getAs(Instruction.self) } + public var instruction: Instruction? { obj.getAs(Instruction.self) } } public class SingleValueInstruction : Instruction, Value { @@ -462,6 +462,12 @@ final public class GlobalAddrInst : GlobalAccessInst {} final public class GlobalValueInst : GlobalAccessInst {} +final public class AllocGlobalInst : Instruction { + public var global: GlobalVariable { + bridged.AllocGlobalInst_getGlobal().globalVar + } +} + final public class IntegerLiteralInst : SingleValueInstruction { public var value: llvm.APInt { bridged.IntegerLiteralInst_getValue() } } @@ -599,6 +605,8 @@ final public class BeginAccessInst : SingleValueInstruction, UnaryInstruction { public var accessKind: AccessKind { bridged.BeginAccessInst_getAccessKind() } public var isStatic: Bool { bridged.BeginAccessInst_isStatic() } + + public var address: Value { operand.value } } // An instruction that is always paired with a scope ending instruction diff --git a/SwiftCompilerSources/Sources/SIL/Registration.swift b/SwiftCompilerSources/Sources/SIL/Registration.swift index cfdb890b9827f..d9f17d08c8708 100644 --- a/SwiftCompilerSources/Sources/SIL/Registration.swift +++ b/SwiftCompilerSources/Sources/SIL/Registration.swift @@ -95,6 +95,7 @@ public func registerSILClasses() { register(PreviousDynamicFunctionRefInst.self) register(GlobalAddrInst.self) register(GlobalValueInst.self) + register(AllocGlobalInst.self) register(IntegerLiteralInst.self) register(StringLiteralInst.self) register(TupleInst.self) diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index b1c52ff2cab35..a87c20da04709 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -34,6 +34,7 @@ SWIFT_BEGIN_NULLABILITY_ANNOTATIONS struct BridgedInstruction; +struct OptionalBridgedInstruction; struct OptionalBridgedOperand; struct OptionalBridgedSuccessor; struct BridgedBasicBlock; @@ -236,6 +237,14 @@ struct BridgedFunction { return getFunction()->isAvailableExternally(); } + bool isGlobalInitFunction() const { + return getFunction()->isGlobalInit(); + } + + bool isGlobalInitOnceFunction() const { + return getFunction()->isGlobalInitOnceFunction(); + } + bool hasSemanticsAttr(llvm::StringRef attrName) const { return getFunction()->hasSemanticsAttr(attrName) ? 1 : 0; } @@ -307,6 +316,23 @@ struct BridgedGlobalVar { llvm::StringRef getName() const { return getGlobal()->getName(); } bool isLet() const { return getGlobal()->isLet(); } + + void setLet(bool value) const { getGlobal()->setLet(value); } + + bool isPossiblyUsedExternally() const { + return getGlobal()->isPossiblyUsedExternally(); + } + + SWIFT_IMPORT_UNSAFE + inline OptionalBridgedInstruction getStaticInitializerValue() const; + + bool canBeInitializedStatically() const; + + static inline bool isValidStaticInitializer(BridgedInstruction inst); +}; + +struct OptionalBridgedGlobalVar { + OptionalSwiftObject obj; }; struct BridgedMultiValueResult { @@ -444,6 +470,11 @@ struct BridgedInstruction { return {getAs()->getReferencedGlobal()}; } + SWIFT_IMPORT_UNSAFE + BridgedGlobalVar AllocGlobalInst_getGlobal() const { + return {getAs()->getReferencedGlobal()}; + } + SWIFT_IMPORT_UNSAFE BridgedFunction FunctionRefBaseInst_getReferencedFunction() const { return {getAs()->getInitiallyReferencedFunction()}; @@ -1100,6 +1131,18 @@ OptionalBridgedBasicBlock BridgedFunction::getLastBlock() const { return {getFunction()->empty() ? nullptr : &*getFunction()->rbegin()}; } +OptionalBridgedInstruction BridgedGlobalVar::getStaticInitializerValue() const { + if (swift::SILInstruction *inst = getGlobal()->getStaticInitializerValue()) { + return {inst->asSILNode()}; + } + return {nullptr}; +} + +bool BridgedGlobalVar::isValidStaticInitializer(BridgedInstruction inst) { + swift::SILInstruction *i = inst.getInst(); + return swift::SILGlobalVariable::isValidStaticInitializerInst(i, i->getModule()); +} + BridgedInstruction BridgedMultiValueResult::getParent() const { return {getMVResult()->getParent()}; } diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index c91595e9f7553..568996bde7a5c 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -304,6 +304,23 @@ struct BridgedPassContext { return {&*nextIter}; } + SWIFT_IMPORT_UNSAFE + OptionalBridgedGlobalVar getFirstGlobalInModule() const { + swift::SILModule *mod = invocation->getPassManager()->getModule(); + if (mod->getSILGlobals().empty()) + return {nullptr}; + return {&*mod->getSILGlobals().begin()}; + } + + SWIFT_IMPORT_UNSAFE + static OptionalBridgedGlobalVar getNextGlobalInModule(BridgedGlobalVar global) { + auto *g = global.getGlobal(); + auto nextIter = std::next(g->getIterator()); + if (nextIter == g->getModule().getSILGlobals().end()) + return {nullptr}; + return {&*nextIter}; + } + struct VTableArray { swift::SILVTable * const _Nonnull * _Nullable base; SwiftInt count; diff --git a/lib/SIL/Utils/SILBridging.cpp b/lib/SIL/Utils/SILBridging.cpp index 0317552554642..fa6367535188b 100644 --- a/lib/SIL/Utils/SILBridging.cpp +++ b/lib/SIL/Utils/SILBridging.cpp @@ -196,6 +196,18 @@ std::string BridgedGlobalVar::getDebugDescription() const { return str; } +bool BridgedGlobalVar::canBeInitializedStatically() const { + SILGlobalVariable *global = getGlobal(); + auto expansion = ResilienceExpansion::Maximal; + if (hasPublicVisibility(global->getLinkage())) + expansion = ResilienceExpansion::Minimal; + + auto &tl = global->getModule().Types.getTypeLowering( + global->getLoweredType(), + TypeExpansionContext::noOpaqueTypeArchetypesSubstitution(expansion)); + return tl.isLoadable(); +} + //===----------------------------------------------------------------------===// // SILVTable //===----------------------------------------------------------------------===// From 3e04b8681c56590b513b4e4fdac384d39897993c Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:13:19 +0200 Subject: [PATCH 11/23] make ModulePassContext conform to CustomStringConvertible and let its description return a dump of the whole module. This is useful for debugging --- .../Sources/Optimizer/PassManager/ModulePassContext.swift | 7 ++++++- include/swift/SILOptimizer/OptimizerBridging.h | 2 ++ lib/SILOptimizer/PassManager/PassManager.cpp | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift index f8d8b5510dff9..557ba9f83663b 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/ModulePassContext.swift @@ -18,9 +18,14 @@ import OptimizerBridging /// It provides access to all functions, v-tables and witness tables of a module, /// but it doesn't provide any APIs to modify functions. /// In order to modify a function, a module pass must use `transform(function:)`. -struct ModulePassContext : Context { +struct ModulePassContext : Context, CustomStringConvertible { let _bridged: BridgedPassContext + public var description: String { + let stdString = _bridged.getModuleDescription() + return String(_cxxString: stdString) + } + struct FunctionList : CollectionLikeSequence, IteratorProtocol { private var currentFunction: Function? diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index 568996bde7a5c..674ffb9cfb643 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -149,6 +149,8 @@ struct BridgedPostDomTree { struct BridgedPassContext { swift::SwiftPassInvocation * _Nonnull invocation; + std::string getModuleDescription() const; + SWIFT_IMPORT_UNSAFE BridgedChangeNotificationHandler asNotificationHandler() const { return {invocation}; diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index 2000b284fcbc2..dacb103e480e2 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -1417,6 +1417,14 @@ void BridgedChangeNotificationHandler::notifyChanges(Kind changeKind) const { } } +std::string BridgedPassContext::getModuleDescription() const { + std::string str; + llvm::raw_string_ostream os(str); + invocation->getPassManager()->getModule()->print(os); + str.pop_back(); // Remove trailing newline. + return str; +} + bool BridgedPassContext::tryDeleteDeadClosure(BridgedInstruction closure) const { return ::tryDeleteDeadClosure(closure.getAs(), InstModCallbacks()); } From 260e68b1025e58dd96b2f208902b5ab349ea6b70 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:09:04 +0200 Subject: [PATCH 12/23] Passmanager: fix a problem with skipping the inliner pass A pass is skipped if no other pass changed the function since the previous run of the same pass. Don't do this is if a pass depends on the function bodies of called functions, e.g. the inliner. Other passes might change the callees, e.g. function signature opts, which makes it worth to run the inliner again, even if the function itself didn't change. --- include/swift/SILOptimizer/PassManager/PassManager.h | 6 ++++++ lib/SILOptimizer/PassManager/PassManager.cpp | 3 ++- lib/SILOptimizer/Transforms/PerformanceInliner.cpp | 12 ++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/swift/SILOptimizer/PassManager/PassManager.h b/include/swift/SILOptimizer/PassManager/PassManager.h index 70869f44302e4..bcc4647b1c083 100644 --- a/include/swift/SILOptimizer/PassManager/PassManager.h +++ b/include/swift/SILOptimizer/PassManager/PassManager.h @@ -203,6 +203,8 @@ class SILPassManager { /// Set to true when a pass invalidates an analysis. bool CurrentPassHasInvalidated = false; + bool currentPassDependsOnCalleeBodies = false; + /// True if we need to stop running passes and restart again on the /// same function. bool RestartPipeline = false; @@ -337,6 +339,10 @@ class SILPassManager { CompletedPassesMap[F].reset(); } + void setDependingOnCalleeBodies() { + currentPassDependsOnCalleeBodies = true; + } + /// Reset the state of the pass manager and remove all transformation /// owned by the pass manager. Analysis passes will be kept. void resetAndRemoveTransformations(); diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index dacb103e480e2..f1eb54eb15fca 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -551,6 +551,7 @@ void SILPassManager::runPassOnFunction(unsigned TransIdx, SILFunction *F) { updateSILModuleStatsBeforeTransform(F->getModule(), SFT, *this, NumPassesRun); CurrentPassHasInvalidated = false; + currentPassDependsOnCalleeBodies = false; numSubpassesRun = 0; auto MatchFun = [&](const std::string &Str) -> bool { @@ -652,7 +653,7 @@ void SILPassManager::runPassOnFunction(unsigned TransIdx, SILFunction *F) { duration.count()); // Remember if this pass didn't change anything. - if (!CurrentPassHasInvalidated) + if (!CurrentPassHasInvalidated && !currentPassDependsOnCalleeBodies) completedPasses.set((size_t)SFT->getPassKind()); if (getOptions().VerifyAll && diff --git a/lib/SILOptimizer/Transforms/PerformanceInliner.cpp b/lib/SILOptimizer/Transforms/PerformanceInliner.cpp index 2412d738ad809..7f34c49a222d1 100644 --- a/lib/SILOptimizer/Transforms/PerformanceInliner.cpp +++ b/lib/SILOptimizer/Transforms/PerformanceInliner.cpp @@ -100,6 +100,7 @@ class SILPerformanceInliner { /// global_init attributes. InlineSelection WhatToInline; + SILPassManager *pm; DominanceAnalysis *DA; SILLoopAnalysis *LA; BasicCalleeAnalysis *BCA; @@ -227,11 +228,12 @@ class SILPerformanceInliner { public: SILPerformanceInliner(StringRef PassName, SILOptFunctionBuilder &FuncBuilder, - InlineSelection WhatToInline, DominanceAnalysis *DA, + InlineSelection WhatToInline, + SILPassManager *pm, DominanceAnalysis *DA, SILLoopAnalysis *LA, BasicCalleeAnalysis *BCA, OptimizationMode OptMode, OptRemark::Emitter &ORE) : PassName(PassName), FuncBuilder(FuncBuilder), - WhatToInline(WhatToInline), DA(DA), LA(LA), BCA(BCA), CBI(DA), ORE(ORE), + WhatToInline(WhatToInline), pm(pm), DA(DA), LA(LA), BCA(BCA), CBI(DA), ORE(ORE), OptMode(OptMode) {} bool inlineCallsIntoFunction(SILFunction *F); @@ -934,6 +936,8 @@ void SILPerformanceInliner::collectAppliesToInline( if (!FullApplySite::isa(&*I)) continue; + pm->setDependingOnCalleeBodies(); + FullApplySite AI = FullApplySite(&*I); auto *Callee = getEligibleFunction(AI, WhatToInline); @@ -1155,8 +1159,8 @@ class SILPerformanceInlinerPass : public SILFunctionTransform { SILOptFunctionBuilder FuncBuilder(*this); - SILPerformanceInliner Inliner(getID(), FuncBuilder, WhatToInline, DA, LA, - BCA, OptMode, ORE); + SILPerformanceInliner Inliner(getID(), FuncBuilder, WhatToInline, + getPassManager(), DA, LA, BCA, OptMode, ORE); assert(getFunction()->isDefinition() && "Expected only functions with bodies!"); From 47a7acd4a70a778bbc0d04f117f162542797d817 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:21:32 +0200 Subject: [PATCH 13/23] LICM: hoist `builtin "once"` calls out of loops `builtin "once"` calls are a result of inlining global accessors --- lib/SILOptimizer/LoopTransforms/LICM.cpp | 30 ++++++++++--- test/SILOptimizer/licm.sil | 56 ++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/lib/SILOptimizer/LoopTransforms/LICM.cpp b/lib/SILOptimizer/LoopTransforms/LICM.cpp index 4b39ff86af810..a02c081b7e7ed 100644 --- a/lib/SILOptimizer/LoopTransforms/LICM.cpp +++ b/lib/SILOptimizer/LoopTransforms/LICM.cpp @@ -224,7 +224,7 @@ static bool mayWriteTo(AliasAnalysis *AA, BasicCalleeAnalysis *BCA, /// Returns true if \p sideEffectInst cannot be reordered with a call to a /// global initializer. static bool mayConflictWithGlobalInit(AliasAnalysis *AA, - SILInstruction *sideEffectInst, ApplyInst *globalInitCall) { + SILInstruction *sideEffectInst, SILInstruction *globalInitCall) { if (auto *SI = dyn_cast(sideEffectInst)) { return AA->mayReadOrWriteMemory(globalInitCall, SI->getDest()); } @@ -239,7 +239,7 @@ static bool mayConflictWithGlobalInit(AliasAnalysis *AA, /// the call. static bool mayConflictWithGlobalInit(AliasAnalysis *AA, InstSet &sideEffectInsts, - ApplyInst *globalInitCall, + SILInstruction *globalInitCall, SILBasicBlock *preHeader, PostDominanceInfo *PD) { if (!PD->dominates(globalInitCall->getParent(), preHeader)) return true; @@ -263,7 +263,7 @@ static bool mayConflictWithGlobalInit(AliasAnalysis *AA, /// block). static bool mayConflictWithGlobalInit(AliasAnalysis *AA, ArrayRef sideEffectInsts, - ApplyInst *globalInitCall) { + SILInstruction *globalInitCall) { for (auto *seInst : sideEffectInsts) { assert(seInst->getParent() == globalInitCall->getParent()); if (mayConflictWithGlobalInit(AA, seInst, globalInitCall)) @@ -864,7 +864,12 @@ void LoopTreeOptimization::analyzeCurrentLoop( // Interesting instructions in the loop: SmallVector ReadOnlyApplies; - SmallVector globalInitCalls; + + // Contains either: + // * an apply to the addressor of the global + // * a builtin "once" of the global initializer + SmallVector globalInitCalls; + SmallVector Loads; SmallVector Stores; SmallVector FixLifetimes; @@ -918,8 +923,8 @@ void LoopTreeOptimization::analyzeCurrentLoop( // Check against side-effects within the same block. // Side-effects in other blocks are checked later (after we // scanned all blocks of the loop). - !mayConflictWithGlobalInit(AA, sideEffectsInBlock, AI)) - globalInitCalls.push_back(AI); + !mayConflictWithGlobalInit(AA, sideEffectsInBlock, &Inst)) + globalInitCalls.push_back(&Inst); } // check for array semantics and side effects - same as default LLVM_FALLTHROUGH; @@ -927,7 +932,18 @@ void LoopTreeOptimization::analyzeCurrentLoop( default: if (auto fullApply = FullApplySite::isa(&Inst)) { fullApplies.push_back(fullApply); + } else if (auto *bi = dyn_cast(&Inst)) { + switch (bi->getBuiltinInfo().ID) { + case BuiltinValueKind::Once: + case BuiltinValueKind::OnceWithContext: + if (!mayConflictWithGlobalInit(AA, sideEffectsInBlock, &Inst)) + globalInitCalls.push_back(&Inst); + break; + default: + break; + } } + checkSideEffects(Inst, sideEffects, sideEffectsInBlock); if (canHoistUpDefault(&Inst, Loop, DomTree, RunsOnHighLevelSIL)) { HoistUp.insert(&Inst); @@ -959,7 +975,7 @@ void LoopTreeOptimization::analyzeCurrentLoop( postDomTree = PDA->get(Preheader->getParent()); } if (postDomTree->getRootNode()) { - for (ApplyInst *ginitCall : globalInitCalls) { + for (SILInstruction *ginitCall : globalInitCalls) { // Check against side effects which are "before" (i.e. post-dominated // by) the global initializer call. if (!mayConflictWithGlobalInit(AA, sideEffects, ginitCall, Preheader, diff --git a/test/SILOptimizer/licm.sil b/test/SILOptimizer/licm.sil index 48242375e7db9..a6911d5169180 100644 --- a/test/SILOptimizer/licm.sil +++ b/test/SILOptimizer/licm.sil @@ -1474,3 +1474,59 @@ bb3: %21 = tuple () return %21 : $() } + +sil_global private @g_token : $Builtin.Word +sil_global @gi : $Int + +sil @g_init : $@convention(c) () -> () + +// CHECK-LABEL: sil @hoist_builtin_once : +// CHECK: function_ref +// CHECK: builtin "once" +// CHECK: br bb1 +// CHECK: bb1: +// CHECK-NOT: builtin +// CHECK: cond_br +// CHECK: } // end sil function 'hoist_builtin_once' +sil @hoist_builtin_once : $@convention(thin) () -> () { +bb0: + br bb1 + +bb1: + %1 = global_addr @g_token : $*Builtin.Word + %2 = address_to_pointer %1 : $*Builtin.Word to $Builtin.RawPointer + %3 = function_ref @g_init : $@convention(c) () -> () + %4 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() + cond_br undef, bb1, bb2 + +bb2: + %r1 = tuple () + return %r1 : $() +} + +// CHECK-LABEL: sil @dont_hoist_builtin_once_memory_conflict : +// CHECK: function_ref +// CHECK: br bb1 +// CHECK: bb1: +// CHECK: builtin "once" +// CHECK: cond_br +// CHECK: } // end sil function 'dont_hoist_builtin_once_memory_conflict' +sil @dont_hoist_builtin_once_memory_conflict : $@convention(thin) (Int) -> () { +bb0(%0 : $Int): + br bb1 + +bb1: + %1 = global_addr @gi : $*Int + store %0 to %1 : $*Int + %3 = global_addr @g_token : $*Builtin.Word + %4 = address_to_pointer %3 : $*Builtin.Word to $Builtin.RawPointer + %5 = function_ref @g_init : $@convention(c) () -> () + %6 = builtin "once"(%4 : $Builtin.RawPointer, %5 : $@convention(c) () -> ()) : $() + cond_br undef, bb1, bb2 + +bb2: + %r1 = tuple () + return %r1 : $() +} + + From 56c09c3aed4e8359abe1032145d50478d96c6a5c Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:24:39 +0200 Subject: [PATCH 14/23] CSE: cse `builtin "once"` calls `builtin "once"` calls are a result of inlining global accessors --- lib/SILOptimizer/Transforms/CSE.cpp | 14 +++++++---- test/SILOptimizer/cse.sil | 37 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/SILOptimizer/Transforms/CSE.cpp b/lib/SILOptimizer/Transforms/CSE.cpp index 16077779cbec4..36bd2c12756f1 100644 --- a/lib/SILOptimizer/Transforms/CSE.cpp +++ b/lib/SILOptimizer/Transforms/CSE.cpp @@ -1187,11 +1187,17 @@ bool CSE::canHandle(SILInstruction *Inst) { return false; } if (auto *BI = dyn_cast(Inst)) { - // Although the onFastPath builtin has no side-effects we don't want to - // (re-)move it. - if (BI->getBuiltinInfo().ID == BuiltinValueKind::OnFastPath) + switch (BI->getBuiltinInfo().ID) { + case BuiltinValueKind::OnFastPath: + // Although the onFastPath builtin has no side-effects we don't want to + // (re-)move it. return false; - return !BI->mayReadOrWriteMemory(); + case BuiltinValueKind::Once: + case BuiltinValueKind::OnceWithContext: + return true; + default: + return !BI->mayReadOrWriteMemory(); + } } if (auto *EMI = dyn_cast(Inst)) { return !EMI->getOperand()->getType().isAddress(); diff --git a/test/SILOptimizer/cse.sil b/test/SILOptimizer/cse.sil index 1a18a50a3ab82..25d9bbca3a4ae 100644 --- a/test/SILOptimizer/cse.sil +++ b/test/SILOptimizer/cse.sil @@ -1411,3 +1411,40 @@ entry(%instance : $FC): return %retval : $() } +sil_global private @g_token : $Builtin.Word +sil_global private @g2_token : $Builtin.Word + +sil @g_init : $@convention(c) () -> () + +// CHECK-LABEL: sil @cse_builtin_once : +// CHECK: builtin +// CHECK-NOT: builtin +// CHECK: } // end sil function 'cse_builtin_once' +sil @cse_builtin_once : $@convention(thin) () -> () { +bb0: + %1 = global_addr @g_token : $*Builtin.Word + %2 = address_to_pointer %1 : $*Builtin.Word to $Builtin.RawPointer + %3 = function_ref @g_init : $@convention(c) () -> () + %4 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() + %5 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() + %r1 = tuple () + return %r1 : $() +} + +// CHECK-LABEL: sil @dont_cse_builtin_once_different_token : +// CHECK: builtin +// CHECK: builtin +// CHECK: } // end sil function 'dont_cse_builtin_once_different_token' +sil @dont_cse_builtin_once_different_token : $@convention(thin) () -> () { +bb0: + %1 = global_addr @g_token : $*Builtin.Word + %2 = address_to_pointer %1 : $*Builtin.Word to $Builtin.RawPointer + %3 = global_addr @g2_token : $*Builtin.Word + %4 = address_to_pointer %3 : $*Builtin.Word to $Builtin.RawPointer + %5 = function_ref @g_init : $@convention(c) () -> () + %6 = builtin "once"(%2 : $Builtin.RawPointer, %5 : $@convention(c) () -> ()) : $() + %7 = builtin "once"(%4 : $Builtin.RawPointer, %5 : $@convention(c) () -> ()) : $() + %r1 = tuple () + return %r1 : $() +} + From 19b828bcecbd11d30e5289e028a397daf733badd Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:32:32 +0200 Subject: [PATCH 15/23] StringOptimization: handle inlined global accessors. In case a global accessor is inlined, the `global_addr` and it's `builtin "once"` call is visible in the SIL instead of the call to the global initializer. --- .../Transforms/StringOptimization.cpp | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/lib/SILOptimizer/Transforms/StringOptimization.cpp b/lib/SILOptimizer/Transforms/StringOptimization.cpp index 0c9f5d648193c..0a815d5e9849f 100644 --- a/lib/SILOptimizer/Transforms/StringOptimization.cpp +++ b/lib/SILOptimizer/Transforms/StringOptimization.cpp @@ -581,25 +581,43 @@ StringOptimization::getStringFromStaticLet(SILValue value) { auto *load = dyn_cast(value); if (!load) return StringInfo::unknown(); - - auto *pta = dyn_cast(load->getOperand()); - if (!pta) - return StringInfo::unknown(); - - auto *addressorCall = dyn_cast(pta->getOperand()); - if (!addressorCall) - return StringInfo::unknown(); - - SILFunction *addressorFunc = addressorCall->getReferencedFunctionOrNull(); - if (!addressorFunc) - return StringInfo::unknown(); - - // The addressor function has a builtin.once call to the initializer. - BuiltinInst *onceCall = nullptr; - SILFunction *initializer = findInitializer(addressorFunc, onceCall); - if (!initializer) + + SILFunction *initializer = nullptr; + auto *globalAddr = dyn_cast(load->getOperand()); + if (globalAddr) { + // The global accessor is inlined. + + // Usually the global_addr is immediately preceeded by a call to + // `builtin "once"` which initializes the global. + SILInstruction *prev = globalAddr->getPreviousInstruction(); + if (!prev) + return StringInfo::unknown(); + auto *bi = dyn_cast(prev); + if (!bi || bi->getBuiltinInfo().ID != BuiltinValueKind::Once) + return StringInfo::unknown(); + initializer = getCalleeOfOnceCall(bi); + } else { + // The global accessor is not inlined, yet. + + auto *pta = dyn_cast(load->getOperand()); + if (!pta) + return StringInfo::unknown(); + + auto *addressorCall = dyn_cast(pta->getOperand()); + if (!addressorCall) + return StringInfo::unknown(); + + SILFunction *addressorFunc = addressorCall->getReferencedFunctionOrNull(); + if (!addressorFunc) + return StringInfo::unknown(); + + // The addressor function has a builtin.once call to the initializer. + BuiltinInst *onceCall = nullptr; + initializer = findInitializer(addressorFunc, onceCall); + } + if (!initializer || !initializer->isGlobalInitOnceFunction()) return StringInfo::unknown(); - + if (initializer->size() != 1) return StringInfo::unknown(); @@ -618,7 +636,10 @@ StringOptimization::getStringFromStaticLet(SILValue value) { } if (!gAddr || !gAddr->getReferencedGlobal()->isLet()) return StringInfo::unknown(); - + + if (globalAddr && globalAddr->getReferencedGlobal() != gAddr->getReferencedGlobal()) + return StringInfo::unknown(); + Operand *gUse = gAddr->getSingleUse(); auto *store = dyn_cast(gUse->getUser()); if (!store || store->getDest() != gAddr) From ee2924fd278e13e6fa01a73dfa1acff4560c9acf Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:40:02 +0200 Subject: [PATCH 16/23] Inliner: don't distinguish between the "mid-level" and "late" inliner Now that we handle inlined global initializers in LICM, CSE and the StringOptimization, we don't need to have a separate mid-level inliner pass, which treats global accessors specially. --- .../swift/SILOptimizer/PassManager/Passes.def | 4 +-- .../Utils/PerformanceInlinerUtils.h | 3 +-- lib/SILOptimizer/PassManager/PassPipeline.cpp | 10 +++----- .../Transforms/PerformanceInliner.cpp | 25 +++++++------------ .../Utils/PerformanceInlinerUtils.cpp | 7 +----- test/SILOptimizer/cast_folding.swift | 1 - test/SILOptimizer/cast_folding_objc.swift | 2 +- test/SILOptimizer/inline_late.sil | 4 +-- .../sil_combiner_concrete_prop_all_args.sil | 2 +- 9 files changed, 19 insertions(+), 39 deletions(-) diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 0294e0735e2d8..ac2b93b6fc47c 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -210,7 +210,7 @@ PASS(OnonePrespecializations, "onone-prespecializer", "Pre specialization via @_specialize") PASS(EarlyCodeMotion, "early-codemotion", "Early Code Motion without Release Hoisting") -PASS(EarlyInliner, "early-inline", +PASS(EarlyPerfInliner, "early-inline", "Early Inlining Preserving Semantic Functions") PASS(EmitDFDiagnostics, "dataflow-diagnostics", "Emit SIL Diagnostics") @@ -283,8 +283,6 @@ PASS(LateCodeMotion, "late-codemotion", "Late Code Motion with Release Hoisting") PASS(LateDeadFunctionAndGlobalElimination, "late-deadfuncelim", "Late Dead Function and Global Elimination") -PASS(LateInliner, "late-inline", - "Late Function Inlining") PASS(LoopCanonicalizer, "loop-canonicalizer", "Loop Canonicalization") PASS(LoopInfoPrinter, "loop-info-printer", diff --git a/include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h b/include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h index 6ac30c668d6c1..443137071db90 100644 --- a/include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h +++ b/include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h @@ -34,8 +34,7 @@ class BasicCalleeAnalysis; // global_init attributes. enum class InlineSelection { Everything, - NoGlobalInit, // and no availability semantics calls - NoSemanticsAndGlobalInit, + NoSemanticsAndEffects, OnlyInlineAlways, }; diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index bee3abbbba490..51796c929603b 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -485,17 +485,13 @@ void addFunctionPasses(SILPassPipelinePlan &P, switch (OpLevel) { case OptimizationLevelKind::HighLevel: - // Does not inline functions with defined semantics. - P.addEarlyInliner(); + // Does not inline functions with defined semantics or effects. + P.addEarlyPerfInliner(); break; case OptimizationLevelKind::MidLevel: - // Does inline semantics-functions (except "availability"), but not - // global-init functions. - P.addPerfInliner(); - break; case OptimizationLevelKind::LowLevel: // Inlines everything - P.addLateInliner(); + P.addPerfInliner(); break; } diff --git a/lib/SILOptimizer/Transforms/PerformanceInliner.cpp b/lib/SILOptimizer/Transforms/PerformanceInliner.cpp index 7f34c49a222d1..1ec9669489bde 100644 --- a/lib/SILOptimizer/Transforms/PerformanceInliner.cpp +++ b/lib/SILOptimizer/Transforms/PerformanceInliner.cpp @@ -360,7 +360,7 @@ bool SILPerformanceInliner::isProfitableToInline( // Bail out if this is a generic call of a `@_specialize(exported:)` function // and we are in the early inliner. We want to give the generic specializer // the opportunity to see specialized call sites. - if (IsGeneric && WhatToInline == InlineSelection::NoSemanticsAndGlobalInit && + if (IsGeneric && WhatToInline == InlineSelection::NoSemanticsAndEffects && Callee->hasPrespecialization()) { return false; } @@ -1141,9 +1141,7 @@ class SILPerformanceInlinerPass : public SILFunctionTransform { public: SILPerformanceInlinerPass(InlineSelection WhatToInline, StringRef LevelName): - WhatToInline(WhatToInline), PassName(LevelName) { - PassName.append(" Performance Inliner"); - } + WhatToInline(WhatToInline), PassName(LevelName) {} void run() override { DominanceAnalysis *DA = PM->getAnalysis(); @@ -1180,24 +1178,19 @@ class SILPerformanceInlinerPass : public SILFunctionTransform { SILTransform *swift::createAlwaysInlineInliner() { return new SILPerformanceInlinerPass(InlineSelection::OnlyInlineAlways, - "InlineAlways"); + "InlineAlways Performance Inliner"); } /// Create an inliner pass that does not inline functions that are marked with -/// the @_semantics, @_effects or global_init attributes. -SILTransform *swift::createEarlyInliner() { +/// the @_semantics or @_effects attributes. +SILTransform *swift::createEarlyPerfInliner() { return new SILPerformanceInlinerPass( - InlineSelection::NoSemanticsAndGlobalInit, "Early"); -} - -/// Create an inliner pass that does not inline functions that are marked with -/// the global_init attribute or have an "availability" semantics attribute. -SILTransform *swift::createPerfInliner() { - return new SILPerformanceInlinerPass(InlineSelection::NoGlobalInit, "Middle"); + InlineSelection::NoSemanticsAndEffects, "Early Performance Inliner"); } /// Create an inliner pass that inlines all functions that are marked with /// the @_semantics, @_effects or global_init attributes. -SILTransform *swift::createLateInliner() { - return new SILPerformanceInlinerPass(InlineSelection::Everything, "Late"); +SILTransform *swift::createPerfInliner() { + return new SILPerformanceInlinerPass( + InlineSelection::Everything, "Performance Inliner"); } diff --git a/lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp b/lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp index 7a2b855f9c50d..e7815256ecc46 100644 --- a/lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp +++ b/lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp @@ -753,7 +753,7 @@ SILFunction *swift::getEligibleFunction(FullApplySite AI, // Don't inline functions that are marked with the @_semantics or @_effects // attribute if the inliner is asked not to inline them. if (Callee->hasSemanticsAttrs() || Callee->hasEffectsKind()) { - if (WhatToInline >= InlineSelection::NoSemanticsAndGlobalInit) { + if (WhatToInline >= InlineSelection::NoSemanticsAndEffects) { // TODO: for stable optimization of semantics, prevent inlining whenever // isOptimizableSemanticFunction(Callee) is true. if (getSemanticFunctionLevel(Callee) == SemanticFunctionLevel::Fundamental @@ -776,11 +776,6 @@ SILFunction *swift::getEligibleFunction(FullApplySite AI, return nullptr; } } - - } else if (Callee->isGlobalInit()) { - if (WhatToInline != InlineSelection::Everything) { - return nullptr; - } } // We can't inline external declarations. diff --git a/test/SILOptimizer/cast_folding.swift b/test/SILOptimizer/cast_folding.swift index 7abe971207d6a..01d7d3d3122f8 100644 --- a/test/SILOptimizer/cast_folding.swift +++ b/test/SILOptimizer/cast_folding.swift @@ -1,5 +1,4 @@ // RUN: %target-swift-frontend -O -emit-sil %s | %FileCheck %s -// RUN: %target-swift-frontend -Xllvm -sil-disable-pass=FunctionSignatureOpts -Xllvm -sil-disable-pass=PerfInliner -O -emit-sil %s | %FileCheck %s // We want to check two things here: // - Correctness diff --git a/test/SILOptimizer/cast_folding_objc.swift b/test/SILOptimizer/cast_folding_objc.swift index a52f1f5e9d4ed..d68977d157e04 100644 --- a/test/SILOptimizer/cast_folding_objc.swift +++ b/test/SILOptimizer/cast_folding_objc.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -Xllvm -sil-disable-pass=PerfInliner -emit-sil %s | %FileCheck %s +// RUN: %target-swift-frontend -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -emit-sil %s | %FileCheck %s // We want to check two things here: // - Correctness diff --git a/test/SILOptimizer/inline_late.sil b/test/SILOptimizer/inline_late.sil index a0fc5fafbb027..b1251ccb10428 100644 --- a/test/SILOptimizer/inline_late.sil +++ b/test/SILOptimizer/inline_late.sil @@ -1,6 +1,6 @@ // RUN: %target-sil-opt -enable-sil-verify-all %s -early-inline -sil-inline-threshold=50 | %FileCheck %s -// RUN: %target-sil-opt -enable-sil-verify-all %s -late-inline -sil-inline-threshold=50 | %FileCheck %s --check-prefix=LATE -// RUN: %target-sil-opt -enable-sil-verify-all %s -late-inline -sil-inline-threshold=50 -module-name Swift | %FileCheck %s --check-prefix=STDLIBLATE +// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -sil-inline-threshold=50 | %FileCheck %s --check-prefix=LATE +// RUN: %target-sil-opt -enable-sil-verify-all %s -inline -sil-inline-threshold=50 -module-name Swift | %FileCheck %s --check-prefix=STDLIBLATE sil_stage canonical diff --git a/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil b/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil index 591676e753519..3ece314306045 100644 --- a/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil +++ b/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -wmo -enable-sil-verify-all %s -inline -sil-combine -generic-specializer -allocbox-to-stack -copy-forwarding -lower-aggregate-instrs -mem2reg -devirtualizer -late-inline -dead-arg-signature-opt -dce | %FileCheck %s +// RUN: %target-sil-opt -wmo -enable-sil-verify-all %s -inline -sil-combine -generic-specializer -allocbox-to-stack -copy-forwarding -lower-aggregate-instrs -mem2reg -devirtualizer -inline -dead-arg-signature-opt -dce | %FileCheck %s import Builtin import Swift import SwiftShims From 78ce13dbd62ef44bf15f8bfad5320adcae04f282 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:44:17 +0200 Subject: [PATCH 17/23] WalkUtils: Don't treat `end_access` as leaf-use in the AddressDefUseWalker Instead just ignore it. --- .../Sources/Optimizer/Utilities/WalkUtils.swift | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/WalkUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/WalkUtils.swift index 78e649edd8658..ed5a77bd55f16 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/WalkUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/WalkUtils.swift @@ -470,14 +470,17 @@ extension AddressDefUseWalker { } else { return unmatchedPath(address: operand, path: path) } - case is InitExistentialAddrInst, is OpenExistentialAddrInst, is BeginAccessInst, - is IndexAddrInst, is MarkMustCheckInst: + case is InitExistentialAddrInst, is OpenExistentialAddrInst, + is IndexAddrInst, is MarkMustCheckInst: // FIXME: for now `index_addr` is treated as a forwarding instruction since // SmallProjectionPath does not track indices. // This is ok since `index_addr` is eventually preceeded by a `tail_addr` // which has pushed a `"ct"` component on the path that matches any // `index_addr` address. return walkDownUses(ofAddress: instruction as! SingleValueInstruction, path: path) + case let ba as BeginAccessInst: + // Don't treat `end_access` as leaf-use. Just ignore it. + return walkDownNonEndAccessUses(of: ba, path: path) case let mdi as MarkDependenceInst: if operand.index == 0 { return walkDownUses(ofAddress: mdi, path: path) @@ -497,6 +500,16 @@ extension AddressDefUseWalker { } return .continueWalk } + + private mutating func walkDownNonEndAccessUses(of beginAccess: BeginAccessInst, path: Path) -> WalkResult { + for operand in beginAccess.uses where !operand.isTypeDependent { + if !(operand.instruction is EndAccessInst), + walkDown(address: operand, path: path) == .abortWalk { + return .abortWalk + } + } + return .continueWalk + } } /// - A `UseDefWalker` can be used to find all "generating" definitions of From 2b117fd3ee766d05824c08bf8f839713dd926f50 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 13:56:15 +0200 Subject: [PATCH 18/23] Swift Optimizer: add APIs to copy from or to a global static initializer * `Context.copyStaticInitializer(fromInitValue:, to:)` * `FunctionPassContext.createStaticInitializer(for:,initValue:)` --- .../Optimizer/PassManager/Context.swift | 33 ++++++++++++++++ .../Sources/SIL/Builder.swift | 2 +- .../swift/SILOptimizer/OptimizerBridging.h | 10 +++++ .../SILOptimizer/Utils/BasicBlockOptUtils.h | 38 +++++++++++++------ lib/SILOptimizer/IPO/GlobalOpt.cpp | 2 +- lib/SILOptimizer/PassManager/PassManager.cpp | 16 ++++++++ .../SILCombiner/SILCombinerMiscVisitors.cpp | 2 +- lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp | 24 ++++++------ 8 files changed, 102 insertions(+), 25 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index 601c06b5fe13b..91b29e6b754bf 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -71,6 +71,33 @@ extension MutatingContext { erase(instruction: inst) } + /// Copies all instructions of a static init value of a global to the insertion point of `builder`. + func copyStaticInitializer(fromInitValue: Value, to builder: Builder) -> Value? { + let range = _bridged.copyStaticInitializer(fromInitValue.bridged, builder.bridged) + guard let result = range.clonedInitValue.value, + let firstClonedInst = range.firstClonedInst.instruction else { + return nil + } + let resultInst = result.definingInstruction! + notifyNewInstructions(from: firstClonedInst, to: resultInst) + notifyInstructionChanged(resultInst) + return result + } + + private func notifyNewInstructions(from: Instruction, to: Instruction) { + var inst = from + while inst != to { + if !inst.isDeleted { + notifyInstructionChanged(inst) + } + if let next = inst.next { + inst = next + } else { + inst = inst.parentBlock.next!.instructions.first! + } + } + } + func tryDeleteDeadClosure(closure: SingleValueInstruction) -> Bool { _bridged.tryDeleteDeadClosure(closure.bridged) } @@ -148,6 +175,12 @@ struct FunctionPassContext : MutatingContext { fileprivate func notifyEffectsChanged() { _bridged.asNotificationHandler().notifyChanges(.effectsChanged) } + + /// Copies `initValue` (including all operand instructions, transitively) to the + /// static init value of `global`. + func createStaticInitializer(for global: GlobalVariable, initValue: SingleValueInstruction) { + _bridged.createStaticInitializer(global.bridged, initValue.bridged) + } } struct SimplifyContext : MutatingContext { diff --git a/SwiftCompilerSources/Sources/SIL/Builder.swift b/SwiftCompilerSources/Sources/SIL/Builder.swift index 2a1612d3666be..6179ba4542ee2 100644 --- a/SwiftCompilerSources/Sources/SIL/Builder.swift +++ b/SwiftCompilerSources/Sources/SIL/Builder.swift @@ -26,7 +26,7 @@ public struct Builder { private let notificationHandler: BridgedChangeNotificationHandler private let notifyNewInstruction: (Instruction) -> () - private var bridged: BridgedBuilder { + public var bridged: BridgedBuilder { switch insertAt { case .before(let inst): return BridgedBuilder(insertBefore: inst.bridged.optional, diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index 674ffb9cfb643..04494fc773d6e 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -207,6 +207,16 @@ struct BridgedPassContext { SWIFT_IMPORT_UNSAFE OptionalBridgedValue constantFoldBuiltin(BridgedInstruction builtin) const; + void createStaticInitializer(BridgedGlobalVar global, BridgedInstruction initValue) const; + + struct StaticInitCloneResult { + OptionalBridgedInstruction firstClonedInst; + OptionalBridgedValue clonedInitValue; + }; + + SWIFT_IMPORT_UNSAFE + StaticInitCloneResult copyStaticInitializer(BridgedValue initValue, BridgedBuilder b) const; + SWIFT_IMPORT_UNSAFE BridgedValue getSILUndef(swift::SILType type) const { return {swift::SILUndef::get(type, *invocation->getFunction())}; diff --git a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h index 81f71aef5c91f..0bbb4bfac2664 100644 --- a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h +++ b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h @@ -357,30 +357,43 @@ class StaticInitCloner : public SILCloner { /// don't have any operands). llvm::SmallVector readyToClone; - SILInstruction *insertionPoint = nullptr; + SILDebugLocation insertLoc; + + SILInstruction *firstClonedInst = nullptr; public: StaticInitCloner(SILGlobalVariable *gVar) - : SILCloner(gVar) {} + : SILCloner(gVar), + insertLoc(ArtificialUnreachableLocation(), nullptr) {} StaticInitCloner(SILInstruction *insertionPoint) : SILCloner(*insertionPoint->getFunction()), - insertionPoint(insertionPoint) { + insertLoc(insertionPoint->getDebugLocation()) { Builder.setInsertionPoint(insertionPoint); } + StaticInitCloner(const SILBuilder &builder) + : SILCloner(builder.getFunction()), + insertLoc(ArtificialUnreachableLocation(), nullptr) { + Builder.setInsertionPoint(builder.getInsertionBB(), builder.getInsertionPoint()); + if (builder.getInsertionPoint() != builder.getInsertionBB()->end()) + insertLoc = builder.getInsertionPoint()->getDebugLocation(); + } + /// Add \p InitVal and all its operands (transitively) for cloning. /// /// Note: all init values must are added, before calling clone(). /// Returns false if cloning is not possible, e.g. if we would end up cloning /// a reference to a private function into a function which is serialized. - bool add(SILInstruction *initVal); + bool add(SILValue initVal); /// Clone \p InitVal and all its operands into the initializer of the /// SILGlobalVariable. /// /// \return Returns the cloned instruction in the SILGlobalVariable. - SingleValueInstruction *clone(SingleValueInstruction *initVal); + SILValue clone(SILValue initVal); + + SILInstruction *getFirstClonedInst() const { return firstClonedInst; } /// Convenience function to clone a single \p InitVal. static void appendToInitializer(SILGlobalVariable *gVar, @@ -393,16 +406,19 @@ class StaticInitCloner : public SILCloner { } protected: + + void postProcess(SILInstruction *Orig, SILInstruction *Cloned) { + if (!firstClonedInst) + firstClonedInst = Cloned; + SILCloner::postProcess(Orig, Cloned); + } + SILLocation remapLocation(SILLocation loc) { - if (insertionPoint) - return insertionPoint->getLoc(); - return ArtificialUnreachableLocation(); + return insertLoc.getLocation(); } const SILDebugScope *remapScope(const SILDebugScope *DS) { - if (insertionPoint) - return insertionPoint->getDebugScope(); - return nullptr; + return insertLoc.getScope(); } }; diff --git a/lib/SILOptimizer/IPO/GlobalOpt.cpp b/lib/SILOptimizer/IPO/GlobalOpt.cpp index f93142d501888..40023b984a14e 100644 --- a/lib/SILOptimizer/IPO/GlobalOpt.cpp +++ b/lib/SILOptimizer/IPO/GlobalOpt.cpp @@ -348,7 +348,7 @@ static void replaceLoadsFromGlobal(SILValue addr, for (Operand *use : addr->getUses()) { SILInstruction *user = use->getUser(); if (auto *load = dyn_cast(user)) { - SingleValueInstruction *clonedInitVal = cloner.clone(initVal); + SingleValueInstruction *clonedInitVal = cast(cloner.clone(initVal)); load->replaceAllUsesWith(clonedInitVal); continue; } diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index f1eb54eb15fca..17c63b7d06b57 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -27,6 +27,7 @@ #include "swift/SILOptimizer/OptimizerBridging.h" #include "swift/SILOptimizer/PassManager/PrettyStackTrace.h" #include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/ConstantFolding.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/OptimizerStatsUtils.h" @@ -1436,6 +1437,21 @@ OptionalBridgedValue BridgedPassContext::constantFoldBuiltin(BridgedInstruction return {::constantFoldBuiltin(bi, resultsInError)}; } +void BridgedPassContext::createStaticInitializer(BridgedGlobalVar global, BridgedInstruction initValue) const { + StaticInitCloner::appendToInitializer(global.getGlobal(), initValue.getAs()); +} + +BridgedPassContext::StaticInitCloneResult BridgedPassContext:: +copyStaticInitializer(BridgedValue initValue, BridgedBuilder b) const { + swift::SILBuilder builder(b.insertBefore.getInst(), b.insertAtEnd.getBlock(), b.loc.getScope()); + StaticInitCloner cloner(builder); + if (!cloner.add(initValue.getSILValue())) { + return {{nullptr}, {nullptr}}; + } + SILValue result = cloner.clone(initValue.getSILValue()); + return {{cloner.getFirstClonedInst()->asSILNode()}, {result}}; +} + void BridgedPassContext::fixStackNesting(BridgedFunction function) const { switch (StackNesting::fixNesting(function.getFunction())) { case StackNesting::Changes::None: diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp index 246596b290343..2e8c6b0ce5ddb 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp @@ -996,7 +996,7 @@ SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) { if (SingleValueInstruction *initVal = getValueFromStaticLet(LI->getOperand())) { StaticInitCloner cloner(LI); if (cloner.add(initVal)) { - return cloner.clone(initVal); + return cloner.clone(initVal).getDefiningInstruction(); } } diff --git a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp index 5cf2c81fb6d6b..b4408ee77120c 100644 --- a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp +++ b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp @@ -310,9 +310,11 @@ bool SinkAddressProjections::cloneProjections() { return true; } -bool StaticInitCloner::add(SILInstruction *initVal) { +bool StaticInitCloner::add(SILValue initVal) { + SILInstruction *initInst = initVal->getDefiningInstruction(); + // Don't schedule an instruction twice for cloning. - if (numOpsToClone.count(initVal) != 0) + if (numOpsToClone.count(initInst) != 0) return true; if (auto *funcRef = dyn_cast(initVal)) { @@ -324,25 +326,25 @@ bool StaticInitCloner::add(SILInstruction *initVal) { } } - ArrayRef operands = initVal->getAllOperands(); - numOpsToClone[initVal] = operands.size(); + ArrayRef operands = initInst->getAllOperands(); + numOpsToClone[initInst] = operands.size(); if (operands.empty()) { // It's an instruction without operands, e.g. a literal. It's ready to be // cloned first. - readyToClone.push_back(initVal); + readyToClone.push_back(initInst); } else { // Recursively add all operands. for (const Operand &operand : operands) { - if (!add(cast(operand.get()))) + if (!add(operand.get())) return false; } } return true; } -SingleValueInstruction * -StaticInitCloner::clone(SingleValueInstruction *initVal) { - assert(numOpsToClone.count(initVal) != 0 && "initVal was not added"); +SILValue StaticInitCloner::clone(SILValue initVal) { + SILInstruction *initInst = initVal->getDefiningInstruction(); + assert(numOpsToClone.count(initInst) != 0 && "initVal was not added"); if (!isValueCloned(initVal)) { // Find the right order to clone: all operands of an instruction must be @@ -361,9 +363,9 @@ StaticInitCloner::clone(SingleValueInstruction *initVal) { readyToClone.push_back(user); } } - if (inst == initVal) + if (inst == initInst) break; } } - return cast(getMappedValue(initVal)); + return getMappedValue(initVal); } From 6d6b94e430c9d35b08144a1a3c0d7d18dd502c3e Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 14:11:56 +0200 Subject: [PATCH 19/23] Swift Optimizer: add the InitializeStaticGlobals function pass It converts a lazily initialized global to a statically initialized global variable. When this pass runs on a global initializer `[global_init_once_fn]` it tries to create a static initializer for the initialized global. ``` sil [global_init_once_fn] @globalinit { alloc_global @the_global %a = global_addr @the_global %i = some_const_initializer_insts store %i to %a } ``` The pass creates a static initializer for the global: ``` sil_global @the_global = { %initval = some_const_initializer_insts } ``` and removes the allocation and store instructions from the initializer function: ``` sil [global_init_once_fn] @globalinit { %a = global_addr @the_global %i = some_const_initializer_insts } ``` The initializer then becomes a side-effect free function which let's the builtin-simplification remove the `builtin "once"` which calls the initializer. --- .../Optimizer/FunctionPasses/CMakeLists.txt | 1 + .../InitializeStaticGlobals.swift | 135 ++++++++++++++++ .../PassManager/PassRegistration.swift | 1 + .../swift/SILOptimizer/PassManager/Passes.def | 2 + test/SILOptimizer/init_static_globals.sil | 153 ++++++++++++++++++ 5 files changed, 292 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift create mode 100644 test/SILOptimizer/init_static_globals.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt index bf43f0302f56b..7cb1b54354ba2 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt @@ -11,6 +11,7 @@ swift_compiler_sources(Optimizer CleanupDebugSteps.swift ComputeEscapeEffects.swift ComputeSideEffects.swift + InitializeStaticGlobals.swift ObjCBridgingOptimization.swift MergeCondFails.swift ReleaseDevirtualizer.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift new file mode 100644 index 0000000000000..5a0bbeee4700c --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift @@ -0,0 +1,135 @@ +//===--- InitializeStaticGlobals.swift -------------------------------------==// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +/// Converts a lazily initialized global to a statically initialized global variable. +/// +/// When this pass runs on a global initializer `[global_init_once_fn]` it tries to +/// create a static initializer for the initialized global. +/// +/// ``` +/// sil [global_init_once_fn] @globalinit { +/// alloc_global @the_global +/// %a = global_addr @the_global +/// %i = some_const_initializer_insts +/// store %i to %a +/// } +/// ``` +/// The pass creates a static initializer for the global: +/// ``` +/// sil_global @the_global = { +/// %initval = some_const_initializer_insts +/// } +/// ``` +/// and removes the allocation and store instructions from the initializer function: +/// ``` +/// sil [global_init_once_fn] @globalinit { +/// %a = global_addr @the_global +/// %i = some_const_initializer_insts +/// } +/// ``` +/// The initializer then becomes a side-effect free function which let's the builtin- +/// simplification remove the `builtin "once"` which calls the initializer. +/// +let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals") { + (function: Function, context: FunctionPassContext) in + + if !function.isGlobalInitOnceFunction { + return + } + + guard let (allocInst, storeToGlobal) = function.getGlobalInitialization() else { + return + } + + if !allocInst.global.canBeInitializedStatically { + return + } + + context.createStaticInitializer(for: allocInst.global, + initValue: storeToGlobal.source as! SingleValueInstruction) + context.erase(instruction: allocInst) + context.erase(instruction: storeToGlobal) +} + +private extension Function { + /// Analyses the global initializer function and returns the `alloc_global` and `store` + /// instructions which initialize the global. + /// + /// The function's single basic block must contain following code pattern: + /// ``` + /// alloc_global @the_global + /// %a = global_addr @the_global + /// %i = some_const_initializer_insts + /// store %i to %a + /// ``` + func getGlobalInitialization() -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? { + + guard let block = singleBlock else { + return nil + } + + var allocInst: AllocGlobalInst? = nil + var globalAddr: GlobalAddrInst? = nil + var store: StoreInst? = nil + + for inst in block.instructions { + switch inst { + case is ReturnInst, + is DebugValueInst, + is DebugStepInst: + break + case let agi as AllocGlobalInst: + if allocInst != nil { + return nil + } + allocInst = agi + case let ga as GlobalAddrInst: + if globalAddr != nil { + return nil + } + guard let agi = allocInst, agi.global == ga.global else { + return nil + } + globalAddr = ga + case let si as StoreInst: + if store != nil { + return nil + } + guard let ga = globalAddr else { + return nil + } + if si.destination != ga { + return nil + } + store = si + default: + if !inst.isValidInStaticInitializerOfGlobal { + return nil + } + } + } + if let store = store { + return (allocInst: allocInst!, storeToGlobal: store) + } + return nil + } + + var singleBlock: BasicBlock? { + let block = entryBlock + if block.next != nil { + return nil + } + return block + } +} diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift index c7290e6a1b4f4..6b68418274cd5 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift @@ -67,6 +67,7 @@ private func registerSwiftPasses() { registerPass(mergeCondFailsPass, { mergeCondFailsPass.run($0) }) registerPass(computeEscapeEffects, { computeEscapeEffects.run($0) }) registerPass(computeSideEffects, { computeSideEffects.run($0) }) + registerPass(initializeStaticGlobalsPass, { initializeStaticGlobalsPass.run($0) }) registerPass(objCBridgingOptimization, { objCBridgingOptimization.run($0) }) registerPass(stackPromotion, { stackPromotion.run($0) }) registerPass(functionStackProtection, { functionStackProtection.run($0) }) diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index ac2b93b6fc47c..d0d974f71c6e1 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -228,6 +228,8 @@ SWIFT_FUNCTION_PASS(ComputeSideEffects, "compute-side-effects", "Computes function side effects") SWIFT_FUNCTION_PASS(TestInstructionIteration, "test-instruction-iteration", "Tests instruction iteration") +SWIFT_FUNCTION_PASS(InitializeStaticGlobals, "initialize-static-globals", + "Initializes static global variables") PASS(FlowIsolation, "flow-isolation", "Enforces flow-sensitive actor isolation rules") PASS(FunctionOrderPrinter, "function-order-printer", diff --git a/test/SILOptimizer/init_static_globals.sil b/test/SILOptimizer/init_static_globals.sil new file mode 100644 index 0000000000000..c287344d05d89 --- /dev/null +++ b/test/SILOptimizer/init_static_globals.sil @@ -0,0 +1,153 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -initialize-static-globals | %FileCheck %s + +// REQUIRES: swift_in_compiler + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public struct TStruct { + let x: Int32 + init(x: Int32) +} + +struct Outer { + let a: Int32 + let b: TStruct + let c: Int32 +} + +let trivialglobal: TStruct + +public class TClass { + final let x: Int32 + init(x: Int32) + deinit +} + +struct GenericStruct { + var x: T +} + +let nontrivialglobal: TClass + +// CHECK-LABEL: sil_global hidden [let] @$trivialglobal : $TStruct = { +// CHECK: [[CONST:%.*]] = integer_literal $Builtin.Int32, 10 +// CHECK: [[INT:%.*]] = struct $Int32 ([[CONST]] : $Builtin.Int32) +// CHECK: %initval = struct $TStruct ([[INT]] : $Int32) +sil_global hidden [let] @$trivialglobal : $TStruct +sil_global private @globalinit_trivialglobal_token : $Builtin.Word + + +// CHECK-LABEL: sil_global hidden [let] @$nontrivialglobal : $TClass{{$}} +sil_global hidden [let] @$nontrivialglobal : $TClass +sil_global private @globalinit_nontrivialglobal_token : $Builtin.Word + +// CHECK-LABEL: sil_global hidden [let] @empty_global : $GenericStruct<()>{{$}} +sil_global hidden [let] @empty_global : $GenericStruct<()> +sil_global private @empty_global_token : $Builtin.Word + +// CHECK: sil_global @go : $Outer = { +// CHECK-NEXT: %0 = integer_literal $Builtin.Int32, 2 +// CHECK-NEXT: %1 = struct $Int32 (%0 : $Builtin.Int32) +// CHECK-NEXT: %2 = struct $TStruct (%1 : $Int32) +// CHECK-NEXT: %initval = struct $Outer (%1 : $Int32, %2 : $TStruct, %1 : $Int32) +// CHECK-NEXT: } +sil_global @go : $Outer +sil_global private @globalinit_token0 : $Builtin.Word + +// CHECK-LABEL: sil_global [let] @g1 : $Int32{{$}} +sil_global [let] @g1 : $Int32 +sil_global private @g1_token : $Builtin.Word + +// CHECK-LABEL: sil_global [let] @g2 : $Int32{{$}} +sil_global [let] @g2 : $Int32 +sil_global private @g2_token : $Builtin.Word + + +// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_trivialglobal_func : +// CHECK-NOT: alloc_global +// CHECK-NOT: store +// CHECK: } // end sil function 'globalinit_trivialglobal_func' +sil [global_init_once_fn] [ossa] @globalinit_trivialglobal_func : $@convention(c) () -> () { +bb0: + alloc_global @$trivialglobal + %1 = global_addr @$trivialglobal : $*TStruct + %2 = integer_literal $Builtin.Int32, 10 + %3 = struct $Int32 (%2 : $Builtin.Int32) + %4 = struct $TStruct (%3 : $Int32) + store %4 to [trivial] %1 : $*TStruct + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_nontrivialglobal_func : +// CHECK: alloc_global +// CHECK: store +// CHECK: } // end sil function 'globalinit_nontrivialglobal_func' +sil [global_init_once_fn] [ossa] @globalinit_nontrivialglobal_func : $@convention(c) () -> () { +bb0: + alloc_global @$nontrivialglobal + %1 = global_addr @$nontrivialglobal : $*TClass + %2 = integer_literal $Builtin.Int32, 10 + %3 = struct $Int32 (%2 : $Builtin.Int32) + %4 = alloc_ref $TClass + %5 = begin_borrow %4 : $TClass + %6 = ref_element_addr %5 : $TClass, #TClass.x + store %3 to [trivial] %6 : $*Int32 + end_borrow %5 : $TClass + store %4 to [init] %1 : $*TClass + %10 = tuple () + return %10 : $() +} + +// Check that we don't crash on an initializer struct with an "undef" operand. + +// CHECK-LABEL: sil [global_init_once_fn] [ossa] @globalinit_with_undef : +// CHECK: alloc_global +// CHECK: store +// CHECK: } // end sil function 'globalinit_with_undef' +sil [global_init_once_fn] [ossa] @globalinit_with_undef : $@convention(c) () -> () { +bb0: + alloc_global @empty_global + %1 = global_addr @empty_global : $*GenericStruct<()> + %2 = struct $GenericStruct<()> (undef : $()) + store %2 to [trivial] %1 : $*GenericStruct<()> + %4 = tuple () + return %4 : $() +} + +// CHECK-LABEL: sil [global_init_once_fn] @globalinit_nested_struct : +// CHECK-NOT: alloc_global +// CHECK-NOT: store +// CHECK: } // end sil function 'globalinit_nested_struct' +sil [global_init_once_fn] @globalinit_nested_struct : $@convention(c) () -> () { +bb0: + alloc_global @go + %0 = global_addr @go : $*Outer + %1 = integer_literal $Builtin.Int32, 2 + %2 = struct $Int32 (%1 : $Builtin.Int32) + %3 = struct $TStruct (%2 : $Int32) + %4 = struct $Outer (%2 : $Int32, %3 : $TStruct, %2 : $Int32) + store %4 to %0 : $*Outer + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil [global_init_once_fn] @globalinit_mismatching_global : +// CHECK: alloc_global +// CHECK: store +// CHECK: } // end sil function 'globalinit_mismatching_global' +sil [global_init_once_fn] @globalinit_mismatching_global : $@convention(c) () -> () { +bb0: + alloc_global @g1 + %1 = global_addr @g2 : $*Int32 + %2 = integer_literal $Builtin.Int32, 10 + %3 = struct $Int32 (%2 : $Builtin.Int32) + store %3 to %1 : $*Int32 + %6 = tuple () + return %6 : $() +} + From 88a4a9769e5b5b0c4ad5af1035268fde86013019 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 14:41:50 +0200 Subject: [PATCH 20/23] Swift Optimizer: add simplification for `load` Replace loads of global let variables with there static initializer values. --- .../InstructionSimplification/CMakeLists.txt | 1 + .../SimplifyLoad.swift | 79 ++++++++++++++++ test/SILOptimizer/simplify_load.sil | 94 +++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift create mode 100644 test/SILOptimizer/simplify_load.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt index 92b933fe36233..5a0a5a6e94f40 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt @@ -16,6 +16,7 @@ swift_compiler_sources(Optimizer SimplifyDebugStep.swift SimplifyDestructure.swift SimplifyGlobalValue.swift + SimplifyLoad.swift SimplifyStrongRetainRelease.swift SimplifyStructExtract.swift SimplifyTupleExtract.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift new file mode 100644 index 0000000000000..70ae6ce9a35c3 --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift @@ -0,0 +1,79 @@ +//===--- SimplifyLoad.swift -----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +extension LoadInst : OnoneSimplifyable { + func simplify(_ context: SimplifyContext) { + replaceLoadOfGlobalLet(context) + } + + private func replaceLoadOfGlobalLet(_ context: SimplifyContext) { + guard let globalInitVal = getGlobalInitValue(address: address) else { + return + } + let builder = Builder(before: self, context) + guard let initVal = context.copyStaticInitializer(fromInitValue: globalInitVal, to: builder) else { + return + } + uses.replaceAll(with: initVal, context) + transitivelyErase(load: self, context) + } +} + +/// Returns the init value of a global which is loaded from `address`. +private func getGlobalInitValue(address: Value) -> Value? { + switch address { + case let gai as GlobalAddrInst: + if gai.global.isLet { + return gai.global.staticInitValue + } + case let pta as PointerToAddressInst: + return globalLoadedViaAddressor(pointer: pta.pointer)?.staticInitValue + case let sea as StructElementAddrInst: + if let structVal = getGlobalInitValue(address: sea.struct) as? StructInst { + return structVal.operands[sea.fieldIndex].value + } + case let tea as TupleElementAddrInst: + if let tupleVal = getGlobalInitValue(address: tea.tuple) as? TupleInst { + return tupleVal.operands[tea.fieldIndex].value + } + case let bai as BeginAccessInst: + return getGlobalInitValue(address: bai.address) + default: + break + } + return nil +} + +private func globalLoadedViaAddressor(pointer: Value) -> GlobalVariable? { + if let ai = pointer as? ApplyInst, + let callee = ai.referencedFunction, + let global = callee.globalOfGlobalInitFunction, + global.isLet { + return global + } + return nil +} + +private func transitivelyErase(load: LoadInst, _ context: SimplifyContext) { + var inst: SingleValueInstruction = load + while inst.uses.isEmpty { + if inst.operands.count != 1 { + context.erase(instruction: inst) + return + } + let operandInst = inst.operands[0].value as! SingleValueInstruction + context.erase(instruction: inst) + inst = operandInst + } +} diff --git a/test/SILOptimizer/simplify_load.sil b/test/SILOptimizer/simplify_load.sil new file mode 100644 index 0000000000000..c6142cc2eb915 --- /dev/null +++ b/test/SILOptimizer/simplify_load.sil @@ -0,0 +1,94 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=load | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +struct Str { + @_hasStorage let x: Int64 + @_hasStorage let y: (Int64, Int64) +} + +sil_global [let] @gstr : $Str = { + %0 = integer_literal $Builtin.Int64, 10 + %1 = struct $Int64 (%0 : $Builtin.Int64) + %2 = integer_literal $Builtin.Int64, 27 + %3 = struct $Int64 (%2 : $Builtin.Int64) + %4 = tuple (%1 : $Int64, %3 : $Int64) + %initval = struct $Str (%1 : $Int64, %4 : $(Int64, Int64)) +} + +sil [global_init] @gstr_addressor : $@convention(thin) () -> Builtin.RawPointer { +bb0: + %0 = global_addr @gstr : $*Str + %1 = address_to_pointer %0 : $*Str to $Builtin.RawPointer + return %1 : $Builtin.RawPointer +} + +// CHECK-LABEL: sil @load_global_simple +// CHECK-DAG: [[L27:%.*]] = integer_literal $Builtin.Int64, 27 +// CHECK-DAG: [[I27:%.*]] = struct $Int64 ([[L27]] : $Builtin.Int64) +// CHECK-DAG: [[L10:%.*]] = integer_literal $Builtin.Int64, 10 +// CHECK-DAG: [[I10:%.*]] = struct $Int64 ([[L10]] : $Builtin.Int64) +// CHECK-DAG: [[T:%.*]] = tuple ([[I10]] : $Int64, [[I27]] : $Int64) +// CHECK: [[STR:%.*]] = struct $Str ([[I10]] : $Int64, [[T]] : $(Int64, Int64)) +// CHECK: return [[STR]] +// CHECK: } // end sil function 'load_global_simple' +sil @load_global_simple : $@convention(thin) () -> Str { +bb0: + %0 = global_addr @gstr : $*Str + %1 = begin_access [read] [static] %0 : $*Str + %2 = load %1 : $*Str + end_access %1 : $*Str + return %2 : $Str +} + +// CHECK-LABEL: sil @load_global_via_addressor +// CHECK-NOT: apply +// CHECK-DAG: [[L27:%.*]] = integer_literal $Builtin.Int64, 27 +// CHECK-DAG: [[I27:%.*]] = struct $Int64 ([[L27]] : $Builtin.Int64) +// CHECK-DAG: [[L10:%.*]] = integer_literal $Builtin.Int64, 10 +// CHECK-DAG: [[I10:%.*]] = struct $Int64 ([[L10]] : $Builtin.Int64) +// CHECK-DAG: [[T:%.*]] = tuple ([[I10]] : $Int64, [[I27]] : $Int64) +// CHECK: [[STR:%.*]] = struct $Str ([[I10]] : $Int64, [[T]] : $(Int64, Int64)) +// CHECK: return [[STR]] +// CHECK: } // end sil function 'load_global_via_addressor' +sil @load_global_via_addressor : $@convention(thin) () -> Str { +bb0: + %0 = function_ref @gstr_addressor : $@convention(thin) () -> Builtin.RawPointer + %1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer + %2 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*Str + %3 = load %2 : $*Str + return %3 : $Str +} + +// CHECK-LABEL: sil @load_global_struct_element +// CHECK-NOT: global_addr +// CHECK-DAG: [[L10:%.*]] = integer_literal $Builtin.Int64, 10 +// CHECK-DAG: [[I10:%.*]] = struct $Int64 ([[L10]] : $Builtin.Int64) +// CHECK: return [[I10]] +// CHECK: } // end sil function 'load_global_struct_element' +sil @load_global_struct_element : $@convention(thin) () -> Int64 { +bb0: + %0 = global_addr @gstr : $*Str + %1 = struct_element_addr %0 : $*Str, #Str.x + %2 = load %1 : $*Int64 + return %2 : $Int64 +} + +// CHECK-LABEL: sil @load_global_struct_tuple +// CHECK-NOT: global_addr +// CHECK-DAG: [[L27:%.*]] = integer_literal $Builtin.Int64, 27 +// CHECK-DAG: [[I27:%.*]] = struct $Int64 ([[L27]] : $Builtin.Int64) +// CHECK: return [[I27]] +// CHECK: } // end sil function 'load_global_struct_tuple' +sil @load_global_struct_tuple : $@convention(thin) () -> Int64 { +bb0: + %0 = global_addr @gstr : $*Str + %1 = struct_element_addr %0 : $*Str, #Str.y + %2 = tuple_element_addr %1 : $*(Int64, Int64), 1 + %3 = load %2 : $*Int64 + return %3 : $Int64 +} + From 960ca70def9de0ab8c050ae586ae3512cc9a1dad Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 14:46:29 +0200 Subject: [PATCH 21/23] Swift Optimizer: add the module pass ReadOnlyGlobalVariables It marks global `var` variables as `let` if they are never written. --- .../Optimizer/ModulePasses/CMakeLists.txt | 1 + .../ReadOnlyGlobalVariables.swift | 75 +++++++++++++++++++ .../PassManager/PassRegistration.swift | 1 + .../swift/SILOptimizer/PassManager/Passes.def | 2 + test/SILOptimizer/read_only_global_vars.sil | 45 +++++++++++ 5 files changed, 124 insertions(+) create mode 100644 SwiftCompilerSources/Sources/Optimizer/ModulePasses/ReadOnlyGlobalVariables.swift create mode 100644 test/SILOptimizer/read_only_global_vars.sil diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/CMakeLists.txt index f96b1c4fc4236..19dc9b7ab91c9 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/CMakeLists.txt @@ -7,5 +7,6 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors swift_compiler_sources(Optimizer + ReadOnlyGlobalVariables.swift StackProtection.swift ) diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/ReadOnlyGlobalVariables.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/ReadOnlyGlobalVariables.swift new file mode 100644 index 0000000000000..5d625543557fb --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/ReadOnlyGlobalVariables.swift @@ -0,0 +1,75 @@ +//===--- ReadOnlyGlobalVariables.swift ------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SIL + +/// Marks global `var` variables as `let` if they are never written. +/// +/// Note that this pass relies on the initialize-static-globals pass which converts lazily +/// initialized globals to statically initialized globals. +/// This pass does not mark lazily initialized globals as `let`, because such globals _are_ +/// written: in their initializers. +/// +let readOnlyGlobalVariablesPass = ModulePass(name: "read-only-global-variables") { + (moduleContext: ModulePassContext) in + + var writtenGlobals = Set() + + for f in moduleContext.functions { + for inst in f.instructions { + if let gAddr = inst as? GlobalAddrInst { + if gAddr.addressHasWrites { + writtenGlobals.insert(gAddr.global) + } + } + } + } + + for g in moduleContext.globalVariables { + if !g.isPossiblyUsedExternally, + !g.isLet, + !writtenGlobals.contains(g) { + g.setIsLet(to: true, moduleContext) + } + } +} + +private extension Value { + var addressHasWrites: Bool { + var walker = FindWrites() + return walker.walkDownUses(ofAddress: self, path: UnusedWalkingPath()) == .abortWalk + } +} + +private struct FindWrites : AddressDefUseWalker { + mutating func leafUse(address: Operand, path: UnusedWalkingPath) -> WalkResult { + switch address.instruction { + case is LoadInst, is LoadBorrowInst: + return .continueWalk + + case let ca as CopyAddrInst: + return address == ca.sourceOperand ? .continueWalk : .abortWalk + + case let apply as FullApplySite: + if let callerArgIdx = apply.argumentIndex(of: address) { + let calleeArgIdx = apply.calleeArgIndex(callerArgIndex: callerArgIdx) + let convention = apply.getArgumentConvention(calleeArgIndex: calleeArgIdx) + if convention.isIndirectIn { + return .continueWalk + } + } + return .abortWalk + default: + return .abortWalk + } + } +} diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift index 6b68418274cd5..0653a965bf733 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift @@ -61,6 +61,7 @@ private func registerForSILCombine( private func registerSwiftPasses() { // Module passes + registerPass(readOnlyGlobalVariablesPass, { readOnlyGlobalVariablesPass.run($0) }) registerPass(stackProtection, { stackProtection.run($0) }) // Function passes diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index d0d974f71c6e1..19b4f160ee90f 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -384,6 +384,8 @@ SWIFT_FUNCTION_PASS(SILPrinter, "sil-printer", "Test pass which prints the SIL of a function") SWIFT_MODULE_PASS(FunctionUsesDumper, "dump-function-uses", "Dump the results of FunctionUses") +SWIFT_MODULE_PASS(ReadOnlyGlobalVariablesPass, "read-only-global-variables", + "Converts read-only var-globals to let-globals") SWIFT_MODULE_PASS(StackProtection, "stack-protection", "Decides which functions need stack protectors") SWIFT_FUNCTION_PASS(FunctionStackProtection, "function-stack-protection", diff --git a/test/SILOptimizer/read_only_global_vars.sil b/test/SILOptimizer/read_only_global_vars.sil new file mode 100644 index 0000000000000..50c9a8a2b62b0 --- /dev/null +++ b/test/SILOptimizer/read_only_global_vars.sil @@ -0,0 +1,45 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -read-only-global-variables | %FileCheck %s + +// REQUIRES: swift_in_compiler + +import Builtin +import Swift + +// CHECK-LABEL: sil_global private [let] @read_only_var : $Int32 = { +sil_global private @read_only_var : $Int32 = { + %0 = integer_literal $Builtin.Int32, 27 + %initval = struct $Int32 (%0 : $Builtin.Int32) +} + +// CHECK-LABEL: sil_global private @written_var : $Int32 = { +sil_global private @written_var : $Int32 = { + %0 = integer_literal $Builtin.Int32, 27 + %initval = struct $Int32 (%0 : $Builtin.Int32) +} + +// CHECK-LABEL: sil_global @public_var : $Int32 = { +sil_global @public_var : $Int32 = { + %0 = integer_literal $Builtin.Int32, 27 + %initval = struct $Int32 (%0 : $Builtin.Int32) +} + +sil @unknown_read_func : $@convention(thin) (@in Int32) -> () + +sil @read_var : $@convention(thin) (@inout Int32) -> Int32 { +bb0(%0 : $*Int32): + %1 = global_addr @read_only_var : $*Int32 + %2 = load %1 : $*Int32 + copy_addr %1 to %0 : $*Int32 + %4 = function_ref @unknown_read_func : $@convention(thin) (@in Int32) -> () + %5 = apply %4(%1) : $@convention(thin) (@in Int32) -> () + return %2 : $Int32 +} + +sil @write_var : $@convention(thin) (Int32) -> () { +bb0(%0 : $Int32): + %1 = global_addr @written_var : $*Int32 + store %0 to %1 : $*Int32 + %r = tuple () + return %r : $() +} + From 1e6511e7c02f8721ce8088ab3ba93fed30363d40 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 14:56:06 +0200 Subject: [PATCH 22/23] Pass Pipeline: replace the old GlobalOpt with the new InitializeStaticGlobals and ReadOnlyGlobalVariablesPass passes. --- lib/SILOptimizer/PassManager/PassPipeline.cpp | 23 ++++++++----------- test/IRGen/globals.swift | 3 ++- test/SILGen/fragile_globals.swift | 2 +- test/SILGen/global_resilience.swift | 2 ++ .../cross-module-optimization.swift | 1 + .../dead_function_elimination.swift | 1 - test/SILOptimizer/default-cmo.swift | 13 +++++++---- test/SILOptimizer/global-c-functionptr.swift | 1 + .../SILOptimizer/global_init_with_empty.swift | 1 + .../globalopt_global_propagation.swift | 4 ++-- .../globalopt_let_propagation.swift | 3 ++- test/SILOptimizer/globalopt_resilience.swift | 2 ++ .../globalopt_resilience_testing.swift | 2 ++ test/SILOptimizer/optimizer_counters.sil | 5 ++-- .../performance-annotations.swift | 1 + .../remove_unused_global_vars.swift | 8 +++---- test/SILOptimizer/target-const-prop.swift | 1 + test/SILOptimizer/zeroInitializer.swift | 2 ++ test/sil-passpipeline-dump/basic.test-sh | 3 +-- 19 files changed, 45 insertions(+), 33 deletions(-) diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index 51796c929603b..064dd8f9c8d8d 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -231,7 +231,9 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) { P.addDiagnoseLifetimeIssues(); } - P.addGlobalOpt(); + P.addOnoneSimplification(); + P.addInitializeStaticGlobals(); + P.addPerformanceDiagnostics(); // Canonical swift requires all non cond_br critical edges to be split. @@ -627,10 +629,6 @@ static void addPerfEarlyModulePassPipeline(SILPassPipelinePlan &P) { // is linked in from the stdlib. P.addTempRValueOpt(); - // Needed to serialize static initializers of globals for cross-module - // optimization. - P.addGlobalOpt(); - // Add the outliner pass (Osize). P.addOutliner(); } @@ -674,7 +672,6 @@ static void addHighLevelModulePipeline(SILPassPipelinePlan &P) { P.addComputeSideEffects(); P.addStackPromotion(); - P.addGlobalOpt(); P.addLetPropertiesOpt(); } @@ -699,25 +696,26 @@ static void addMidLevelFunctionPipeline(SILPassPipelinePlan &P) { static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) { P.startPipeline("ClosureSpecialize"); P.addDeadFunctionAndGlobalElimination(); + P.addReadOnlyGlobalVariablesPass(); P.addTargetConstantFolding(); P.addDeadStoreElimination(); P.addDeadObjectElimination(); - // These few passes are needed to cleanup between loop unrolling and GlobalOpt. + // These few passes are needed to cleanup between loop unrolling and InitializeStaticGlobals. // This is needed to fully optimize static small String constants. P.addSimplifyCFG(); P.addSILCombine(); P.addPerformanceConstantPropagation(); P.addSimplifyCFG(); - + P.addSimplification(); + + P.addInitializeStaticGlobals(); + // ComputeEffects should be done at the end of a function-pipeline. The next - // pass (GlobalOpt) is a module pass, so this is the end of a function-pipeline. + // pass (LetPropertiesOpt) is a module pass, so this is the end of a function-pipeline. P.addComputeEscapeEffects(); P.addComputeSideEffects(); - // Hoist globals out of loops. - // Global-init functions should not be inlined GlobalOpt is done. - P.addGlobalOpt(); P.addLetPropertiesOpt(); // Propagate constants into closures and convert to static dispatch. This @@ -984,7 +982,6 @@ SILPassPipelinePlan::getOnonePassPipeline(const SILOptions &Options) { // in the editor. P.startPipeline("Non-Diagnostic Mandatory Optimizations"); P.addForEachLoopUnroll(); - P.addOnoneSimplification(); // TODO: MandatoryARCOpts should be subsumed by CopyPropagation. There should // be no need to run another analysis of copies at -Onone. diff --git a/test/IRGen/globals.swift b/test/IRGen/globals.swift index 0bf280b9f3071..dce8d83ad2208 100644 --- a/test/IRGen/globals.swift +++ b/test/IRGen/globals.swift @@ -1,6 +1,7 @@ // RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s -// REQUIRES: CPU=x86_64 +// REQUIRES: swift_in_compiler +// REQUIRES: PTRSIZE=64 var g0 : Int = 1 var g1 : (Void, Int, Void) diff --git a/test/SILGen/fragile_globals.swift b/test/SILGen/fragile_globals.swift index d1833c5c3ba80..9c51796232c5b 100644 --- a/test/SILGen/fragile_globals.swift +++ b/test/SILGen/fragile_globals.swift @@ -1,7 +1,7 @@ // RUN: %empty-directory(%t) // RUN: %target-swift-frontend -emit-module -parse-as-library -o %t %S/Inputs/ModuleA.swift // RUN: %target-swift-frontend -emit-module -parse-as-library -o %t %S/Inputs/ModuleB.swift -// RUN: %target-swift-emit-sil -parse-as-library -I%t %s -Xllvm -sil-disable-pass=GlobalOpt -O | %FileCheck %s +// RUN: %target-swift-emit-sil -parse-as-library -I%t %s -Xllvm -sil-disable-pass=initialize-static-globals -O | %FileCheck %s import ModuleA import ModuleB diff --git a/test/SILGen/global_resilience.swift b/test/SILGen/global_resilience.swift index 1d0902bae7002..8a12df713a918 100644 --- a/test/SILGen/global_resilience.swift +++ b/test/SILGen/global_resilience.swift @@ -3,6 +3,8 @@ // RUN: %target-swift-emit-silgen -I %t -enable-library-evolution -parse-as-library %s | %FileCheck %s // RUN: %target-swift-emit-sil -I %t -O -enable-library-evolution -parse-as-library %s | %FileCheck --check-prefix=CHECK-OPT %s +// REQUIRES: swift_in_compiler + import resilient_global public struct MyEmptyStruct {} diff --git a/test/SILOptimizer/cross-module-optimization.swift b/test/SILOptimizer/cross-module-optimization.swift index 8000614b1fe38..8e86ded76b804 100644 --- a/test/SILOptimizer/cross-module-optimization.swift +++ b/test/SILOptimizer/cross-module-optimization.swift @@ -18,6 +18,7 @@ // RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT // REQUIRES: executable_test +// REQUIRES: swift_in_compiler // Second test: check if CMO really imports the SIL of functions in other modules. diff --git a/test/SILOptimizer/dead_function_elimination.swift b/test/SILOptimizer/dead_function_elimination.swift index bcb467d945118..256c33426cd95 100644 --- a/test/SILOptimizer/dead_function_elimination.swift +++ b/test/SILOptimizer/dead_function_elimination.swift @@ -198,7 +198,6 @@ public func keepPtrAlive() { // CHECK-TESTING: sil {{.*}}publicClassMethod // CHECK-TESTING: sil {{.*}}DeadWitness -// CHECK-LABEL: sil_global {{.*}}@$s25dead_function_elimination5GFStrV12aliveFuncPtryycvpZ // CHECK-LABEL: @$s25dead_function_elimination14donotEliminateyyF // CHECK-LABEL: sil @$s25dead_function_elimination12keepPtrAliveyyF diff --git a/test/SILOptimizer/default-cmo.swift b/test/SILOptimizer/default-cmo.swift index 36b44c346adad..f86bb491098a0 100644 --- a/test/SILOptimizer/default-cmo.swift +++ b/test/SILOptimizer/default-cmo.swift @@ -7,19 +7,24 @@ // RUN: %target-build-swift -O -wmo -module-name=Main -I%t -I%S/Inputs/cross-module %s -emit-sil | %FileCheck %s +// REQUIRES: swift_in_compiler import Module import ModuleTBD -// CHECK-LABEL: sil_global public_external [serialized] @$s6Module0A6StructV21publicFunctionPointeryS2icvpZ : $@callee_guaranteed (Int) -> Int = { -// CHECK: %0 = function_ref @$s6Module16incrementByThreeyS2iF - -// CHECK-LABEL: sil_global public_external @$s6Module0A6StructV22privateFunctionPointeryS2icvpZ : $@callee_guaranteed (Int) -> Int{{$}} +// CHECK-LABEL: sil_global public_external [let] @$s6Module0A6StructV22privateFunctionPointeryS2icvpZ : $@callee_guaranteed (Int) -> Int{{$}} public func callPublicFunctionPointer(_ x: Int) -> Int { return Module.ModuleStruct.publicFunctionPointer(x) } +// CHECK-LABEL: sil @$s4Main25callPublicFunctionPointeryS2iF : +// CHECK-NOT: global_addr +// CHECK-NOT: apply +// CHECK: builtin "sadd +// CHECK-NOT: global_addr +// CHECK-NOT: apply +// CHECK: } // end sil function '$s4Main25callPublicFunctionPointeryS2iF' public func callPrivateFunctionPointer(_ x: Int) -> Int { return Module.ModuleStruct.privateFunctionPointer(x) } diff --git a/test/SILOptimizer/global-c-functionptr.swift b/test/SILOptimizer/global-c-functionptr.swift index 14c722d549147..95c24fa1f8726 100644 --- a/test/SILOptimizer/global-c-functionptr.swift +++ b/test/SILOptimizer/global-c-functionptr.swift @@ -12,6 +12,7 @@ // RUN: %target-build-swift -O -module-name=Test %s -emit-sil | %FileCheck %s -check-prefix=CHECK-SIL // REQUIRES: executable_test +// REQUIRES: swift_in_compiler internal func cFn(_ i: Int) -> Int { diff --git a/test/SILOptimizer/global_init_with_empty.swift b/test/SILOptimizer/global_init_with_empty.swift index 89175c35fa6ba..34719a5323eec 100644 --- a/test/SILOptimizer/global_init_with_empty.swift +++ b/test/SILOptimizer/global_init_with_empty.swift @@ -6,6 +6,7 @@ // RUN: %target-codesign %t/a.out // RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT // REQUIRES: executable_test,optimized_stdlib +// REQUIRES: swift_in_compiler struct Empty { } diff --git a/test/SILOptimizer/globalopt_global_propagation.swift b/test/SILOptimizer/globalopt_global_propagation.swift index cb1be1f40643a..64f3e63b128ea 100644 --- a/test/SILOptimizer/globalopt_global_propagation.swift +++ b/test/SILOptimizer/globalopt_global_propagation.swift @@ -1,8 +1,8 @@ -// RUN: %target-swift-frontend -O -emit-sil %s | %FileCheck %s -// RUN: %target-swift-frontend -O -wmo -emit-sil %s | %FileCheck -check-prefix=CHECK-WMO %s // RUN: %target-swift-frontend -parse-as-library -O -emit-sil %s | %FileCheck %s // RUN: %target-swift-frontend -parse-as-library -O -wmo -emit-sil %s | %FileCheck -check-prefix=CHECK-WMO %s +// REQUIRES: swift_in_compiler + // Check that values of internal and private global variables, which are provably assigned only // once, are propagated into their uses and enable further optimizations like constant // propagation, simplifications, etc. diff --git a/test/SILOptimizer/globalopt_let_propagation.swift b/test/SILOptimizer/globalopt_let_propagation.swift index 35098f523200a..9d6336e3d33f9 100644 --- a/test/SILOptimizer/globalopt_let_propagation.swift +++ b/test/SILOptimizer/globalopt_let_propagation.swift @@ -1,6 +1,7 @@ -// RUN: %target-swift-frontend -O -emit-sil -primary-file %s | %FileCheck %s // RUN: %target-swift-frontend -parse-as-library -O -emit-sil -primary-file %s | %FileCheck -check-prefix=CHECK -check-prefix=CHECK-LIB %s +// REQUIRES: swift_in_compiler + // Check that values of static let and global let variables are propagated into their uses // and enable further optimizations like constant propagation, simplifications, etc. diff --git a/test/SILOptimizer/globalopt_resilience.swift b/test/SILOptimizer/globalopt_resilience.swift index 68563d1f0c571..600f601ab5024 100644 --- a/test/SILOptimizer/globalopt_resilience.swift +++ b/test/SILOptimizer/globalopt_resilience.swift @@ -1,5 +1,7 @@ // RUN: %target-swift-frontend -O -module-name=test -enable-library-evolution -emit-sil -primary-file %s | %FileCheck %s +// REQUIRES: swift_in_compiler + // Check if GlobalOpt generates the optimal getter for a static property with a resilient type. // The (resilient) getter should just return the literal (and not lazily initialize a global variable). diff --git a/test/SILOptimizer/globalopt_resilience_testing.swift b/test/SILOptimizer/globalopt_resilience_testing.swift index 4d0b1c3a5162e..52f6b36bdd671 100644 --- a/test/SILOptimizer/globalopt_resilience_testing.swift +++ b/test/SILOptimizer/globalopt_resilience_testing.swift @@ -1,6 +1,8 @@ // RUN: %target-swift-frontend -emit-sil -O -enable-library-evolution -primary-file %s | %FileCheck %s // RUN: %target-swift-frontend -emit-sil -O -enable-library-evolution -enable-testing -primary-file %s | %FileCheck %s --check-prefix=CHECK-TESTING +// REQUIRES: swift_in_compiler + // If a global variable with a resilient type has public linkage, we have to // allocate a buffer for it even if the type has a fixed size in its // defining module. diff --git a/test/SILOptimizer/optimizer_counters.sil b/test/SILOptimizer/optimizer_counters.sil index 76f287ab85ded..c9ae58b68fb9b 100644 --- a/test/SILOptimizer/optimizer_counters.sil +++ b/test/SILOptimizer/optimizer_counters.sil @@ -19,9 +19,8 @@ sil @fatalError : $@convention(thin) () -> Never // Check that module level statistics are produced. // -// CHECK-SIL-STATS-MODULES: module, inst, HighLevel,Function+EarlyLoopOpt, PerformanceConstantPropagation, {{.*}}, 14, 11 -// CHECK-SIL-STATS-MODULES: module, block, HighLevel,Function+EarlyLoopOpt, SimplifyCFG, {{.*}}, 5, 3 -// CHECK-SIL-STATS-MODULES: module, inst, HighLevel,Function+EarlyLoopOpt, SimplifyCFG, {{.*}}, 11, 6 +// CHECK-SIL-STATS-MODULES: module, block, PrepareOptimizationPasses, Simplification, {{.*}}, 6, 3 +// CHECK-SIL-STATS-MODULES: module, inst, PrepareOptimizationPasses, Simplification, {{.*}}, 15, 6 // Check that module level statistics are produced. // diff --git a/test/SILOptimizer/performance-annotations.swift b/test/SILOptimizer/performance-annotations.swift index ab6953e5daef3..44caf37e93121 100644 --- a/test/SILOptimizer/performance-annotations.swift +++ b/test/SILOptimizer/performance-annotations.swift @@ -1,5 +1,6 @@ // RUN: %target-swift-frontend -experimental-performance-annotations -emit-sil %s -o /dev/null -verify // REQUIRES: swift_stdlib_no_asserts,optimized_stdlib +// REQUIRES: swift_in_compiler protocol P { func protoMethod(_ a: Int) -> Int diff --git a/test/SILOptimizer/remove_unused_global_vars.swift b/test/SILOptimizer/remove_unused_global_vars.swift index eb5988a619811..1e6a527b5b826 100644 --- a/test/SILOptimizer/remove_unused_global_vars.swift +++ b/test/SILOptimizer/remove_unused_global_vars.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -primary-file %s -O -module-name=test -emit-sil | %FileCheck %s +// RUN: %target-swift-frontend -parse-as-library -primary-file %s -O -module-name=test -emit-sil | %FileCheck %s import SwiftShims @@ -16,8 +16,8 @@ struct Foo { private let unused1 = 0 // CHECK-NOT: sil_global private {{.*}}unused2{{.*}} private var unused2 = 42 -// CHECK: sil_global private [let] @${{.*}}used1{{.*}} : $Int -private let used1 = 0 +// CHECK: sil_global private @${{.*}}used1{{.*}} : $Int +private var used1 = 0 // CHECK: sil_global private @${{.*}}used2{{.*}} : $Int private var used2 = 0 @@ -48,8 +48,6 @@ var unused6 = 0 // CHECK-LABEL: sil [Onone] @${{.*}}test{{.*}} @_optimize(none) public func test(x: Int) -> Int { - // CHECK: %{{[0-9]+}} = global_addr @${{.*}}used2{{.*}} - // CHECK: %{{[0-9]+}} = global_addr @${{.*}}used1{{.*}} return used1 + used2 + x } diff --git a/test/SILOptimizer/target-const-prop.swift b/test/SILOptimizer/target-const-prop.swift index 01bc2a169f959..9117bd69feb53 100644 --- a/test/SILOptimizer/target-const-prop.swift +++ b/test/SILOptimizer/target-const-prop.swift @@ -6,6 +6,7 @@ // RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT // REQUIRES: executable_test,swift_stdlib_no_asserts,optimized_stdlib +// REQUIRES: swift_in_compiler struct S { diff --git a/test/SILOptimizer/zeroInitializer.swift b/test/SILOptimizer/zeroInitializer.swift index 83b8f861c5f04..fa1f15c37e813 100644 --- a/test/SILOptimizer/zeroInitializer.swift +++ b/test/SILOptimizer/zeroInitializer.swift @@ -1,5 +1,7 @@ // RUN: %target-swift-frontend -O -parse-stdlib -emit-ir -module-name ZeroInit -verify %s | %FileCheck %s +// REQUIRES: swift_in_compiler + import Swift @frozen diff --git a/test/sil-passpipeline-dump/basic.test-sh b/test/sil-passpipeline-dump/basic.test-sh index 7b97506f2f7a3..d9fbeae9a1fff 100644 --- a/test/sil-passpipeline-dump/basic.test-sh +++ b/test/sil-passpipeline-dump/basic.test-sh @@ -7,8 +7,7 @@ // CHECK: --- // CHECK: name: Non-Diagnostic Mandatory Optimizations -// CHECK: passes: [ "for-each-loop-unroll", "onone-simplification", "mandatory-arc-opts", -// CHECK: "onone-prespecializer" ] +// CHECK: passes: [ "for-each-loop-unroll", "mandatory-arc-opts", "onone-prespecializer" ] // CHECK: --- // CHECK: name: Serialization // CHECK: passes: [ "serialize-sil", "sil-moved-async-var-dbginfo-propagator", From df7c71badb74f3d03d72311c089525b26ea10b9f Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 8 May 2023 14:58:12 +0200 Subject: [PATCH 23/23] Optimizer: remove the now obsolete GlobalOpt pass --- .../swift/SILOptimizer/PassManager/Passes.def | 2 - lib/SILOptimizer/IPO/CMakeLists.txt | 1 - lib/SILOptimizer/IPO/GlobalOpt.cpp | 871 ------------------ .../OwnershipVerifier/load_borrow_verify.sil | 50 - test/SILOptimizer/globalopt.sil | 55 -- test/SILOptimizer/globalopt_ossa.sil | 55 -- .../globalopt_trivial_nontrivial.sil | 133 --- .../globalopt_trivial_nontrivial_ossa.sil | 160 ---- test/SILOptimizer/static_initializer.sil | 65 -- 9 files changed, 1392 deletions(-) delete mode 100644 lib/SILOptimizer/IPO/GlobalOpt.cpp delete mode 100644 test/SILOptimizer/globalopt.sil delete mode 100644 test/SILOptimizer/globalopt_ossa.sil delete mode 100644 test/SILOptimizer/globalopt_trivial_nontrivial.sil delete mode 100644 test/SILOptimizer/globalopt_trivial_nontrivial_ossa.sil delete mode 100644 test/SILOptimizer/static_initializer.sil diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 19b4f160ee90f..f981df7f11605 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -257,8 +257,6 @@ PASS(ExistentialSpecializer, "existential-specializer", PASS(SILSkippingChecker, "check-sil-skipping", "Utility pass to ensure -experimental-skip-*-function-bodies skip " "SIL generation of not-to-be-serialized functions entirely") -PASS(GlobalOpt, "global-opt", - "SIL Global Optimization") PASS(GlobalPropertyOpt, "global-property-opt", "Global Property Optimization") PASS(MandatoryARCOpts, "mandatory-arc-opts", diff --git a/lib/SILOptimizer/IPO/CMakeLists.txt b/lib/SILOptimizer/IPO/CMakeLists.txt index f19ae4316bff5..a011d1d3daff3 100644 --- a/lib/SILOptimizer/IPO/CMakeLists.txt +++ b/lib/SILOptimizer/IPO/CMakeLists.txt @@ -3,7 +3,6 @@ target_sources(swiftSILOptimizer PRIVATE ClosureSpecializer.cpp CrossModuleOptimization.cpp DeadFunctionElimination.cpp - GlobalOpt.cpp GlobalPropertyOpt.cpp LetPropertiesOpts.cpp UsePrespecialized.cpp) diff --git a/lib/SILOptimizer/IPO/GlobalOpt.cpp b/lib/SILOptimizer/IPO/GlobalOpt.cpp deleted file mode 100644 index 40023b984a14e..0000000000000 --- a/lib/SILOptimizer/IPO/GlobalOpt.cpp +++ /dev/null @@ -1,871 +0,0 @@ -//===--- GlobalOpt.cpp - Optimize global initializers ---------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -#define DEBUG_TYPE "globalopt" -#include "swift/AST/ASTMangler.h" -#include "swift/Demangling/Demangle.h" -#include "swift/Demangling/Demangler.h" -#include "swift/Demangling/ManglingMacros.h" -#include "swift/SIL/CFG.h" -#include "swift/SIL/DebugUtils.h" -#include "swift/SIL/SILCloner.h" -#include "swift/SIL/SILGlobalVariable.h" -#include "swift/SIL/SILInstruction.h" -#include "swift/SIL/SILInstructionWorklist.h" -#include "swift/SILOptimizer/Analysis/ColdBlockInfo.h" -#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" -#include "swift/SILOptimizer/PassManager/Passes.h" -#include "swift/SILOptimizer/PassManager/Transforms.h" -#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" -#include "swift/SILOptimizer/Utils/InstructionDeleter.h" -#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h" -#include "swift/SILOptimizer/Utils/ConstantFolding.h" -#include "llvm/ADT/MapVector.h" -#include "llvm/ADT/SCCIterator.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Debug.h" - -using namespace swift; - -namespace { - -/// Optimize the placement of global initializers. -/// -/// TODO: -/// -/// - Analyze the module to move initializers to the module's public -/// entry points. -/// -/// - Convert trivial initializers to static initialization. This requires -/// serializing globals. -/// -/// - For global "lets", generate addressors that return by value. If we also -/// converted to a static initializer, then remove the load from the addressor. -/// -/// - When the addressor is local to the module, be sure it is inlined to allow -/// constant propagation in case of statically initialized "lets". -class SILGlobalOpt { - SILOptFunctionBuilder &FunctionBuilder; - SILModule *Module; - DominanceAnalysis *DA; - SILPassManager *PM; - bool HasChanged = false; - - typedef SmallVector GlobalInitCalls; - typedef SmallVector GlobalAccesses; - typedef SmallVector GlobalAddrs; - - /// A map from each visited global initializer call to a list of call sites. - llvm::MapVector GlobalInitCallMap; - - // The following mappings are used if this is a compilation - // in scripting mode and global variables are accessed without - // addressors. - - /// A map from each visited global to its set of begin_access instructions. - llvm::MapVector GlobalAccessMap; - - /// A map from each visited global to all of its global address instructions. - llvm::MapVector GlobalAddrMap; - - /// A map from each visited global let variable to the store instructions - /// which initialize it. - llvm::MapVector GlobalVarStore; - - /// A map for each visited global variable to the alloc instruction that - /// allocated space for it. - llvm::MapVector AllocGlobalStore; - - /// A set of visited global variables that for some reason we have decided is - /// not able to be optimized safely or for which we do not know how to - /// optimize safely. - /// - /// Once a global variable is in this set, we no longer will process it. - llvm::SmallPtrSet GlobalVarSkipProcessing; - - /// The set of blocks that this pass has determined to be inside a loop. - /// - /// This is used to mark any block that this pass has determined to be inside - /// a loop. - llvm::DenseSet LoopBlocks; - - /// The set of functions that have had their loops analyzed. - llvm::DenseSet LoopCheckedFunctions; - - /// Whether we have seen any "once" calls to callees that we currently don't - /// handle. - bool UnhandledOnceCallee = false; - - /// A map from a globalinit_func to the number of times "once" has called the - /// function. - llvm::DenseMap InitializerCount; - - llvm::SmallVector InstToRemove; - llvm::SmallVector GlobalsToRemove; - -public: - SILGlobalOpt(SILOptFunctionBuilder &FunctionBuilder, SILModule *M, - DominanceAnalysis *DA, SILPassManager *PM) - : FunctionBuilder(FunctionBuilder), Module(M), DA(DA), PM(PM) {} - - bool run(); - -protected: - /// Checks if a given global variable is assigned only once. - bool isAssignedOnlyOnceInInitializer(SILGlobalVariable *SILG, - SILFunction *globalAddrF); - - /// Reset all the maps of global variables. - void reset(); - - /// Collect all global variables. - void collect(); - - void collectUsesOfInstructionForDeletion(SILInstruction *inst); - - /// This is the main entrypoint for collecting global accesses. - void collectGlobalAccess(GlobalAddrInst *GAI); - - /// Returns true if we think that \p CurBB is inside a loop. - bool isInLoop(SILBasicBlock *CurBB); - - /// Given that we are trying to place initializers in new locations, see if - /// we can hoist the passed in apply \p AI out of any loops that it is - /// currently within. - ApplyInst *getHoistedApplyForInitializer( - ApplyInst *AI, DominanceInfo *DT, SILFunction *InitF, - SILFunction *ParentF, - llvm::DenseMap &ParentFuncs); - - /// Update UnhandledOnceCallee and InitializerCount by going through all - /// "once" calls. - void collectOnceCall(BuiltinInst *AI); - - /// Set the static initializer and remove "once" from addressor if a global - /// can be statically initialized. - bool optimizeInitializer(SILFunction *AddrF, GlobalInitCalls &Calls); - - /// If possible, remove global address instructions associated with the given - /// global. - bool tryRemoveGlobalAddr(SILGlobalVariable *global); - - /// If possible, remove global alloc instructions associated with the given - /// global. - bool tryRemoveGlobalAlloc(SILGlobalVariable *global, AllocGlobalInst *alloc); - - /// If a global has no uses, remove it. - bool tryRemoveUnusedGlobal(SILGlobalVariable *global); - - /// Optimize access to the global variable, which is known to have a constant - /// value. Replace all loads from the global address by invocations of a - /// getter that returns the value of this variable. - void optimizeGlobalAccess(SILGlobalVariable *SILG, StoreInst *SI); - - /// Replace loads from a global variable by the known value. - void replaceLoadsByKnownValue(SILFunction *InitF, - SILGlobalVariable *SILG, - GlobalInitCalls &Calls); -}; - -/// Helper class to copy only a set of SIL instructions providing in the -/// constructor. -class InstructionsCloner : public SILClonerWithScopes { - friend class SILInstructionVisitor; - friend class SILCloner; - - ArrayRef Insns; - -protected: - SILBasicBlock *FromBB, *DestBB; - -public: - /// A map of old to new available values. - SmallVector, 16> AvailVals; - - InstructionsCloner(SILFunction &F, - ArrayRef Insns, - SILBasicBlock *Dest = nullptr) - : SILClonerWithScopes(F), Insns(Insns), FromBB(nullptr), DestBB(Dest) {} - - void process(SILInstruction *I) { visit(I); } - - SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; } - - SILValue getMappedValue(SILValue Value) { - return SILCloner::getMappedValue(Value); - } - - void postProcess(SILInstruction *Orig, SILInstruction *Cloned) { - DestBB->push_back(Cloned); - SILClonerWithScopes::postProcess(Orig, Cloned); - auto origResults = Orig->getResults(), clonedResults = Cloned->getResults(); - assert(origResults.size() == clonedResults.size()); - for (auto i : indices(origResults)) - AvailVals.push_back(std::make_pair(origResults[i], clonedResults[i])); - } - - /// Clone all instructions from Insns into DestBB - void clone() { - for (auto I : Insns) - process(I); - } -}; - -} // end anonymous namespace - -/// Remove an unused global token used by once calls. -static void removeToken(SILValue Op) { - if (auto *ATPI = dyn_cast(Op)) { - Op = ATPI->getOperand(); - if (ATPI->use_empty()) - ATPI->eraseFromParent(); - } - - if (auto *GAI = dyn_cast(Op)) { - auto *Global = GAI->getReferencedGlobal(); - // If "global_addr token" is used more than one time, bail. - if (!(GAI->use_empty() || GAI->hasOneUse())) - return; - // If it is not a *_token global variable, bail. - if (!Global || !Global->getName().contains("_token")) - return; - GAI->getModule().eraseGlobalVariable(Global); - GAI->replaceAllUsesWithUndef(); - GAI->eraseFromParent(); - } -} - -// Update UnhandledOnceCallee and InitializerCount by going through all "once" -// calls. -void SILGlobalOpt::collectOnceCall(BuiltinInst *BI) { - if (UnhandledOnceCallee) - return; - - const BuiltinInfo &Builtin = Module->getBuiltinInfo(BI->getName()); - if (Builtin.ID != BuiltinValueKind::Once) - return; - - SILFunction *Callee = getCalleeOfOnceCall(BI); - if (!Callee) { - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: unhandled once callee\n"); - UnhandledOnceCallee = true; - return; - } - if (!Callee->isGlobalInitOnceFunction()) - return; - - // We currently disable optimizing the initializer if a globalinit_func - // is called by "once" from multiple locations. - if (!BI->getFunction()->isGlobalInit()) - // If a globalinit_func is called by "once" from a function that is not - // an addressor, we set count to 2 to disable optimizing the initializer. - InitializerCount[Callee] = 2; - else - ++InitializerCount[Callee]; -} - -static bool isPotentialStore(SILInstruction *inst) { - switch (inst->getKind()) { - case SILInstructionKind::LoadInst: - return false; - case SILInstructionKind::PointerToAddressInst: - case SILInstructionKind::StructElementAddrInst: - case SILInstructionKind::TupleElementAddrInst: - for (Operand *op : cast(inst)->getUses()) { - if (isPotentialStore(op->getUser())) - return true; - } - return false; - case SILInstructionKind::BeginAccessInst: - return cast(inst)->getAccessKind() != SILAccessKind::Read; - default: - return true; - } -} - -/// return true if this block is inside a loop. -bool SILGlobalOpt::isInLoop(SILBasicBlock *CurBB) { - SILFunction *F = CurBB->getParent(); - // Catch the common case in which we've already hoisted the initializer. - if (CurBB == &F->front()) - return false; - - if (LoopCheckedFunctions.insert(F).second) { - for (auto I = scc_begin(F); !I.isAtEnd(); ++I) { - if (I.hasCycle()) - for (SILBasicBlock *BB : *I) - LoopBlocks.insert(BB); - } - } - return LoopBlocks.count(CurBB); -} - -bool SILGlobalOpt::isAssignedOnlyOnceInInitializer(SILGlobalVariable *SILG, - SILFunction *globalAddrF) { - if (SILG->isLet()) - return true; - - // Don't replace loads from `var` globals when compiled with -Onone. - if (Module->getOptions().OptMode == OptimizationMode::NoOptimization) - return false; - - // If we should skip this, it is probably because there are multiple stores. - // Return false if there are multiple stores or no stores. - if (GlobalVarSkipProcessing.count(SILG) || !GlobalVarStore.count(SILG)) - return false; - - if (GlobalInitCallMap.count(globalAddrF)) { - for (ApplyInst *initCall : GlobalInitCallMap[globalAddrF]) { - for (auto *Op : getNonDebugUses(initCall)) { - if (isPotentialStore(Op->getUser())) - return false; - } - } - } - - // Otherwise, return true if this can't be used externally (false, otherwise). - return !isPossiblyUsedExternally(SILG->getLinkage(), - SILG->getModule().isWholeModule()); -} - -/// Replace loads from \a addr by the \p initVal of a global. -/// -/// Recursively walk over all uses of \p addr and look through address -/// projections. The \p initVal is an instruction in the static initializer of -/// a SILGlobalVariable. It is cloned into the current function with \p cloner. -static void replaceLoadsFromGlobal(SILValue addr, - SingleValueInstruction *initVal, - StaticInitCloner &cloner) { - for (Operand *use : addr->getUses()) { - SILInstruction *user = use->getUser(); - if (auto *load = dyn_cast(user)) { - SingleValueInstruction *clonedInitVal = cast(cloner.clone(initVal)); - load->replaceAllUsesWith(clonedInitVal); - continue; - } - if (auto *seai = dyn_cast(user)) { - auto *si = cast(initVal); - auto *member = cast( - si->getOperandForField(seai->getField())->get()); - replaceLoadsFromGlobal(seai, member, cloner); - continue; - } - if (auto *teai = dyn_cast(user)) { - auto *ti = cast(initVal); - auto *member = cast( - ti->getElement(teai->getFieldIndex())); - replaceLoadsFromGlobal(teai, member, cloner); - continue; - } - if (isa(user) || isa(user)) { - auto *svi = cast(user); - replaceLoadsFromGlobal(svi, initVal, cloner); - continue; - } - } -} - -/// Replace loads from a global variable by the known initial value. -void SILGlobalOpt:: -replaceLoadsByKnownValue(SILFunction *InitF, SILGlobalVariable *SILG, - GlobalInitCalls &Calls) { - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: replacing loads with known value for " - << SILG->getName() << '\n'); - - for (ApplyInst *initCall : Calls) { - auto *initVal = - dyn_cast(SILG->getStaticInitializerValue()); - if (!initVal) { - // This should never happen. Just to be on the safe side. - continue; - } - - StaticInitCloner cloner(initCall); - SmallVector insertedInsts; - cloner.setTrackingList(&insertedInsts); - if (!cloner.add(initVal)) - continue; - - // Replace all loads from the addressor with the initial value of the global. - replaceLoadsFromGlobal(initCall, initVal, cloner); - - // Remove all instructions which are dead now. - InstructionDeleter deleter; - deleter.recursivelyDeleteUsersIfDead(initCall); - if (initCall->use_empty()) { - // The call to the addressor is dead as well and can be removed. - auto *callee = dyn_cast(initCall->getCallee()); - deleter.forceDelete(initCall); - if (callee) - deleter.deleteIfDead(callee); - } - - // Constant folding the global value can enable other initializers to become - // constant, e.g. - // let a = 1 - // let b = a + 1 - ConstantFolder constFolder(FunctionBuilder, PM->getOptions().AssertConfig); - for (SILInstruction *inst : insertedInsts) { - constFolder.addToWorklist(inst); - } - constFolder.processWorkList(); - } - Calls.clear(); -} - -/// We analyze the body of globalinit_func to see if it can be statically -/// initialized. If yes, we set the initial value of the SILGlobalVariable and -/// remove the "once" call to globalinit_func from the addressor. -bool SILGlobalOpt::optimizeInitializer(SILFunction *AddrF, - GlobalInitCalls &Calls) { - if (UnhandledOnceCallee) - return false; - - // Find the initializer and the SILGlobalVariable. - BuiltinInst *CallToOnce; - - // If the addressor contains a single "once" call, it calls globalinit_func, - // and the globalinit_func is called by "once" from a single location, - // continue; otherwise bail. - auto *InitF = findInitializer(AddrF, CallToOnce); - if (!InitF || InitializerCount[InitF] > 1) - return false; - - // If the globalinit_func is trivial, continue; otherwise bail. - SingleValueInstruction *InitVal; - SILGlobalVariable *SILG = getVariableOfStaticInitializer(InitF, InitVal); - if (!SILG) - return false; - - auto expansion = ResilienceExpansion::Maximal; - if (hasPublicVisibility(SILG->getLinkage())) - expansion = ResilienceExpansion::Minimal; - - auto &tl = Module->Types.getTypeLowering( - SILG->getLoweredType(), - TypeExpansionContext::noOpaqueTypeArchetypesSubstitution(expansion)); - if (!tl.isLoadable()) - return false; - - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for " - << SILG->getName() << '\n'); - - // Remove "once" call from the addressor. - removeToken(CallToOnce->getOperand(0)); - InstructionDeleter deleter; - deleter.forceDeleteWithUsers(CallToOnce); - deleter.cleanupDeadInstructions(); - - // Create the constant initializer of the global variable. - StaticInitCloner::appendToInitializer(SILG, InitVal); - - if (isAssignedOnlyOnceInInitializer(SILG, AddrF)) { - replaceLoadsByKnownValue(InitF, SILG, Calls); - } - - HasChanged = true; - return true; -} - -static bool canBeChangedExternally(SILGlobalVariable *SILG) { - // Don't assume anything about globals which are imported from other modules. - if (isAvailableExternally(SILG->getLinkage())) - return true; - - // Use access specifiers from the declarations, - // if possible. - if (auto *Decl = SILG->getDecl()) { - switch (Decl->getEffectiveAccess()) { - case AccessLevel::Private: - case AccessLevel::FilePrivate: - return false; - case AccessLevel::Internal: - return !SILG->getModule().isWholeModule(); - case AccessLevel::Package: - case AccessLevel::Public: - case AccessLevel::Open: - return true; - } - } - - if (SILG->getLinkage() == SILLinkage::Private) - return false; - - if (SILG->getLinkage() == SILLinkage::Hidden - && SILG->getModule().isWholeModule()) { - return false; - } - - return true; -} - -static bool canBeUsedOrChangedExternally(SILGlobalVariable *global) { - if (global->isLet()) - return isPossiblyUsedExternally(global->getLinkage(), - global->getModule().isWholeModule()); - return canBeChangedExternally(global); -} - -static bool isSafeToRemove(SILGlobalVariable *global) { - return global->getDecl() && !canBeUsedOrChangedExternally(global); -} - -bool SILGlobalOpt::tryRemoveGlobalAlloc(SILGlobalVariable *global, - AllocGlobalInst *alloc) { - if (!isSafeToRemove(global)) - return false; - - // Make sure the global's address is never taken and we shouldn't skip this - // global. - if (GlobalVarSkipProcessing.count(global) || - (GlobalAddrMap[global].size() && - std::any_of(GlobalAddrMap[global].begin(), GlobalAddrMap[global].end(), - [=](GlobalAddrInst *addr) { - return std::find(InstToRemove.begin(), InstToRemove.end(), - addr) == InstToRemove.end(); - }))) - return false; - - InstToRemove.push_back(alloc); - return true; -} - -/// If there are no loads or accesses of a given global, then remove its -/// associated global addr and all associated instructions. -bool SILGlobalOpt::tryRemoveGlobalAddr(SILGlobalVariable *global) { - if (!isSafeToRemove(global)) - return false; - - if (GlobalVarSkipProcessing.count(global) || GlobalAccessMap[global].size()) - return false; - - // Check if the address is used in anything but a store. If any global_addr - // instruction associated with a global is used in anything but a store, we - // can't remove ANY global_addr instruction associated with that global. - for (auto *addr : GlobalAddrMap[global]) { - for (auto *use : addr->getUses()) { - if (!isa(use->getUser())) - return false; - } - } - - // Now that it's safe, remove all global addresses associated with this global - for (auto *addr : GlobalAddrMap[global]) { - InstToRemove.push_back(addr); - } - - return true; -} - -bool SILGlobalOpt::tryRemoveUnusedGlobal(SILGlobalVariable *global) { - if (!isSafeToRemove(global)) - return false; - - if (GlobalVarSkipProcessing.count(global)) - return false; - - // If this global is used, check if the user is going to be removed. - // Make sure none of the removed instructions are the same as this global's - // alloc instruction - if (AllocGlobalStore.count(global) && - std::none_of(InstToRemove.begin(), InstToRemove.end(), - [=](SILInstruction *inst) { - return AllocGlobalStore[global] == inst; - })) - return false; - - if (GlobalVarStore.count(global) && - std::none_of( - InstToRemove.begin(), InstToRemove.end(), - [=](SILInstruction *inst) { return GlobalVarStore[global] == inst; })) - return false; - - // Check if any of the global_addr instructions associated with this global - // aren't going to be removed. In that case, we need to keep the global. - if (GlobalAddrMap[global].size() && - std::any_of(GlobalAddrMap[global].begin(), GlobalAddrMap[global].end(), - [=](GlobalAddrInst *addr) { - return std::find(InstToRemove.begin(), InstToRemove.end(), - addr) == InstToRemove.end(); - })) - return false; - - if (GlobalAccessMap[global].size() && - std::any_of(GlobalAccessMap[global].begin(), - GlobalAccessMap[global].end(), [=](BeginAccessInst *access) { - return std::find(InstToRemove.begin(), InstToRemove.end(), - access) == InstToRemove.end(); - })) - return false; - - GlobalsToRemove.push_back(global); - return true; -} - -/// If this is a read from a global let variable, map it. -void SILGlobalOpt::collectGlobalAccess(GlobalAddrInst *GAI) { - auto *SILG = GAI->getReferencedGlobal(); - if (!SILG) - return; - - if (!SILG->getDecl()) - return; - - GlobalAddrMap[SILG].push_back(GAI); - - if (!SILG->isLet()) { - // We cannot determine the value for global variables which could be - // changed externally at run-time. - if (canBeChangedExternally(SILG)) - return; - } - - if (GlobalVarSkipProcessing.count(SILG)) - return; - - auto *F = GAI->getFunction(); - - if (!SILG->getLoweredType().isTrivial(*F)) { - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: type is not trivial: " - << SILG->getName() << '\n'); - GlobalVarSkipProcessing.insert(SILG); - return; - } - - // Ignore any accesses inside addressors for SILG - auto GlobalVar = getVariableOfGlobalInit(F); - if (GlobalVar == SILG) - return; - - for (auto *Op : getNonDebugUses(GAI)) { - SILInstruction *user = Op->getUser(); - auto *SI = dyn_cast(user); - if (SI && SI->getDest() == GAI && GlobalVarStore.count(SILG) == 0) { - // The one and only store to global. - GlobalVarStore[SILG] = SI; - continue; - } - if (auto *beginAccess = dyn_cast(user)) { - GlobalAccessMap[SILG].push_back(beginAccess); - } - if (isPotentialStore(user)) { - // An unknown store or the second store we see. - // If there are multiple stores to a global we cannot reason about the - // value. - GlobalVarSkipProcessing.insert(SILG); - } - } -} - -// Optimize access to the global variable, which is known to have a constant -// value. Replace all loads from the global address by invocations of a getter -// that returns the value of this variable. -void SILGlobalOpt::optimizeGlobalAccess(SILGlobalVariable *SILG, - StoreInst *SI) { - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: use static initializer for " - << SILG->getName() << '\n'); - - if (GlobalVarSkipProcessing.count(SILG)) { - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: already decided to skip: " - << SILG->getName() << '\n'); - return; - } - - if (GlobalAddrMap[SILG].empty()) { - LLVM_DEBUG(llvm::dbgs() << "GlobalOpt: not in load map: " - << SILG->getName() << '\n'); - return; - } - - auto *initVal = dyn_cast(SI->getSrc()); - if (!initVal) - return; - - SmallVector unused; - if (!analyzeStaticInitializer(initVal, unused)) - return; - - // Iterate over all loads and replace them by values. - for (auto *globalAddr : GlobalAddrMap[SILG]) { - if (globalAddr->getFunction()->isSerialized()) - continue; - - StaticInitCloner cloner(globalAddr); - if (!cloner.add(initVal)) - continue; - - // Replace all loads from the addressor with the initial value of the global. - replaceLoadsFromGlobal(globalAddr, initVal, cloner); - - HasChanged = true; - } -} - -void SILGlobalOpt::reset() { - AllocGlobalStore.clear(); - GlobalVarStore.clear(); - GlobalAddrMap.clear(); - GlobalAccessMap.clear(); - GlobalInitCallMap.clear(); -} - -void SILGlobalOpt::collect() { - for (auto &F : *Module) { - // Make sure to create an entry. This is important in case a global variable - // (e.g. a public one) is not used inside the same module. - if (F.isGlobalInit()) - (void)GlobalInitCallMap[&F]; - - // Cache cold blocks per function. - ColdBlockInfo ColdBlocks(DA); - for (auto &BB : F) { - bool IsCold = ColdBlocks.isCold(&BB); - for (auto &I : BB) { - if (auto *BI = dyn_cast(&I)) { - collectOnceCall(BI); - continue; - } - - if (auto *AI = dyn_cast(&I)) { - if (!IsCold) { - if (SILFunction *callee = AI->getReferencedFunctionOrNull()) { - if (callee->isGlobalInit() && ApplySite(AI).canOptimize()) - GlobalInitCallMap[callee].push_back(AI); - } - } - continue; - } - - if (auto *GAI = dyn_cast(&I)) { - collectGlobalAccess(GAI); - continue; - } - - if (auto *allocGlobal = dyn_cast(&I)) { - AllocGlobalStore[allocGlobal->getReferencedGlobal()] = allocGlobal; - continue; - } - } - } - } -} - -bool SILGlobalOpt::run() { - // Collect all the global variables and associated instructions. - collect(); - - // Iterate until a fixed point to be able to optimize globals which depend - // on other globals, e.g. - // let a = 1 - // let b = a + 10 - // let c = b + 5 - // ... - bool changed = false; - do { - changed = false; - for (auto &InitCalls : GlobalInitCallMap) { - // Try to create a static initializer for the global and replace all uses - // of the global by this constant value. - changed |= optimizeInitializer(InitCalls.first, InitCalls.second); - } - } while (changed); - - // This is similar to optimizeInitializer, but it's for globals which are - // initialized in the "main" function and not by an initializer function. - for (auto &Init : GlobalVarStore) { - // Don't optimize functions that are marked with the opt.never attribute. - if (!Init.second->getFunction()->shouldOptimize()) - continue; - - optimizeGlobalAccess(Init.first, Init.second); - } - - /// Don't perform the remaining optimizations when compiled with -Onone. - if (Module->getOptions().OptMode == OptimizationMode::NoOptimization) - return HasChanged; - - SmallVector addrGlobals; - for (auto &addrPair : GlobalAddrMap) { - // Don't optimize functions that are marked with the opt.never attribute. - bool shouldOptimize = true; - for (auto *addr : addrPair.second) { - if (!addr->getFunction()->shouldOptimize()) { - shouldOptimize = false; - break; - } - } - if (!shouldOptimize) - continue; - - addrGlobals.push_back(addrPair.first); - } - - for (auto *global : addrGlobals) { - HasChanged |= tryRemoveGlobalAddr(global); - } - - SmallVector, 12> - globalAllocPairs; - for (auto &alloc : AllocGlobalStore) { - if (!alloc.second->getFunction()->shouldOptimize()) - continue; - globalAllocPairs.push_back(std::make_pair(alloc.first, alloc.second)); - } - - for (auto &allocPair : globalAllocPairs) { - HasChanged |= tryRemoveGlobalAlloc(allocPair.first, allocPair.second); - } - if (HasChanged) { - // Erase the instructions that we have marked for deletion. - InstructionDeleter deleter; - for (auto *inst : InstToRemove) { - deleter.forceDeleteWithUsers(inst); - } - deleter.cleanupDeadInstructions(); - } else { - assert(InstToRemove.empty()); - } - - for (auto &global : Module->getSILGlobals()) { - HasChanged |= tryRemoveUnusedGlobal(&global); - } - - for (auto *global : GlobalsToRemove) { - Module->eraseGlobalVariable(global); - } - - // Reset in case we re-run this function (when HasChanged is true). - reset(); - return HasChanged; -} - -//===----------------------------------------------------------------------===// -// Top Level Entry Point -//===----------------------------------------------------------------------===// - -namespace { - -class SILGlobalOptPass : public SILModuleTransform { - void run() override { - auto *DA = PM->getAnalysis(); - SILOptFunctionBuilder FunctionBuilder(*this); - if (SILGlobalOpt(FunctionBuilder, getModule(), DA, PM).run()) { - invalidateAll(); - } - } -}; - -} // end anonymous namespace - -SILTransform *swift::createGlobalOpt() { - return new SILGlobalOptPass(); -} diff --git a/test/SIL/OwnershipVerifier/load_borrow_verify.sil b/test/SIL/OwnershipVerifier/load_borrow_verify.sil index cbcfe9f8716e0..2e2eb92ab8396 100644 --- a/test/SIL/OwnershipVerifier/load_borrow_verify.sil +++ b/test/SIL/OwnershipVerifier/load_borrow_verify.sil @@ -4,8 +4,6 @@ // The verification should not bail-out on these, but there's no way // to check that. -// RUN: %target-sil-opt -enable-sil-verify-all %s -global-opt | %FileCheck %s - sil_stage canonical import Builtin @@ -16,54 +14,6 @@ public enum FakeOptional { case some(T) } -private var testGlobal: Int64 - -sil_global private @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_token0 : $Builtin.Word - -sil_global private @$s4test10testGlobalSivp : $Int64 - -sil private @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_func0 : $@convention(c) () -> () { -bb0: - alloc_global @$s4test10testGlobalSivp - %1 = global_addr @$s4test10testGlobalSivp : $*Int64 - %2 = integer_literal $Builtin.Int64, 27 - %3 = struct $Int64 (%2 : $Builtin.Int64) - store %3 to %1 : $*Int64 - %5 = tuple () - return %5 : $() -} - -sil hidden [global_init] @$s4test10testGlobalSivau : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_token0 : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_func0 : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$s4test10testGlobalSivp : $*Int64 - %5 = address_to_pointer %4 : $*Int64 to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// CHECK-LABEL: sil @dont_propagate_global_with_multiple_writes -// CHECK: [[V:%[0-9]+]] = load -// CHECK: return [[V]] -// CHECK: } // end sil function 'dont_propagate_global_with_multiple_writes' -sil @dont_propagate_global_with_multiple_writes : $@convention(thin) (Int64) -> Int64 { -bb0(%0 : $Int64): - %2 = function_ref @$s4test10testGlobalSivau : $@convention(thin) () -> Builtin.RawPointer - %3 = apply %2() : $@convention(thin) () -> Builtin.RawPointer - %4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Int64 - %5 = integer_literal $Builtin.Int64, 42 - %6 = struct $Int64 (%5 : $Builtin.Int64) - %7 = begin_access [modify] [dynamic] [no_nested_conflict] %4 : $*Int64 - store %6 to %7 : $*Int64 - end_access %7 : $*Int64 - %33 = begin_access [read] [dynamic] [no_nested_conflict] %4 : $*Int64 - %35 = load %33 : $*Int64 - end_access %33 : $*Int64 - return %35 : $Int64 -} - // CHECK-LABEL: sil [ossa] @test_borrow_init_enum_addr : $@convention(thin) (@owned AnyObject) -> () { // CHECK: bb0(%0 : @owned $AnyObject): // CHECK: [[ALLOC:%.*]] = alloc_stack $Optional diff --git a/test/SILOptimizer/globalopt.sil b/test/SILOptimizer/globalopt.sil deleted file mode 100644 index 0892873906699..0000000000000 --- a/test/SILOptimizer/globalopt.sil +++ /dev/null @@ -1,55 +0,0 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -global-opt | %FileCheck %s - -sil_stage canonical - -import Builtin -import Swift - -private var testGlobal: Int64 - -sil_global private @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_token0 : $Builtin.Word - -sil_global private @$s4test10testGlobalSivp : $Int64 - -sil private @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_func0 : $@convention(c) () -> () { -bb0: - alloc_global @$s4test10testGlobalSivp - %1 = global_addr @$s4test10testGlobalSivp : $*Int64 - %2 = integer_literal $Builtin.Int64, 27 - %3 = struct $Int64 (%2 : $Builtin.Int64) - store %3 to %1 : $*Int64 - %5 = tuple () - return %5 : $() -} - -sil hidden [global_init] @$s4test10testGlobalSivau : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_token0 : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_func0 : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$s4test10testGlobalSivp : $*Int64 - %5 = address_to_pointer %4 : $*Int64 to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// CHECK-LABEL: sil @dont_propagate_global_with_multiple_writes -// CHECK: [[V:%[0-9]+]] = load -// CHECK: return [[V]] -// CHECK: } // end sil function 'dont_propagate_global_with_multiple_writes' -sil @dont_propagate_global_with_multiple_writes : $@convention(thin) (Int64) -> Int64 { -bb0(%0 : $Int64): - %2 = function_ref @$s4test10testGlobalSivau : $@convention(thin) () -> Builtin.RawPointer - %3 = apply %2() : $@convention(thin) () -> Builtin.RawPointer - %4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Int64 - %5 = integer_literal $Builtin.Int64, 42 - %6 = struct $Int64 (%5 : $Builtin.Int64) - %7 = begin_access [modify] [dynamic] [no_nested_conflict] %4 : $*Int64 - store %6 to %7 : $*Int64 - end_access %7 : $*Int64 - %33 = begin_access [read] [dynamic] [no_nested_conflict] %4 : $*Int64 - %35 = load %33 : $*Int64 - end_access %33 : $*Int64 - return %35 : $Int64 -} - diff --git a/test/SILOptimizer/globalopt_ossa.sil b/test/SILOptimizer/globalopt_ossa.sil deleted file mode 100644 index 43b946993d734..0000000000000 --- a/test/SILOptimizer/globalopt_ossa.sil +++ /dev/null @@ -1,55 +0,0 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -global-opt | %FileCheck %s - -sil_stage canonical - -import Builtin -import Swift - -private var testGlobal: Int64 - -sil_global private @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_token0 : $Builtin.Word - -sil_global private @$s4test10testGlobalSivp : $Int64 - -sil private [ossa] @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_func0 : $@convention(c) () -> () { -bb0: - alloc_global @$s4test10testGlobalSivp - %1 = global_addr @$s4test10testGlobalSivp : $*Int64 - %2 = integer_literal $Builtin.Int64, 27 - %3 = struct $Int64 (%2 : $Builtin.Int64) - store %3 to [trivial] %1 : $*Int64 - %5 = tuple () - return %5 : $() -} - -sil hidden [global_init] [ossa] @$s4test10testGlobalSivau : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_token0 : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_33_00F4D2139E6BDDFEC71E5005B67B5674_func0 : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$s4test10testGlobalSivp : $*Int64 - %5 = address_to_pointer %4 : $*Int64 to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// CHECK-LABEL: sil [ossa] @dont_propagate_global_with_multiple_writes -// CHECK: [[V:%[0-9]+]] = load -// CHECK: return [[V]] -// CHECK: } // end sil function 'dont_propagate_global_with_multiple_writes' -sil [ossa] @dont_propagate_global_with_multiple_writes : $@convention(thin) (Int64) -> Int64 { -bb0(%0 : $Int64): - %2 = function_ref @$s4test10testGlobalSivau : $@convention(thin) () -> Builtin.RawPointer - %3 = apply %2() : $@convention(thin) () -> Builtin.RawPointer - %4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Int64 - %5 = integer_literal $Builtin.Int64, 42 - %6 = struct $Int64 (%5 : $Builtin.Int64) - %7 = begin_access [modify] [dynamic] [no_nested_conflict] %4 : $*Int64 - store %6 to [trivial] %7 : $*Int64 - end_access %7 : $*Int64 - %33 = begin_access [read] [dynamic] [no_nested_conflict] %4 : $*Int64 - %35 = load [trivial] %33 : $*Int64 - end_access %33 : $*Int64 - return %35 : $Int64 -} - diff --git a/test/SILOptimizer/globalopt_trivial_nontrivial.sil b/test/SILOptimizer/globalopt_trivial_nontrivial.sil deleted file mode 100644 index 8f094f633febb..0000000000000 --- a/test/SILOptimizer/globalopt_trivial_nontrivial.sil +++ /dev/null @@ -1,133 +0,0 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -global-opt | %FileCheck %s - -// This tests GlobalOpt of a trivial global variable -// Loads of trivial global variable initialized with a constant is replaced with the constant in GlobalOpt -// This also tests GlobalOpt to be a no-op for a non-trivial global variable used in the same way -// The difference in GlobalOpt for trivial v/s non-trivial values is shown in $bartrivial and $barnontrivial - -sil_stage canonical - -import Builtin -import Swift -import SwiftShims - -public struct TStruct { - let x: Int32 - init(x: Int32) -} - -let trivialglobal: TStruct - -public class TClass { - final let x: Int32 - init(x: Int32) - deinit -} - -let nontrivialglobal: TClass - -// CHECK-LABEL: sil_global hidden [let] @$trivialglobal : $TStruct = { -// CHECK: [[CONST:%.*]] = integer_literal $Builtin.Int32, 10 -// CHECK: [[INT:%.*]] = struct $Int32 ([[CONST]] : $Builtin.Int32) -// CHECK: %initval = struct $TStruct ([[INT]] : $Int32) -sil_global private @globalinit_trivialglobal_token : $Builtin.Word - -sil_global hidden [let] @$trivialglobal : $TStruct - -sil_global private @globalinit_nontrivialglobal_token : $Builtin.Word - -sil_global hidden [let] @$nontrivialglobal : $TClass - -sil private [global_init_once_fn] @globalinit_trivialglobal_func : $@convention(c) () -> () { -bb0: - alloc_global @$trivialglobal - %1 = global_addr @$trivialglobal : $*TStruct - %2 = integer_literal $Builtin.Int32, 10 - %3 = struct $Int32 (%2 : $Builtin.Int32) - %4 = struct $TStruct (%3 : $Int32) - store %4 to %1 : $*TStruct - %6 = tuple () - return %6 : $() -} - -// CHECK-LABEL: sil hidden [global_init] @$trivialglobal_unsafemutableaddressor : -// CHECK: [[GLOBL:%.*]] = global_addr @$trivialglobal : $*TStruct -// CHECK: [[GLOBL_ADDR:%.*]] = address_to_pointer [[GLOBL]] : $*TStruct to $Builtin.RawPointer -// CHECK: return [[GLOBL_ADDR]] : $Builtin.RawPointer -// CHECK: } // end sil function '$trivialglobal_unsafemutableaddressor' -sil hidden [global_init] @$trivialglobal_unsafemutableaddressor : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_trivialglobal_token : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_trivialglobal_func : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$trivialglobal : $*TStruct - %5 = address_to_pointer %4 : $*TStruct to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// $bartrivial's access to the trivial global variable via the accessor is optimized to the rhs of the init value - -// CHECK-LABEL: sil hidden [noinline] @$bartrivial : -// CHECK: [[CONST:%.*]] = integer_literal $Builtin.Int32, 10 -// CHECK: [[INT:%.*]] = struct $Int32 ([[CONST]] : $Builtin.Int32) -// CHECK: return [[INT]] : $Int32 -// CHECK-LABEL: } // end sil function '$bartrivial' -sil hidden [noinline] @$bartrivial : $@convention(thin) () -> Int32 { -bb0: - %0 = function_ref @$trivialglobal_unsafemutableaddressor : $@convention(thin) () -> Builtin.RawPointer - %1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer - %2 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*TStruct - %3 = struct_element_addr %2 : $*TStruct, #TStruct.x - %4 = load %3 : $*Int32 - return %4 : $Int32 -} - -// CHECK-LABEL: sil private [global_init_once_fn] @globalinit_nontrivialglobal_func : -// CHECK: alloc_global @$nontrivialglobal -// CHECK: [[GLOBL_ADDR:%.*]] = global_addr @$nontrivialglobal : $*TClass -// CHECK: [[REF:%.*]] = alloc_ref $TClass -// CHECK: store [[REF]] to [[GLOBL_ADDR]] : $*TClass -// CHECK: } // end sil function 'globalinit_nontrivialglobal_func' -sil private [global_init_once_fn] @globalinit_nontrivialglobal_func : $@convention(c) () -> () { -bb0: - alloc_global @$nontrivialglobal - %1 = global_addr @$nontrivialglobal : $*TClass - %2 = integer_literal $Builtin.Int32, 10 - %3 = struct $Int32 (%2 : $Builtin.Int32) - %4 = alloc_ref $TClass - %7 = ref_element_addr %4 : $TClass, #TClass.x - store %3 to %7 : $*Int32 - store %4 to %1 : $*TClass - %10 = tuple () - return %10 : $() -} - -sil hidden [global_init] @$nontrivialglobal_unsafemutableaccessor : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_nontrivialglobal_token : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_nontrivialglobal_func : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$nontrivialglobal : $*TClass - %5 = address_to_pointer %4 : $*TClass to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// $barnontrivial's access to the non-trivial global variable does not get optimized away - -// CHECK-LABEL: sil hidden [noinline] @$barnontrivial : -// CHECK: [[FUNC_REF:%.*]] = function_ref @$nontrivialglobal_unsafemutableaccessor : -// CHECK: [[APPLY:%.*]] = apply [[FUNC_REF]]() : -// CHECK-LABEL: } // end sil function '$barnontrivial' -sil hidden [noinline] @$barnontrivial : $@convention(thin) () -> Int32 { -bb0: - %0 = function_ref @$nontrivialglobal_unsafemutableaccessor : $@convention(thin) () -> Builtin.RawPointer - %1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer - %2 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*TClass - %3 = load %2 : $*TClass - %4 = ref_element_addr %3 : $TClass, #TClass.x - %5 = load %4 : $*Int32 - return %5 : $Int32 -} - diff --git a/test/SILOptimizer/globalopt_trivial_nontrivial_ossa.sil b/test/SILOptimizer/globalopt_trivial_nontrivial_ossa.sil deleted file mode 100644 index e7de17d4bac9e..0000000000000 --- a/test/SILOptimizer/globalopt_trivial_nontrivial_ossa.sil +++ /dev/null @@ -1,160 +0,0 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -global-opt | %FileCheck %s -sil_stage canonical - -import Builtin -import Swift -import SwiftShims - -public struct TStruct { - let x: Int32 - init(x: Int32) -} - -let trivialglobal: TStruct - -public class TClass { - final let x: Int32 - init(x: Int32) - deinit -} - -struct GenericStruct { - var x: T -} - -let nontrivialglobal: TClass - -// CHECK-LABEL: sil_global hidden [let] @$trivialglobal : $TStruct = { -// CHECK: [[CONST:%.*]] = integer_literal $Builtin.Int32, 10 -// CHECK: [[INT:%.*]] = struct $Int32 ([[CONST]] : $Builtin.Int32) -// CHECK: %initval = struct $TStruct ([[INT]] : $Int32) -sil_global private @globalinit_trivialglobal_token : $Builtin.Word - -sil_global hidden [let] @$trivialglobal : $TStruct - -sil_global private @globalinit_nontrivialglobal_token : $Builtin.Word - -sil_global hidden [let] @$nontrivialglobal : $TClass - -sil_global hidden [let] @empty_global : $GenericStruct<()> -sil_global private @empty_global_token : $Builtin.Word - -sil private [global_init_once_fn] [ossa] @globalinit_trivialglobal_func : $@convention(c) () -> () { -bb0: - alloc_global @$trivialglobal - %1 = global_addr @$trivialglobal : $*TStruct - %2 = integer_literal $Builtin.Int32, 10 - %3 = struct $Int32 (%2 : $Builtin.Int32) - %4 = struct $TStruct (%3 : $Int32) - store %4 to [trivial] %1 : $*TStruct - %6 = tuple () - return %6 : $() -} - -// CHECK-LABEL: sil hidden [global_init] [ossa] @$trivialglobal_unsafemutableaddressor : -// CHECK: [[GLOBL:%.*]] = global_addr @$trivialglobal : $*TStruct -// CHECK: [[GLOBL_ADDR:%.*]] = address_to_pointer [[GLOBL]] : $*TStruct to $Builtin.RawPointer -// CHECK: return [[GLOBL_ADDR]] : $Builtin.RawPointer -// CHECK: } // end sil function '$trivialglobal_unsafemutableaddressor' -sil hidden [global_init] [ossa] @$trivialglobal_unsafemutableaddressor : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_trivialglobal_token : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_trivialglobal_func : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$trivialglobal : $*TStruct - %5 = address_to_pointer %4 : $*TStruct to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// CHECK-LABEL: sil hidden [noinline] [ossa] @$bartrivial : -// CHECK: [[CONST:%.*]] = integer_literal $Builtin.Int32, 10 -// CHECK: [[INT:%.*]] = struct $Int32 ([[CONST]] : $Builtin.Int32) -// CHECK: return [[INT]] : $Int32 -// CHECK-LABEL: } // end sil function '$bartrivial' -sil hidden [noinline] [ossa] @$bartrivial : $@convention(thin) () -> Int32 { -bb0: - %0 = function_ref @$trivialglobal_unsafemutableaddressor : $@convention(thin) () -> Builtin.RawPointer - %1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer - %2 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*TStruct - %3 = struct_element_addr %2 : $*TStruct, #TStruct.x - %4 = load [trivial] %3 : $*Int32 - return %4 : $Int32 -} - -// CHECK-LABEL: sil private [global_init_once_fn] [ossa] @globalinit_nontrivialglobal_func : -// CHECK: alloc_global @$nontrivialglobal -// CHECK: [[GLOBL_ADDR:%.*]] = global_addr @$nontrivialglobal : $*TClass -// CHECK: [[REF:%.*]] = alloc_ref $TClass -// CHECK: store [[REF]] to [init] [[GLOBL_ADDR]] : $*TClass -// CHECK: } // end sil function 'globalinit_nontrivialglobal_func' -sil private [global_init_once_fn] [ossa] @globalinit_nontrivialglobal_func : $@convention(c) () -> () { -bb0: - alloc_global @$nontrivialglobal - %1 = global_addr @$nontrivialglobal : $*TClass - %2 = integer_literal $Builtin.Int32, 10 - %3 = struct $Int32 (%2 : $Builtin.Int32) - %4 = alloc_ref $TClass - %5 = begin_borrow %4 : $TClass - %6 = ref_element_addr %5 : $TClass, #TClass.x - store %3 to [trivial] %6 : $*Int32 - end_borrow %5 : $TClass - store %4 to [init] %1 : $*TClass - %10 = tuple () - return %10 : $() -} - -sil hidden [global_init] [ossa] @$nontrivialglobal_unsafemutableaccessor : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @globalinit_nontrivialglobal_token : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_nontrivialglobal_func : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @$nontrivialglobal : $*TClass - %5 = address_to_pointer %4 : $*TClass to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - -// CHECK-LABEL: sil hidden [noinline] [ossa] @$barnontrivial : -// CHECK: [[FUNC_REF:%.*]] = function_ref @$nontrivialglobal_unsafemutableaccessor : -// CHECK: [[APPLY:%.*]] = apply [[FUNC_REF]]() : -// CHECK-LABEL: } // end sil function '$barnontrivial' -sil hidden [noinline] [ossa] @$barnontrivial : $@convention(thin) () -> Int32 { -bb0: - %0 = function_ref @$nontrivialglobal_unsafemutableaccessor : $@convention(thin) () -> Builtin.RawPointer - %1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer - %2 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*TClass - %3 = load [copy] %2 : $*TClass - %3b = begin_borrow %3 : $TClass - %4 = ref_element_addr %3b : $TClass, #TClass.x - %5 = load [trivial] %4 : $*Int32 - end_borrow %3b : $TClass - destroy_value %3 : $TClass - return %5 : $Int32 -} - -// Check that we don't crash on an initializer struct with an "undef" operand. -// CHECK-LABEL: sil hidden [global_init_once_fn] [ossa] @globalinit_empty_global : -// CHECK: struct $GenericStruct<()> (undef : $()) -// CHECK-LABEL: } // end sil function 'globalinit_empty_global' -sil hidden [global_init_once_fn] [ossa] @globalinit_empty_global : $@convention(c) () -> () { -bb0: - alloc_global @empty_global - %1 = global_addr @empty_global : $*GenericStruct<()> - %2 = struct $GenericStruct<()> (undef : $()) - store %2 to [trivial] %1 : $*GenericStruct<()> - %4 = tuple () - return %4 : $() -} - -sil hidden [global_init] [ossa] @empty_global_accessor : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %0 = global_addr @empty_global_token : $*Builtin.Word - %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer - %2 = function_ref @globalinit_empty_global : $@convention(c) () -> () - %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $() - %4 = global_addr @empty_global : $*GenericStruct<()> - %5 = address_to_pointer %4 : $*GenericStruct<()> to $Builtin.RawPointer - return %5 : $Builtin.RawPointer -} - diff --git a/test/SILOptimizer/static_initializer.sil b/test/SILOptimizer/static_initializer.sil deleted file mode 100644 index dbc5e28526a4f..0000000000000 --- a/test/SILOptimizer/static_initializer.sil +++ /dev/null @@ -1,65 +0,0 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -global-opt | %FileCheck %s - -import Builtin -import Swift - -struct Inner { - let x: Int32 -} - -struct Outer { - let a: Int32 - let b: Inner - let c: Int32 -} - -// Check that token0 gets eliminated as it is not used after removing the once call. -// CHECK-NOT: sil_global private @globalinit_token0 : $Builtin.Word -sil_global private @globalinit_token0 : $Builtin.Word - -// CHECK: sil_global @_Tv2ch1xSi : $Outer = { -// CHECK-NEXT: %0 = integer_literal $Builtin.Int32, 2 -// CHECK-NEXT: %1 = struct $Int32 (%0 : $Builtin.Int32) -// CHECK-NEXT: %2 = struct $Inner (%1 : $Int32) -// CHECK-NEXT: %initval = struct $Outer (%1 : $Int32, %2 : $Inner, %1 : $Int32) -// CHECK-NEXT: } -sil_global @_Tv2ch1xSi : $Outer - -// CHECK-LABEL: sil private [global_init_once_fn] @globalinit_func0 : $@convention(c) () -> () { -sil private [global_init_once_fn] @globalinit_func0 : $@convention(c) () -> () { -bb0: - %0 = global_addr @_Tv2ch1xSi : $*Outer - %1 = integer_literal $Builtin.Int32, 2 - %2 = struct $Int32 (%1 : $Builtin.Int32) - %3 = struct $Inner (%2 : $Int32) - %4 = struct $Outer (%2 : $Int32, %3 : $Inner, %2 : $Int32) - store %4 to %0 : $*Outer - %r = tuple () - return %r : $() -} - -// CHECK-LABEL: sil [global_init] @_TF2cha1xSi : $@convention(thin) () -> Builtin.RawPointer { -// CHECK-NEXT: bb0: -// CHECK-NEXT: global_addr @_Tv2ch1xSi : $*Outer -// CHECK-NEXT: address_to_pointer -// CHECK-NEXT: return -sil [global_init] @_TF2cha1xSi : $@convention(thin) () -> Builtin.RawPointer { -bb0: - %1 = global_addr @globalinit_token0 : $*Builtin.Word - %2 = address_to_pointer %1 : $*Builtin.Word to $Builtin.RawPointer - %3 = function_ref @globalinit_func0 : $@convention(c) () -> () - %5 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $() - %6 = global_addr @_Tv2ch1xSi : $*Outer - %7 = address_to_pointer %6 : $*Outer to $Builtin.RawPointer - return %7 : $Builtin.RawPointer -} - -// CHECK-LABEL: sil @_TF2ch1fFT_Si : $@convention(thin) () -> Outer { -sil @_TF2ch1fFT_Si : $@convention(thin) () -> Outer { -bb0: - %0 = function_ref @_TF2cha1xSi : $@convention(thin) () -> Builtin.RawPointer - %1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer - %2 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*Outer - %3 = load %2 : $*Outer - return %3 : $Outer -}