From eaabcfd933d6807399de0a0048459683c5c38aea Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Fri, 29 Dec 2023 18:54:05 +0100 Subject: [PATCH 1/7] IRGen: restrict generation of read-only static objects to Array buffers Currently only arrays can be put into a read-only data section. "Regular" classes have dynamically initialized metadata, which needs to be stored into the isa field at runtime. --- lib/IRGen/GenConstant.cpp | 2 +- lib/IRGen/GenDecl.cpp | 6 +++--- lib/IRGen/IRGenModule.cpp | 12 +++++++++++- lib/IRGen/IRGenModule.h | 2 +- lib/IRGen/IRGenSIL.cpp | 2 +- lib/IRGen/Linking.cpp | 2 +- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/IRGen/GenConstant.cpp b/lib/IRGen/GenConstant.cpp index 40830e39bf4c9..2325db78c87bb 100644 --- a/lib/IRGen/GenConstant.cpp +++ b/lib/IRGen/GenConstant.cpp @@ -432,7 +432,7 @@ llvm::Constant *irgen::emitConstantObject(IRGenModule &IGM, ObjectInst *OI, // Construct the object header. llvm::StructType *ObjectHeaderTy = cast(sTy->getElementType(0)); - if (IGM.canMakeStaticObjectsReadOnly()) { + if (IGM.canMakeStaticObjectReadOnly(OI->getType())) { if (!IGM.swiftImmortalRefCount) { auto *var = new llvm::GlobalVariable(IGM.Module, IGM.Int8Ty, /*constant*/ true, llvm::GlobalValue::ExternalLinkage, diff --git a/lib/IRGen/GenDecl.cpp b/lib/IRGen/GenDecl.cpp index af062584cdd7d..6d78721c79e1b 100644 --- a/lib/IRGen/GenDecl.cpp +++ b/lib/IRGen/GenDecl.cpp @@ -2804,7 +2804,7 @@ Address IRGenModule::getAddrOfSILGlobalVariable(SILGlobalVariable *var, if (initVal) { gvar->setInitializer(initVal); if (var->isLet() || - (var->isInitializedObject() && canMakeStaticObjectsReadOnly())) { + (var->isInitializedObject() && canMakeStaticObjectReadOnly(var->getLoweredType()))) { gvar->setConstant(true); } } else { @@ -2814,7 +2814,7 @@ Address IRGenModule::getAddrOfSILGlobalVariable(SILGlobalVariable *var, } llvm::Constant *addr = gvar; - if (var->isInitializedObject() && !canMakeStaticObjectsReadOnly()) { + if (var->isInitializedObject() && !canMakeStaticObjectReadOnly(var->getLoweredType())) { // Project out the object from the container. llvm::Constant *Indices[2] = { llvm::ConstantExpr::getIntegerValue(Int32Ty, APInt(32, 0)), @@ -2842,7 +2842,7 @@ llvm::Constant *IRGenModule::getGlobalInitValue(SILGlobalVariable *var, StructLayout *layout = StaticObjectLayouts[var].get(); ObjectInst *oi = cast(var->getStaticInitializerValue()); llvm::Constant *initVal = emitConstantObject(*this, oi, layout); - if (!canMakeStaticObjectsReadOnly()) { + if (!canMakeStaticObjectReadOnly(var->getLoweredType())) { // A statically initialized object must be placed into a container struct // because the swift_initStaticObject needs a swift_once_t at offset -1: // struct Container { diff --git a/lib/IRGen/IRGenModule.cpp b/lib/IRGen/IRGenModule.cpp index e1cbe702ed151..b77020c8159d3 100644 --- a/lib/IRGen/IRGenModule.cpp +++ b/lib/IRGen/IRGenModule.cpp @@ -2023,7 +2023,7 @@ bool IRGenModule::canUseObjCSymbolicReferences() { context.getObjCSymbolicReferencesAvailability()); } -bool IRGenModule::canMakeStaticObjectsReadOnly() { +bool IRGenModule::canMakeStaticObjectReadOnly(SILType objectType) { if (getOptions().DisableReadonlyStaticObjects) return false; @@ -2032,6 +2032,16 @@ bool IRGenModule::canMakeStaticObjectsReadOnly() { if (!Triple.isOSDarwin()) return false; + auto *clDecl = objectType.getClassOrBoundGenericClass(); + if (!clDecl) + return false; + + // Currently only arrays can be put into a read-only data section. + // "Regular" classes have dynamically initialized metadata, which needs to be + // stored into the isa field at runtime. + if (clDecl->getNameStr() != "_ContiguousArrayStorage") + return false; + if (!getAvailabilityContext().isContainedIn(Context.getStaticReadOnlyArraysAvailability())) return false; diff --git a/lib/IRGen/IRGenModule.h b/lib/IRGen/IRGenModule.h index f9eef8bbbe5ea..a2599ef9b3a30 100644 --- a/lib/IRGen/IRGenModule.h +++ b/lib/IRGen/IRGenModule.h @@ -925,7 +925,7 @@ class IRGenModule { bool shouldPrespecializeGenericMetadata(); - bool canMakeStaticObjectsReadOnly(); + bool canMakeStaticObjectReadOnly(SILType objectType); ClassDecl *getStaticArrayStorageDecl(); diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index 97445e398f97d..822055c5f248f 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -3097,7 +3097,7 @@ void IRGenSILFunction::visitGlobalValueInst(GlobalValueInst *i) { NotForDefinition).getAddress(); // We don't need to initialize the global object if it's never used for // something which can access the object header. - if (!i->isBare() && !IGM.canMakeStaticObjectsReadOnly()) { + if (!i->isBare() && !IGM.canMakeStaticObjectReadOnly(var->getLoweredType())) { auto ClassType = loweredTy.getASTType(); llvm::Value *Metadata = emitClassHeapMetadataRef(*this, ClassType, MetadataValueType::TypeMetadata, diff --git a/lib/IRGen/Linking.cpp b/lib/IRGen/Linking.cpp index 9825b4a26fbd0..2c83debbb760e 100644 --- a/lib/IRGen/Linking.cpp +++ b/lib/IRGen/Linking.cpp @@ -97,7 +97,7 @@ LinkEntity LinkEntity::forSILGlobalVariable(SILGlobalVariable *G, LinkEntity entity; entity.Pointer = G; entity.SecondaryPointer = nullptr; - auto kind = (G->isInitializedObject() && IGM.canMakeStaticObjectsReadOnly() ? + auto kind = (G->isInitializedObject() && IGM.canMakeStaticObjectReadOnly(G->getLoweredType()) ? Kind::ReadOnlyGlobalObject : Kind::SILGlobalVariable); entity.Data = unsigned(kind) << KindShift; return entity; From bc99986cf9cc4d05e53a20a7661b198a6555a612 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 2 Jan 2024 12:45:43 +0100 Subject: [PATCH 2/7] SIL: add a dependency token operand to `global_addr` Optionally, the dependency to the initialization of the global can be specified with a dependency token `depends_on `. This is usually a `builtin "once"` which calls the initializer for the global variable. --- .../FunctionPasses/AllocVectorLowering.swift | 2 +- .../FunctionPasses/ObjectOutliner.swift | 2 +- .../SimplifyBuiltin.swift | 4 +++ .../SimplifyLoad.swift | 2 ++ .../Optimizer/PassManager/Context.swift | 8 +++++ .../Sources/SIL/Builder.swift | 4 +-- .../Sources/SIL/Instruction.swift | 4 +++ docs/SIL.rst | 8 ++++- include/swift/AST/DiagnosticsParse.def | 3 ++ include/swift/SIL/SILBridging.h | 4 ++- include/swift/SIL/SILBridgingImpl.h | 9 ++++-- include/swift/SIL/SILBuilder.h | 8 ++--- include/swift/SIL/SILCloner.h | 5 ++- include/swift/SIL/SILInstruction.h | 28 ++++++++++++++++ lib/AST/Builtins.cpp | 6 ++-- lib/IRGen/LoadableByAddress.cpp | 3 +- lib/SIL/IR/OperandOwnership.cpp | 2 +- lib/SIL/IR/SILInstructions.cpp | 7 +++- lib/SIL/IR/SILPrinter.cpp | 3 ++ lib/SIL/Parser/ParseSIL.cpp | 16 ++++++++-- lib/SIL/Verifier/SILVerifier.cpp | 9 ++++++ lib/SILGen/SILGenDecl.cpp | 2 +- lib/SILGen/SILGenExpr.cpp | 4 +-- lib/SILGen/SILGenGlobalVariable.cpp | 19 ++++++----- lib/Serialization/DeserializeSIL.cpp | 32 ++++++++++++------- lib/Serialization/ModuleFormat.h | 2 +- lib/Serialization/SerializeSIL.cpp | 28 ++++++++++------ test/SIL/Parser/basic.sil | 7 ++-- test/SILGen/lazy_globals.swift | 12 +++---- test/SILOptimizer/simplify_builtin.sil | 25 ++++++++------- test/Serialization/Inputs/def_basic.sil | 7 ++-- 31 files changed, 198 insertions(+), 77 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift index 86cfa04ac1d78..d9a7dff7a6218 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift @@ -246,7 +246,7 @@ private func createOutlinedGlobal( } let builder = Builder(before: allocVectorBuiltin, context) - let globalAddr = builder.createGlobalAddr(global: outlinedGlobal) + let globalAddr = builder.createGlobalAddr(global: outlinedGlobal, dependencyToken: nil) let rawVectorPointer = builder.createAddressToPointer(address: globalAddr, pointerType: allocVectorBuiltin.type, needStackProtection: false) allocVectorBuiltin.uses.replaceAll(with: rawVectorPointer, context) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift index ee7f56e5bcbd5..ad87b7aa4a911 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift @@ -503,7 +503,7 @@ private func replace(findStringCall: ApplyInst, _ = varBuilder.createStruct(type: cacheType, elements: [zero, zero]) let builder = Builder(before: findStringCall, context) - let cacheAddr = builder.createGlobalAddr(global: cacheVar) + let cacheAddr = builder.createGlobalAddr(global: cacheVar, dependencyToken: nil) let findStringRef = builder.createFunctionRef(cachedFindStringFunc) let newCall = builder.createApply(function: findStringRef, SubstitutionMap(), arguments: [findStringCall.arguments[0], diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift index d090dbd8fad2b..4064b07fef567 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift @@ -109,6 +109,10 @@ private extension BuiltinInst { if callee.instructions.contains(where: hasSideEffectForBuiltinOnce) { return } + for use in uses { + let ga = use.instruction as! GlobalAddrInst + ga.clearToken(context) + } context.erase(instruction: self) } diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift index fcaf3d4e04ab0..cf97434cbe528 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift @@ -110,6 +110,8 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable { let initVal = cloner.clone(globalInitVal) uses.replaceAll(with: initVal, context) + // Also erases a builtin "once" on which the global_addr depends on. This is fine + // because we only replace the load if the global init function doesn't have any side effect. transitivelyErase(load: self, context) return true } diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index 5db11976a8244..180f3cbf893a1 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -535,6 +535,14 @@ extension RefElementAddrInst { } } +extension GlobalAddrInst { + func clearToken(_ context: some MutatingContext) { + context.notifyInstructionsChanged() + bridged.GlobalAddrInst_clearToken() + context.notifyInstructionChanged(self) + } +} + extension GlobalValueInst { func setIsBare(_ context: some MutatingContext) { context.notifyInstructionsChanged() diff --git a/SwiftCompilerSources/Sources/SIL/Builder.swift b/SwiftCompilerSources/Sources/SIL/Builder.swift index 5382c05f4074a..fd1fda89fe1af 100644 --- a/SwiftCompilerSources/Sources/SIL/Builder.swift +++ b/SwiftCompilerSources/Sources/SIL/Builder.swift @@ -305,8 +305,8 @@ public struct Builder { return notifyNew(vectorInst.getAs(VectorInst.self)) } - public func createGlobalAddr(global: GlobalVariable) -> GlobalAddrInst { - return notifyNew(bridged.createGlobalAddr(global.bridged).getAs(GlobalAddrInst.self)) + public func createGlobalAddr(global: GlobalVariable, dependencyToken: Value?) -> GlobalAddrInst { + return notifyNew(bridged.createGlobalAddr(global.bridged, dependencyToken.bridged).getAs(GlobalAddrInst.self)) } public func createGlobalValue(global: GlobalVariable, isBare: Bool) -> GlobalValueInst { diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index 9d123f4ad60e3..029070811497e 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -650,6 +650,10 @@ final public class GlobalAddrInst : GlobalAccessInst, VarDeclInstruction { public var varDecl: VarDecl? { VarDecl(bridged: bridged.GlobalAddr_getDecl()) } + + public var dependencyToken: Value? { + operands.count == 1 ? operands[0].value : nil + } } final public class GlobalValueInst : GlobalAccessInst { diff --git a/docs/SIL.rst b/docs/SIL.rst index a2afbe5f05803..81df2ff2abc07 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -5739,15 +5739,21 @@ global_addr :: - sil-instruction ::= 'global_addr' sil-global-name ':' sil-type + sil-instruction ::= 'global_addr' sil-global-name ':' sil-type ('depends_on' sil-operand)? %1 = global_addr @foo : $*Builtin.Word + %3 = global_addr @globalvar : $*Builtin.Word depends_on %2 + // %2 has type $Builtin.SILToken Creates a reference to the address of a global variable which has been previously initialized by ``alloc_global``. It is undefined behavior to perform this operation on a global variable which has not been initialized, except the global variable has a static initializer. +Optionally, the dependency to the initialization of the global can be +specified with a dependency token ``depends_on ``. This is usually +a ``builtin "once"`` which calls the initializer for the global variable. + global_value ````````````` :: diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 0e455cd67655e..a97b0cc160dcb 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1502,6 +1502,9 @@ WARNING(warning_in_effects_attribute,none, ERROR(expected_in_attribute_list,none, "expected ']' or ',' in attribute list", ()) +ERROR(expected_depends_on,none, + "expected 'depends_on'", ()) + ERROR(type_attribute_applied_to_decl,none, "attribute can only be applied to types, not declarations", ()) ERROR(decl_attribute_applied_to_type,none, diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index ddb1871282f24..fa624f6463e07 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -809,6 +809,7 @@ struct BridgedInstruction { BRIDGED_INLINE void TermInst_replaceBranchTarget(BridgedBasicBlock from, BridgedBasicBlock to) const; BRIDGED_INLINE SwiftInt KeyPathInst_getNumComponents() const; BRIDGED_INLINE void KeyPathInst_getReferencedFunctions(SwiftInt componentIdx, KeyPathFunctionResults * _Nonnull results) const; + BRIDGED_INLINE void GlobalAddrInst_clearToken() const; BRIDGED_INLINE bool GlobalValueInst_isBare() const; BRIDGED_INLINE void GlobalValueInst_setIsBare() const; BRIDGED_INLINE void LoadInst_setOwnership(SwiftInt ownership) const; @@ -1093,7 +1094,8 @@ struct BridgedBuilder{ SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createObject(BridgedType type, BridgedValueArray arguments, SwiftInt numBaseElements) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createVector(BridgedValueArray arguments) const; - SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createGlobalAddr(BridgedGlobalVar global) const; + SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createGlobalAddr(BridgedGlobalVar global, + OptionalBridgedValue dependencyToken) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createGlobalValue(BridgedGlobalVar global, bool isBare) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createStruct(BridgedType type, diff --git a/include/swift/SIL/SILBridgingImpl.h b/include/swift/SIL/SILBridgingImpl.h index d33d995557905..00e18d0eb75a0 100644 --- a/include/swift/SIL/SILBridgingImpl.h +++ b/include/swift/SIL/SILBridgingImpl.h @@ -1114,6 +1114,10 @@ void BridgedInstruction::KeyPathInst_getReferencedFunctions(SwiftInt componentId }, [](swift::SILDeclRef) {}); } +void BridgedInstruction::GlobalAddrInst_clearToken() const { + getAs()->clearToken(); +} + bool BridgedInstruction::GlobalValueInst_isBare() const { return getAs()->isBare(); } @@ -1522,8 +1526,9 @@ BridgedInstruction BridgedBuilder::createVector(BridgedValueArray arguments) con return {unbridged().createVector(swift::ArtificialUnreachableLocation(), arguments.getValues(argValues))}; } -BridgedInstruction BridgedBuilder::createGlobalAddr(BridgedGlobalVar global) const { - return {unbridged().createGlobalAddr(regularLoc(), global.getGlobal())}; +BridgedInstruction BridgedBuilder::createGlobalAddr(BridgedGlobalVar global, + OptionalBridgedValue dependencyToken) const { + return {unbridged().createGlobalAddr(regularLoc(), global.getGlobal(), dependencyToken.getSILValue())}; } BridgedInstruction BridgedBuilder::createGlobalValue(BridgedGlobalVar global, bool isBare) const { diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index f7678028eaeb9..a4bbac810ecb8 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -680,14 +680,12 @@ class SILBuilder { return insert(new (getModule()) AllocGlobalInst(getSILDebugLocation(Loc), g)); } - GlobalAddrInst *createGlobalAddr(SILLocation Loc, SILGlobalVariable *g) { + GlobalAddrInst *createGlobalAddr(SILLocation Loc, SILGlobalVariable *g, + SILValue dependencyToken) { return insert(new (getModule()) GlobalAddrInst(getSILDebugLocation(Loc), g, + dependencyToken, getTypeExpansionContext())); } - GlobalAddrInst *createGlobalAddr(SILLocation Loc, SILType Ty) { - return insert(new (F->getModule()) - GlobalAddrInst(getSILDebugLocation(Loc), Ty)); - } GlobalValueInst *createGlobalValue(SILLocation Loc, SILGlobalVariable *g, bool isBare) { return insert(new (getModule()) GlobalValueInst(getSILDebugLocation(Loc), g, getTypeExpansionContext(), isBare)); diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 7360fcc24f5ee..d22803f083eff 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1124,7 +1124,10 @@ SILCloner::visitGlobalAddrInst(GlobalAddrInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); recordClonedInstruction( Inst, getBuilder().createGlobalAddr(getOpLocation(Inst->getLoc()), - Inst->getReferencedGlobal())); + Inst->getReferencedGlobal(), + Inst->hasOperand() ? + getOpValue(Inst->getDependencyToken()) : + SILValue())); } template diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 5cbaeda1e9f49..750d42c0ec612 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -4176,8 +4176,11 @@ class GlobalAddrInst friend SILBuilder; GlobalAddrInst(SILDebugLocation DebugLoc, SILGlobalVariable *Global, + SILValue dependencyToken, TypeExpansionContext context); + llvm::Optional> dependencyToken; + public: // FIXME: This constructor should be private but is currently used // in the SILParser. @@ -4185,6 +4188,31 @@ class GlobalAddrInst /// Create a placeholder instruction with an unset global reference. GlobalAddrInst(SILDebugLocation DebugLoc, SILType Ty) : InstructionBase(DebugLoc, Ty, nullptr) {} + + SILValue getDependencyToken() const { + if (hasOperand()) + return getOperand(); + return SILValue(); + } + + void clearToken() { + dependencyToken = llvm::None; + } + + bool hasOperand() const { return dependencyToken.has_value(); } + SILValue getOperand() const { return dependencyToken->asValueArray()[0]; } + + Operand &getOperandRef() { return dependencyToken->asArray()[0]; } + const Operand &getOperandRef() const { return dependencyToken->asArray()[0]; } + + ArrayRef getAllOperands() const { + return dependencyToken ? dependencyToken->asArray() : ArrayRef{}; + } + + MutableArrayRef getAllOperands() { + return dependencyToken + ? dependencyToken->asArray() : MutableArrayRef{}; + } }; /// Creates a base address for offset calculations. diff --git a/lib/AST/Builtins.cpp b/lib/AST/Builtins.cpp index 6e2c497d16576..e755a7264fd2c 100644 --- a/lib/AST/Builtins.cpp +++ b/lib/AST/Builtins.cpp @@ -1925,9 +1925,11 @@ static ValueDecl *getOnceOperation(ASTContext &Context, .build(); auto BlockTy = FunctionType::get(CFuncParams, VoidTy, Thin); SmallVector ArgTypes = {HandleTy, BlockTy}; - if (withContext) + if (withContext) { ArgTypes.push_back(ContextTy); - return getBuiltinFunction(Id, ArgTypes, VoidTy); + return getBuiltinFunction(Id, ArgTypes, VoidTy); + } + return getBuiltinFunction(Id, ArgTypes, Context.TheSILTokenType); } static ValueDecl *getPolymorphicBinaryOperation(ASTContext &ctx, diff --git a/lib/IRGen/LoadableByAddress.cpp b/lib/IRGen/LoadableByAddress.cpp index 3ac8473bbc106..1393a99aa3732 100644 --- a/lib/IRGen/LoadableByAddress.cpp +++ b/lib/IRGen/LoadableByAddress.cpp @@ -3164,7 +3164,8 @@ void LoadableByAddress::run() { } else if (auto *globalAddr = dyn_cast(inst)) { SILBuilderWithScope builder(inst); auto *newInst = builder.createGlobalAddr( - globalAddr->getLoc(), globalAddr->getReferencedGlobal()); + globalAddr->getLoc(), globalAddr->getReferencedGlobal(), + globalAddr->getDependencyToken()); globalAddr->replaceAllUsesWith(newInst); globalAddr->eraseFromParent(); } else if (auto *globalVal = dyn_cast(inst)) { diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index b6c4d44a072cb..e92ee4bb0e54f 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -107,7 +107,6 @@ SHOULD_NEVER_VISIT_INST(FunctionRef) SHOULD_NEVER_VISIT_INST(DebugStep) SHOULD_NEVER_VISIT_INST(DynamicFunctionRef) SHOULD_NEVER_VISIT_INST(PreviousDynamicFunctionRef) -SHOULD_NEVER_VISIT_INST(GlobalAddr) SHOULD_NEVER_VISIT_INST(GlobalValue) SHOULD_NEVER_VISIT_INST(BaseAddrForOffset) SHOULD_NEVER_VISIT_INST(HasSymbol) @@ -207,6 +206,7 @@ OPERAND_OWNERSHIP(TrivialUse, PackPackIndex) OPERAND_OWNERSHIP(TrivialUse, PackElementGet) OPERAND_OWNERSHIP(TrivialUse, PackElementSet) OPERAND_OWNERSHIP(TrivialUse, TuplePackElementAddr) +OPERAND_OWNERSHIP(TrivialUse, GlobalAddr) // The dealloc_stack_ref operand needs to have NonUse ownership because // this use comes after the last consuming use (which is usually a dealloc_ref). diff --git a/lib/SIL/IR/SILInstructions.cpp b/lib/SIL/IR/SILInstructions.cpp index 894b40d54f46a..49769180b7bb0 100644 --- a/lib/SIL/IR/SILInstructions.cpp +++ b/lib/SIL/IR/SILInstructions.cpp @@ -1038,10 +1038,15 @@ AllocGlobalInst::AllocGlobalInst(SILDebugLocation Loc, GlobalAddrInst::GlobalAddrInst(SILDebugLocation DebugLoc, SILGlobalVariable *Global, + SILValue dependencyToken, TypeExpansionContext context) : InstructionBase(DebugLoc, Global->getLoweredTypeInContext(context).getAddressType(), - Global) {} + Global) { + if (dependencyToken) { + this->dependencyToken.emplace(this, dependencyToken); + } +} GlobalValueInst::GlobalValueInst(SILDebugLocation DebugLoc, SILGlobalVariable *Global, diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index df8276c12af42..bb96cf1ce3483 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -1644,6 +1644,9 @@ class SILPrinter : public SILInstructionVisitor { *this << "<>"; } *this << " : " << GAI->getType(); + if (SILValue token = GAI->getDependencyToken()) { + *this << " depends_on " << Ctx.getID(token); + } } void visitGlobalValueInst(GlobalValueInst *GVI) { diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index d0f6f0f35df58..006ac7e56f520 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -5671,7 +5671,7 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, parseSILIdentifier(GlobalName, IdLoc, diag::expected_sil_value_name) || P.parseToken(tok::colon, diag::expected_tok_in_sil_instr, ":") || - parseSILType(Ty) || parseSILDebugLocation(InstLoc, B)) + parseSILType(Ty)) return true; // Go through list of global variables in the SILModule. @@ -5681,6 +5681,18 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, return true; } + SILValue dependencyToken; + if (Opcode == SILInstructionKind::GlobalAddrInst && P.Tok.isContextualKeyword("depends_on")) { + P.consumeToken(); + if (parseValueRef(dependencyToken, SILType::getSILTokenType(P.Context), + RegularLocation(P.Tok.getLoc()), B)) { + return true; + } + } + + if (parseSILDebugLocation(InstLoc, B)) + return true; + SILType expectedType = (Opcode == SILInstructionKind::GlobalAddrInst ? global->getLoweredType().getAddressType() : global->getLoweredType()); @@ -5692,7 +5704,7 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, } if (Opcode == SILInstructionKind::GlobalAddrInst) { - ResultVal = B.createGlobalAddr(InstLoc, global); + ResultVal = B.createGlobalAddr(InstLoc, global, dependencyToken); } else { ResultVal = B.createGlobalValue(InstLoc, global, isBare); } diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 4767430fb131d..58fd63db8459b 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -1515,6 +1515,11 @@ class SILVerifier : public SILVerifierBase { if (isa(value)) return true; + if (auto *bi = dyn_cast(value)) { + if (bi->getBuiltinInfo().ID == BuiltinValueKind::Once) + return true; + } + // Add more token cases here as they arise. return false; @@ -2297,6 +2302,10 @@ class SILVerifier : public SILVerifierBase { "global_addr cannot refer to a statically initialized object"); checkGlobalAccessInst(GAI); checkAddressWalkerCanVisitAllTransitiveUses(GAI); + if (SILValue token = GAI->getDependencyToken()) { + require(token->getType().is(), + "depends_on operand of global_addr must be a token"); + } } void checkGlobalValueInst(GlobalValueInst *GVI) { diff --git a/lib/SILGen/SILGenDecl.cpp b/lib/SILGen/SILGenDecl.cpp index b2d368b94a5f6..f4ac73d9e8bf8 100644 --- a/lib/SILGen/SILGenDecl.cpp +++ b/lib/SILGen/SILGenDecl.cpp @@ -1480,7 +1480,7 @@ SILGenFunction::emitInitializationForVarDecl(VarDecl *vd, bool forceImmutable, RegularLocation loc(vd); loc.markAutoGenerated(); B.createAllocGlobal(loc, silG); - SILValue addr = B.createGlobalAddr(loc, silG); + SILValue addr = B.createGlobalAddr(loc, silG, /*dependencyToken=*/ SILValue()); if (isUninitialized) addr = B.createMarkUninitializedVar(loc, addr); diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 6bd0b537e66ed..4f4cba0aa4c4c 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -4344,14 +4344,14 @@ visitMagicIdentifierLiteralExpr(MagicIdentifierLiteralExpr *E, SGFContext C) { SILGlobalVariable::create(M, SILLinkage::DefaultForDeclaration, IsNotSerialized, "__ImageBase", BuiltinRawPtrTy); - ModuleBase = B.createGlobalAddr(SILLoc, ImageBase); + ModuleBase = B.createGlobalAddr(SILLoc, ImageBase, /*dependencyToken=*/ SILValue()); } else { auto DSOHandle = M.lookUpGlobalVariable("__dso_handle"); if (!DSOHandle) DSOHandle = SILGlobalVariable::create(M, SILLinkage::PublicExternal, IsNotSerialized, "__dso_handle", BuiltinRawPtrTy); - ModuleBase = B.createGlobalAddr(SILLoc, DSOHandle); + ModuleBase = B.createGlobalAddr(SILLoc, DSOHandle, /*dependencyToken=*/ SILValue()); } auto ModuleBasePointer = diff --git a/lib/SILGen/SILGenGlobalVariable.cpp b/lib/SILGen/SILGenGlobalVariable.cpp index 81533a9f1627e..e7abe821fc4ac 100644 --- a/lib/SILGen/SILGenGlobalVariable.cpp +++ b/lib/SILGen/SILGenGlobalVariable.cpp @@ -115,7 +115,8 @@ SILGenFunction::emitGlobalVariableRef(SILLocation loc, VarDecl *var, auto *silG = SGM.getSILGlobalVariable(var, NotForDefinition); if (silG->getLoweredType().isMoveOnly()) { SILValue addr = B.createGlobalAddr( - RegularLocation::getAutoGeneratedLocation(var), silG); + RegularLocation::getAutoGeneratedLocation(var), silG, + /*dependencyToken=*/ SILValue()); return ManagedValue::forLValue(addr); } @@ -127,7 +128,8 @@ SILGenFunction::emitGlobalVariableRef(SILLocation loc, VarDecl *var, // Because we jump back into the prologue, we can't use loc. SILValue addr = prologueB.createGlobalAddr( - RegularLocation::getAutoGeneratedLocation(), silG); + RegularLocation::getAutoGeneratedLocation(), silG, + /*dependencyToken=*/ SILValue()); VarLocs[var] = SILGenFunction::VarLoc::get(addr); return ManagedValue::forLValue(addr); @@ -279,13 +281,14 @@ void SILGenFunction::emitLazyGlobalInitializer(PatternBindingDecl *binding, B.createReturn(ImplicitReturnLocation(binding), ret); } -static void emitOnceCall(SILGenFunction &SGF, VarDecl *global, +static SILValue emitOnceCall(SILGenFunction &SGF, VarDecl *global, SILGlobalVariable *onceToken, SILFunction *onceFunc) { SILType rawPointerSILTy = SGF.getLoweredLoadableType(SGF.getASTContext().TheRawPointerType); // Emit a reference to the global token. - SILValue onceTokenAddr = SGF.B.createGlobalAddr(global, onceToken); + SILValue onceTokenAddr = SGF.B.createGlobalAddr(global, onceToken, + /*dependencyToken=*/ SILValue()); onceTokenAddr = SGF.B.createAddressToPointer(global, onceTokenAddr, rawPointerSILTy, /*needsStackProtection=*/ false); @@ -295,8 +298,8 @@ static void emitOnceCall(SILGenFunction &SGF, VarDecl *global, // Call Builtin.once. SILValue onceArgs[] = {onceTokenAddr, onceFuncRef}; - SGF.B.createBuiltin(global, SGF.getASTContext().getIdentifier("once"), - SGF.SGM.Types.getEmptyTupleType(), {}, onceArgs); + return SGF.B.createBuiltin(global, SGF.getASTContext().getIdentifier("once"), + SILType::getSILTokenType(SGF.SGM.getASTContext()), {}, onceArgs); } void SILGenFunction::emitGlobalAccessor(VarDecl *global, @@ -305,12 +308,12 @@ void SILGenFunction::emitGlobalAccessor(VarDecl *global, if (global->requiresUnavailableDeclABICompatibilityStubs()) emitApplyOfUnavailableCodeReached(); - emitOnceCall(*this, global, onceToken, onceFunc); + SILValue token = emitOnceCall(*this, global, onceToken, onceFunc); // Return the address of the global variable. // FIXME: It'd be nice to be able to return a SIL address directly. auto *silG = SGM.getSILGlobalVariable(global, NotForDefinition); - SILValue addr = B.createGlobalAddr(global, silG); + SILValue addr = B.createGlobalAddr(global, silG, token); SILType rawPointerSILTy = getLoweredLoadableType(getASTContext().TheRawPointerType); diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 009898ccfd15e..1ad44442278d6 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -1877,7 +1877,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, ResultInst = Builder.createAllocGlobal(Loc, g); break; } - case SILInstructionKind::GlobalAddrInst: case SILInstructionKind::GlobalValueInst: { // Format: Name and type. Use SILOneOperandLayout. auto Ty = MF->getType(TyID); @@ -1886,20 +1885,31 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, // Find the global variable. SILGlobalVariable *g = getGlobalForReference(Name); assert(g && "Can't deserialize global variable"); - SILType expectedType = - (OpCode == SILInstructionKind::GlobalAddrInst - ? g->getLoweredTypeInContext(TypeExpansionContext(*Fn)) - .getAddressType() - : g->getLoweredTypeInContext(TypeExpansionContext(*Fn))); + SILType expectedType = g->getLoweredTypeInContext(TypeExpansionContext(*Fn)); assert(expectedType == getSILType(Ty, (SILValueCategory)TyCategory, Fn) && "Type of a global variable does not match GlobalAddr."); (void)Ty; (void)expectedType; - if (OpCode == SILInstructionKind::GlobalAddrInst) { - ResultInst = Builder.createGlobalAddr(Loc, g); - } else { - ResultInst = Builder.createGlobalValue(Loc, g, /*isBare=*/ (Attr & 1) != 0); - } + ResultInst = Builder.createGlobalValue(Loc, g, /*isBare=*/ (Attr & 1) != 0); + break; + } + case SILInstructionKind::GlobalAddrInst: { + // Format: Name and type. Use SILOneOperandLayout. + auto Ty = MF->getType(TyID); + StringRef Name = MF->getIdentifierText(ValID); + + // Find the global variable. + SILGlobalVariable *g = getGlobalForReference(Name); + assert(g && "Can't deserialize global variable"); + SILType expectedType = g->getLoweredTypeInContext(TypeExpansionContext(*Fn)) + .getAddressType(); + assert(expectedType == getSILType(Ty, (SILValueCategory)TyCategory, Fn) && + "Type of a global variable does not match GlobalAddr."); + (void)Ty; + (void)expectedType; + SILValue token = ValID2 == 0 ? SILValue() + : getLocalValue(ValID2, SILType::getSILTokenType(MF->getContext())); + ResultInst = Builder.createGlobalAddr(Loc, g, token); break; } case SILInstructionKind::BaseAddrForOffsetInst: diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 216c728d28431..f1715213a49b5 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 832; // reorder ValueOwnership +const uint16_t SWIFTMODULE_VERSION_MINOR = 833; // global_addr with dependency token /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index b991e788c4820..2b0380849233f 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -1281,23 +1281,33 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { S.addUniquedStringRef(G->getName())); break; } - case SILInstructionKind::GlobalAddrInst: case SILInstructionKind::GlobalValueInst: { // Format: Name and type. Use SILOneOperandLayout. - const GlobalAccessInst *GI = cast(&SI); - auto *G = GI->getReferencedGlobal(); + auto *gv = cast(&SI); + auto *G = gv->getReferencedGlobal(); addReferencedGlobalVariable(G); - bool isBare = false; - if (auto *gv = dyn_cast(&SI)) - isBare = gv->isBare(); SILOneOperandLayout::emitRecord(Out, ScratchRecord, SILAbbrCodes[SILOneOperandLayout::Code], - (unsigned)SI.getKind(), isBare ? 1 : 0, - S.addTypeRef(GI->getType().getRawASTType()), - (unsigned)GI->getType().getCategory(), + (unsigned)SI.getKind(), gv->isBare() ? 1 : 0, + S.addTypeRef(gv->getType().getRawASTType()), + (unsigned)gv->getType().getCategory(), S.addUniquedStringRef(G->getName())); break; } + case SILInstructionKind::GlobalAddrInst: { + // Format: Name and type. Use SILOneOperandLayout. + auto *ga = cast(&SI); + auto *G = ga->getReferencedGlobal(); + addReferencedGlobalVariable(G); + SILOneValueOneOperandLayout::emitRecord(Out, ScratchRecord, + SILAbbrCodes[SILOneValueOneOperandLayout::Code], + (unsigned)SI.getKind(), 0, + S.addUniquedStringRef(G->getName()), + S.addTypeRef(ga->getType().getRawASTType()), + (unsigned)ga->getType().getCategory(), + addValueRef(ga->getDependencyToken())); + break; + } case SILInstructionKind::BaseAddrForOffsetInst: { const BaseAddrForOffsetInst *BAI = cast(&SI); writeOneTypeLayout(BAI->getKind(), /*attrs*/ 0, BAI->getType()); diff --git a/test/SIL/Parser/basic.sil b/test/SIL/Parser/basic.sil index 883f32957f67a..a03b692400d56 100644 --- a/test/SIL/Parser/basic.sil +++ b/test/SIL/Parser/basic.sil @@ -1294,9 +1294,10 @@ bb0: %a = unchecked_addr_cast %1 : $*Builtin.Word to $*Builtin.RawPointer %2 = address_to_pointer %a : $*Builtin.RawPointer to $Builtin.RawPointer %3 = function_ref @globalinit_func0 : $@convention(thin) () -> () - // CHECK: {{%.*}} = builtin "once"({{%.*}} : $Builtin.RawPointer, {{%.*}} : $@convention(thin) () -> ()) : $() - %5 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(thin) () -> ()) : $() - %6 = global_addr @staticProp : $*Int + // CHECK: [[O:%.*]] = builtin "once"({{%.*}} : $Builtin.RawPointer, {{%.*}} : $@convention(thin) () -> ()) : $Builtin.SILToken + %5 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(thin) () -> ()) : $Builtin.SILToken + // CHECK: global_addr @staticProp : $*Int depends_on [[O]] + %6 = global_addr @staticProp : $*Int depends_on %5 %7 = address_to_pointer %6 : $*Int to $Builtin.RawPointer return %7 : $Builtin.RawPointer } diff --git a/test/SILGen/lazy_globals.swift b/test/SILGen/lazy_globals.swift index c0211802191b7..94b9157fbffde 100644 --- a/test/SILGen/lazy_globals.swift +++ b/test/SILGen/lazy_globals.swift @@ -9,8 +9,8 @@ // CHECK: [[TOKEN_ADDR:%.*]] = global_addr @[[T]]Wz : $*Builtin.Word // CHECK: [[TOKEN_PTR:%.*]] = address_to_pointer [[TOKEN_ADDR]] : $*Builtin.Word to $Builtin.RawPointer // CHECK: [[INIT_FUNC:%.*]] = function_ref @[[T]]WZ : $@convention(c) (Builtin.RawPointer) -> () -// CHECK: builtin "once"([[TOKEN_PTR]] : $Builtin.RawPointer, [[INIT_FUNC]] : $@convention(c) (Builtin.RawPointer) -> ()) : $() -// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s12lazy_globals1xSivp : $*Int +// CHECK: [[ONCE:%.*]] = builtin "once"([[TOKEN_PTR]] : $Builtin.RawPointer, [[INIT_FUNC]] : $@convention(c) (Builtin.RawPointer) -> ()) : $Builtin.SILToken +// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s12lazy_globals1xSivp : $*Int depends_on [[ONCE]] // CHECK: [[GLOBAL_PTR:%.*]] = address_to_pointer [[GLOBAL_ADDR]] : $*Int to $Builtin.RawPointer // CHECK: return [[GLOBAL_PTR]] : $Builtin.RawPointer // CHECK: } @@ -27,8 +27,8 @@ struct Foo { // CHECK: [[TOKEN_ADDR:%.*]] = global_addr @[[T]]Wz : $*Builtin.Word // CHECK: [[TOKEN_PTR:%.*]] = address_to_pointer [[TOKEN_ADDR]] : $*Builtin.Word to $Builtin.RawPointer // CHECK: [[INIT_FUNC:%.*]] = function_ref @[[T]]WZ : $@convention(c) (Builtin.RawPointer) -> () -// CHECK: builtin "once"([[TOKEN_PTR]] : $Builtin.RawPointer, [[INIT_FUNC]] : $@convention(c) (Builtin.RawPointer) -> ()) : $() -// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s12lazy_globals3FooV3fooSivpZ : $*Int +// CHECK: [[ONCE:%.*]] = builtin "once"([[TOKEN_PTR]] : $Builtin.RawPointer, [[INIT_FUNC]] : $@convention(c) (Builtin.RawPointer) -> ()) : $Builtin.SILToken +// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s12lazy_globals3FooV3fooSivpZ : $*Int depends_on [[ONCE]] // CHECK: [[GLOBAL_PTR:%.*]] = address_to_pointer [[GLOBAL_ADDR]] : $*Int to $Builtin.RawPointer // CHECK: return [[GLOBAL_PTR]] : $Builtin.RawPointer static var foo: Int = 22 @@ -51,8 +51,8 @@ enum Bar { // CHECK: [[TOKEN_ADDR:%.*]] = global_addr @[[T]]Wz : $*Builtin.Word // CHECK: [[TOKEN_PTR:%.*]] = address_to_pointer [[TOKEN_ADDR]] : $*Builtin.Word to $Builtin.RawPointer // CHECK: [[INIT_FUNC:%.*]] = function_ref @[[T]]WZ : $@convention(c) (Builtin.RawPointer) -> () -// CHECK: builtin "once"([[TOKEN_PTR]] : $Builtin.RawPointer, [[INIT_FUNC]] : $@convention(c) (Builtin.RawPointer) -> ()) : $() -// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s12lazy_globals3BarO3barSivpZ : $*Int +// CHECK: [[ONCE:%.*]] = builtin "once"([[TOKEN_PTR]] : $Builtin.RawPointer, [[INIT_FUNC]] : $@convention(c) (Builtin.RawPointer) -> ()) : $Builtin.SILToken +// CHECK: [[GLOBAL_ADDR:%.*]] = global_addr @$s12lazy_globals3BarO3barSivpZ : $*Int depends_on [[ONCE]] // CHECK: [[GLOBAL_PTR:%.*]] = address_to_pointer [[GLOBAL_ADDR]] : $*Int to $Builtin.RawPointer // CHECK: return [[GLOBAL_PTR]] : $Builtin.RawPointer static var bar: Int = 33 diff --git a/test/SILOptimizer/simplify_builtin.sil b/test/SILOptimizer/simplify_builtin.sil index 1e1c519f37216..49f571f548139 100644 --- a/test/SILOptimizer/simplify_builtin.sil +++ b/test/SILOptimizer/simplify_builtin.sil @@ -29,6 +29,8 @@ class C2 : C1 { class A { } +sil_global [let] @gg : $Int + // CHECK-LABEL: sil @constantFoldAdd // CHECK: [[A:%.*]] = integer_literal $Builtin.Int64, 12 // CHECK: [[C:%.*]] = integer_literal $Builtin.Int1, 0 @@ -327,34 +329,33 @@ 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) -> () { +sil @remove_builtin_once : $@convention(thin) (Builtin.RawPointer) -> 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 : $() + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + %3 = global_addr @gg : $*Int depends_on %2 + %4 = address_to_pointer %3 : $*Int to $Builtin.RawPointer + return %4 : $Builtin.RawPointer } // 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) -> () { +sil @dont_remove_builtin_once_side_effect : $@convention(thin) (Builtin.RawPointer) -> 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 : $() + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + return %0 : $Builtin.RawPointer } // 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) -> () { +sil @dont_remove_builtin_once_unknown : $@convention(thin) (Builtin.RawPointer) -> 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 : $() + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + return %0 : $Builtin.RawPointer } // CHECK-LABEL: sil @generic_canBeClass diff --git a/test/Serialization/Inputs/def_basic.sil b/test/Serialization/Inputs/def_basic.sil index 5cd5e7af545b4..d8adc94cf21b6 100644 --- a/test/Serialization/Inputs/def_basic.sil +++ b/test/Serialization/Inputs/def_basic.sil @@ -55,9 +55,10 @@ bb0: %a = unchecked_addr_cast %1 : $*Builtin.Word to $*Builtin.RawPointer %2 = address_to_pointer %a : $*Builtin.RawPointer to $Builtin.RawPointer %3 = function_ref @globalinit_func0 : $@convention(thin) () -> () - // CHECK: {{%.*}} = builtin "once"({{%.*}} : $Builtin.RawPointer, {{%.*}} : $@convention(thin) () -> ()) : $() - %5 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(thin) () -> ()) : $() - %6 = global_addr @staticProp : $*Int + // CHECK: [[O:%.*]] = builtin "once"({{%.*}} : $Builtin.RawPointer, {{%.*}} : $@convention(thin) () -> ()) : $Builtin.SILToken + %5 = builtin "once"(%2 : $Builtin.RawPointer, %3 : $@convention(thin) () -> ()) : $Builtin.SILToken + // CHECK: global_addr @staticProp : $*Int depends_on [[O]] + %6 = global_addr @staticProp : $*Int depends_on %5 %7 = address_to_pointer %6 : $*Int to $Builtin.RawPointer return %7 : $Builtin.RawPointer } From 543fe31e8fb7918a8c12b7565bfa516faba3cbd3 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 2 Jan 2024 13:35:38 +0100 Subject: [PATCH 3/7] SwiftCompilerSources: add `Context.notifyDependency(onBodyOf:)` It notifies the pass manager that the optimization result of the current pass depends on the body (i.e. SIL instructions) of another function than the currently optimized one. --- .../InstructionSimplification/SimplifyBuiltin.swift | 2 ++ .../Sources/Optimizer/PassManager/Context.swift | 6 ++++++ include/swift/SILOptimizer/OptimizerBridging.h | 1 + include/swift/SILOptimizer/OptimizerBridgingImpl.h | 6 ++++++ 4 files changed, 15 insertions(+) diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift index 4064b07fef567..4d045cbe6155a 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift @@ -101,6 +101,8 @@ private extension BuiltinInst { guard let callee = calleeOfOnce, callee.isDefinition else { return } + context.notifyDependency(onBodyOf: callee) + // 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. diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index 180f3cbf893a1..aa133d70f68a3 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -210,6 +210,12 @@ extension MutatingContext { func notifyBranchesChanged() { _bridged.asNotificationHandler().notifyChanges(.branchesChanged) } + + /// Notifies the pass manager that the optimization result of the current pass depends + /// on the body (i.e. SIL instructions) of another function than the currently optimized one. + func notifyDependency(onBodyOf otherFunction: Function) { + _bridged.notifyDependencyOnBodyOf(otherFunction.bridged) + } } /// The context which is passed to the run-function of a FunctionPass. diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index 753176eaad6e3..c6a307136fcba 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -171,6 +171,7 @@ struct BridgedPassContext { SWIFT_IMPORT_UNSAFE BridgedOwnedString getModuleDescription() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedChangeNotificationHandler asNotificationHandler() const; + BRIDGED_INLINE void notifyDependencyOnBodyOf(BridgedFunction otherFunction) const; BRIDGED_INLINE SILStage getSILStage() const; BRIDGED_INLINE bool hadError() const; BRIDGED_INLINE bool moduleIsSerialized() const; diff --git a/include/swift/SILOptimizer/OptimizerBridgingImpl.h b/include/swift/SILOptimizer/OptimizerBridgingImpl.h index 1efabaf1074c1..fbdbc695046e8 100644 --- a/include/swift/SILOptimizer/OptimizerBridgingImpl.h +++ b/include/swift/SILOptimizer/OptimizerBridgingImpl.h @@ -137,6 +137,12 @@ BridgedChangeNotificationHandler BridgedPassContext::asNotificationHandler() con return {invocation}; } +void BridgedPassContext::notifyDependencyOnBodyOf(BridgedFunction otherFunction) const { + // Currently `otherFunction` is ignored. We could design a more accurate dependency system + // in the pass manager, which considers the actual function. But it's probaboly not worth the effort. + invocation->getPassManager()->setDependingOnCalleeBodies(); +} + BridgedPassContext::SILStage BridgedPassContext::getSILStage() const { return (SILStage)invocation->getPassManager()->getModule()->getStage(); } From 33a666872d9e765bc6ff7eb399105423bb667109 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 2 Jan 2024 13:41:04 +0100 Subject: [PATCH 4/7] SILGen: remove an unused field in GenGlobalAccessors NFC --- lib/SILGen/SILGenGlobalVariable.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/SILGen/SILGenGlobalVariable.cpp b/lib/SILGen/SILGenGlobalVariable.cpp index e7abe821fc4ac..036bbb89574e6 100644 --- a/lib/SILGen/SILGenGlobalVariable.cpp +++ b/lib/SILGen/SILGenGlobalVariable.cpp @@ -152,9 +152,6 @@ struct GenGlobalAccessors : public PatternVisitor /// The function containing the initialization code. SILFunction *OnceFunc; - /// A reference to the Builtin.once declaration. - FuncDecl *BuiltinOnceDecl; - GenGlobalAccessors(SILGenModule &SGM, SILGlobalVariable *OnceToken, SILFunction *OnceFunc) @@ -167,8 +164,6 @@ struct GenGlobalAccessors : public PatternVisitor NLKind::QualifiedLookup, found); assert(found.size() == 1 && "didn't find Builtin.once?!"); - - BuiltinOnceDecl = cast(found[0]); } // Walk through non-binding patterns. From 32297492579cad4e7482ad17e0b50fe3434fb3d0 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 2 Jan 2024 13:44:00 +0100 Subject: [PATCH 5/7] SwiftCompilerSources: move some private utilities of passes into OptUtils * `var Function.initializedGlobal` * `func getGlobalInitialization` and add `var CollectionLikeSequence.singleElement` --- .../FunctionPasses/AllocVectorLowering.swift | 14 ---- .../InitializeStaticGlobals.swift | 74 +----------------- .../Optimizer/Utilities/OptUtils.swift | 77 +++++++++++++++++++ .../SIL/Utilities/SequenceUtilities.swift | 11 +++ 4 files changed, 91 insertions(+), 85 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift index d9a7dff7a6218..a3707aee30fa9 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocVectorLowering.swift @@ -441,17 +441,3 @@ private extension StoreInst { return false } } - -private extension Function { - var initializedGlobal: GlobalVariable? { - if !isGlobalInitOnceFunction { - return nil - } - for inst in entryBlock.instructions { - if let allocGlobal = inst as? AllocGlobalInst { - return allocGlobal.global - } - } - return nil - } -} diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift index 871a65159c4e0..9212f2bfad895 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/InitializeStaticGlobals.swift @@ -57,7 +57,9 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals" // Merge such individual stores to a single store of the whole struct. mergeStores(in: function, context) - guard let (allocInst, storeToGlobal) = getGlobalInitialization(of: function) else { + // The initializer must not contain a `global_value` because `global_value` needs to + // initialize the class metadata at runtime. + guard let (allocInst, storeToGlobal) = getGlobalInitialization(of: function, allowGlobalValue: false) else { return } @@ -81,76 +83,6 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals" context.removeTriviallyDeadInstructionsIgnoringDebugUses(in: 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 -/// ``` -private func getGlobalInitialization(of function: Function) -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? { - - guard let block = function.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, - is BeginAccessInst, - is EndAccessInst: - break - case let agi as AllocGlobalInst: - if allocInst != nil { - return nil - } - allocInst = agi - case let ga as GlobalAddrInst: - if let agi = allocInst, agi.global == ga.global { - 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 -} - -private extension Function { - var singleBlock: BasicBlock? { - let block = entryBlock - if block.next != nil { - return nil - } - return block - } -} - /// Merges stores to individual struct fields to a single store of the whole struct. /// /// store %element1 to %element1Addr diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift index 7e8d752be0d21..ee38a23bb4d13 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift @@ -484,6 +484,18 @@ extension Function { } return nil } + + var initializedGlobal: GlobalVariable? { + if !isGlobalInitOnceFunction { + return nil + } + for inst in entryBlock.instructions { + if let allocGlobal = inst as? AllocGlobalInst { + return allocGlobal.global + } + } + return nil + } } extension FullApplySite { @@ -570,3 +582,68 @@ extension InstructionRange { } } } + +/// Analyses the global initializer function and returns the `alloc_global` and `store` +/// instructions which initialize the global. +/// Returns nil if `function` has any side-effects beside initializing 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( + of function: Function, + allowGlobalValue: Bool +) -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? { + guard let block = function.blocks.singleElement 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, + is BeginAccessInst, + is EndAccessInst: + break + case let agi as AllocGlobalInst: + if allocInst != nil { + return nil + } + allocInst = agi + case let ga as GlobalAddrInst: + if let agi = allocInst, agi.global == ga.global { + 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 + case is GlobalValueInst where allowGlobalValue: + break + default: + if !inst.isValidInStaticInitializerOfGlobal { + return nil + } + } + } + if let store = store { + return (allocInst: allocInst!, storeToGlobal: store) + } + return nil +} diff --git a/SwiftCompilerSources/Sources/SIL/Utilities/SequenceUtilities.swift b/SwiftCompilerSources/Sources/SIL/Utilities/SequenceUtilities.swift index 047a62538bf6d..08d4afd4aecf9 100644 --- a/SwiftCompilerSources/Sources/SIL/Utilities/SequenceUtilities.swift +++ b/SwiftCompilerSources/Sources/SIL/Utilities/SequenceUtilities.swift @@ -87,6 +87,17 @@ public protocol CollectionLikeSequence : FormattedLikeArray { public extension CollectionLikeSequence { var isEmpty: Bool { !contains(where: { _ in true }) } + + var singleElement: Element? { + var singleElement: Element? = nil + for e in self { + if singleElement != nil { + return nil + } + singleElement = e + } + return singleElement + } } // Also make the lazy sequences a CollectionLikeSequence if the underlying sequence is one. From cee4505840b72c87c20f862bc4579f38d55b2325 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 2 Jan 2024 16:28:02 +0100 Subject: [PATCH 6/7] SimplifyLoad: improve simplification of load of global variable Inline the initialization code of a global initializer if the load is a global_addr with a builtin "once" dependency. --- .../SimplifyLoad.swift | 33 +++++-- test/SILOptimizer/simplify_load.sil | 88 +++++++++++++++++++ 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift index cf97434cbe528..bf14b180da542 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift @@ -98,7 +98,7 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable { /// The load of a global let variable is replaced by its static initializer value. private func replaceLoadOfGlobalLet(_ context: SimplifyContext) -> Bool { - guard let globalInitVal = getGlobalInitValue(address: address) else { + guard let globalInitVal = getGlobalInitValue(address: address, context) else { return false } if !globalInitVal.canBeCopied(into: parentFunction, context) { @@ -211,30 +211,51 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable { } /// Returns the init value of a global which is loaded from `address`. -private func getGlobalInitValue(address: Value) -> Value? { +private func getGlobalInitValue(address: Value, _ context: SimplifyContext) -> Value? { switch address { case let gai as GlobalAddrInst: if gai.global.isLet { - return gai.global.staticInitValue + if let staticInitValue = gai.global.staticInitValue { + return staticInitValue + } + if let staticInitValue = getInitializerFromInitFunction(of: gai, context) { + return 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 { + if let structVal = getGlobalInitValue(address: sea.struct, context) as? StructInst { return structVal.operands[sea.fieldIndex].value } case let tea as TupleElementAddrInst: - if let tupleVal = getGlobalInitValue(address: tea.tuple) as? TupleInst { + if let tupleVal = getGlobalInitValue(address: tea.tuple, context) as? TupleInst { return tupleVal.operands[tea.fieldIndex].value } case let bai as BeginAccessInst: - return getGlobalInitValue(address: bai.address) + return getGlobalInitValue(address: bai.address, context) default: break } return nil } +private func getInitializerFromInitFunction(of globalAddr: GlobalAddrInst, _ context: SimplifyContext) -> Value? { + guard let dependentOn = globalAddr.dependencyToken, + let builtinOnce = dependentOn as? BuiltinInst, + builtinOnce.id == .Once, + let initFnRef = builtinOnce.operands[1].value as? FunctionRefInst else + { + return nil + } + let initFn = initFnRef.referencedFunction + context.notifyDependency(onBodyOf: initFn) + guard let (_, storeToGlobal) = getGlobalInitialization(of: initFn, allowGlobalValue: true) else { + return nil + } + return storeToGlobal.source +} + private func globalLoadedViaAddressor(pointer: Value) -> GlobalVariable? { if let ai = pointer as? ApplyInst, let callee = ai.referencedFunction, diff --git a/test/SILOptimizer/simplify_load.sil b/test/SILOptimizer/simplify_load.sil index db5788a3af301..2a7e8545d4294 100644 --- a/test/SILOptimizer/simplify_load.sil +++ b/test/SILOptimizer/simplify_load.sil @@ -23,6 +23,36 @@ sil_global [let] @gstr : $Str = { class B { } class E : B { } +sil_global hidden [let] @gb : $B +sil_global hidden [let] @gb2 : $(B, Int64) + +sil_global private @gb_obj : $B = { + %initval = object $B () +} + +sil [global_init_once_fn] @init_gb : $@convention(c) () -> () { +bb0: + alloc_global @gb + %1 = global_addr @gb : $*B + %2 = global_value @gb_obj : $B + store %2 to %1 : $*B + %6 = tuple () + return %6 : $() +} + +sil [global_init_once_fn] @init_gb2 : $@convention(c) () -> () { +bb0: + alloc_global @gb2 + %1 = global_addr @gb2 : $*(B, Int64) + %2 = global_value @gb_obj : $B + %3 = integer_literal $Builtin.Int64, 10 + %4 = struct $Int64 (%3 : $Builtin.Int64) + %5 = tuple (%2 : $B, %4 : $Int64) + store %5 to %1 : $*(B, Int64) + %6 = tuple () + return %6 : $() +} + sil [global_init] @gstr_addressor : $@convention(thin) () -> Builtin.RawPointer { bb0: %0 = global_addr @gstr : $*Str @@ -96,6 +126,64 @@ bb0: return %3 : $Int64 } +// CHECK-LABEL: sil @load_global_object : +// CHECK: %1 = global_value @gb_obj +// CHECK-NEXT: return %1 +// CHECK: } // end sil function 'load_global_object' +sil @load_global_object : $@convention(thin) (Builtin.RawPointer) -> @owned B { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @init_gb : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + %3 = global_addr @gb : $*B depends_on %2 + %4 = load %3 : $*B + return %4 : $B +} + +// CHECK-LABEL: sil @load_global_object_keep_once : +// CHECK: %2 = builtin "once" +// CHECK: %3 = global_value @gb_obj +// CHECK: fix_lifetime +// CHECK: return %3 +// CHECK: } // end sil function 'load_global_object_keep_once' +sil @load_global_object_keep_once : $@convention(thin) (Builtin.RawPointer) -> @owned B { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @init_gb : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + %3 = global_addr @gb : $*B depends_on %2 + %4 = load %3 : $*B + %5 = global_addr @gb : $*B depends_on %2 + fix_lifetime %5 : $*B + return %4 : $B +} + +// CHECK-LABEL: sil @load_global_object_from_tuple : +// CHECK: %1 = global_value @gb_obj +// CHECK-NEXT: return %1 +// CHECK: } // end sil function 'load_global_object_from_tuple' +sil @load_global_object_from_tuple : $@convention(thin) (Builtin.RawPointer) -> @owned B { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @init_gb2 : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + %3 = global_addr @gb2 : $*(B, Int64) depends_on %2 + %4 = tuple_element_addr %3 : $*(B, Int64), 0 + %5 = load %4 : $*B + return %5 : $B +} + +// CHECK-LABEL: sil @load_global_tuple : +// CHECK: %1 = global_value @gb_obj +// CHECK: [[T:%.*]] = tuple (%1 : $B, {{%.*}} : $Int64) +// CHECK-NEXT: return [[T]] +// CHECK: } // end sil function 'load_global_tuple' +sil @load_global_tuple : $@convention(thin) (Builtin.RawPointer) -> @owned (B, Int64) { +bb0(%0 : $Builtin.RawPointer): + %1 = function_ref @init_gb2 : $@convention(c) () -> () + %2 = builtin "once"(%0 : $Builtin.RawPointer, %1 : $@convention(c) () -> ()) : $Builtin.SILToken + %3 = global_addr @gb2 : $*(B, Int64) depends_on %2 + %4 = load %3 : $*(B, Int64) + return %4 : $(B, Int64) +} + // CHECK-LABEL: sil @load_first_char_from_string_literal // CHECK: bb0: // CHECK-NEXT: %0 = integer_literal $Builtin.Int8, 97 From fa3a9595249343db2072903a6b3e60fff2c6eda0 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Wed, 3 Jan 2024 12:15:19 +0100 Subject: [PATCH 7/7] ObjectOutliner: support outlining of classes in global let variables ``` let c = SomeClass() ``` is turned into ``` private let outlinedVariable = SomeClass() // statically initialized and allocated in the data section let c = outlinedVariable ``` rdar://111021230 rdar://115502043 Also, make the ObjectOutliner work for OSSA. Though, it currently doesn't run in the OSSA pipeline. --- .../FunctionPasses/ObjectOutliner.swift | 164 +++++++++++------- lib/SIL/IR/ValueOwnership.cpp | 2 +- test/SILOptimizer/objectoutliner.sil | 99 ++++++++++- test/SILOptimizer/static_objects.swift | 56 ++++++ 4 files changed, 254 insertions(+), 67 deletions(-) create mode 100644 test/SILOptimizer/static_objects.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift index ad87b7aa4a911..b6523a27fe84d 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ObjectOutliner.swift @@ -12,8 +12,9 @@ import SIL -/// Outlines COW objects from functions into statically initialized global variables. -/// This is currently only done for Arrays. +/// Outlines class objects from functions into statically initialized global variables. +/// This is currently done for Arrays and for global let variables. +/// /// If a function constructs an Array literal with constant elements (done by storing /// the element values into the array buffer), a new global variable is created which /// contains the constant elements in its static initializer. @@ -26,14 +27,25 @@ import SIL /// ``` /// is turned into /// ``` -/// private let outlinedVariable_from_arrayLookup = [10, 11, 12] // statically initialized +/// private let outlinedVariable = [10, 11, 12] // statically initialized and allocated in the data section /// /// public func arrayLookup(_ i: Int) -> Int { -/// return outlinedVariable_from_arrayLookup[i] +/// return outlinedVariable[i] /// } /// ``` /// -/// As a second optimization, if the array is a string literal which is a parameter to the +/// Similar with global let variables: +/// ``` +/// let c = SomeClass() +/// ``` +/// is turned into +/// ``` +/// private let outlinedVariable = SomeClass() // statically initialized and allocated in the data section +/// +/// let c = outlinedVariable +/// ``` +/// +/// As a second optimization, if an array is a string literal which is a parameter to the /// `_findStringSwitchCase` library function and the array has many elements (> 16), the /// call is redirected to `_findStringSwitchCaseWithCache`. This function builds a cache /// (e.g. a Dictionary) and stores it into a global variable. @@ -41,8 +53,17 @@ import SIL /// let objectOutliner = FunctionPass(name: "object-outliner") { (function: Function, context: FunctionPassContext) in + + if function.hasOwnership && !function.isSwift51RuntimeAvailable { + // Since Swift 5.1 global objects have immortal ref counts. And that's required for ownership. + return + } + for inst in function.instructions { if let ari = inst as? AllocRefInstBase { + if !context.continueWithNextSubpassRun(for: inst) { + return + } if let globalValue = optimizeObjectAllocation(allocRef: ari, context) { optimizeFindStringCall(stringArray: globalValue, context) } @@ -55,14 +76,11 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun return nil } - // The presence of an end_cow_mutation guarantees that the originally initialized - // object is not mutated (because it must be copied before mutation). - guard let endCOW = findEndCOWMutation(of: allocRef), - !endCOW.doKeepUnique else { + guard let endOfInitInst = findEndOfInitialization(of: allocRef) else { return nil } - guard let (storesToClassFields, storesToTailElements) = getInitialization(of: allocRef) else { + guard let (storesToClassFields, storesToTailElements) = getInitialization(of: allocRef, ignore: endOfInitInst) else { return nil } @@ -77,23 +95,32 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun return replace(object: allocRef, with: outlinedGlobal, context) } -private func findEndCOWMutation(of object: Value) -> EndCOWMutationInst? { +// The end-of-initialization is either an end_cow_mutation, because it guarantees that the originally initialized +// object is not mutated (it must be copied before mutation). +// Or it is the store to a global let variable in the global's initializer function. +private func findEndOfInitialization(of object: Value) -> Instruction? { for use in object.uses { - switch use.instruction { - case let uci as UpcastInst: - if let ecm = findEndCOWMutation(of: uci) { - return ecm - } - case let urci as UncheckedRefCastInst: - if let ecm = findEndCOWMutation(of: urci) { - return ecm - } - case let mv as MoveValueInst: - if let ecm = findEndCOWMutation(of: mv) { + let user = use.instruction + switch user { + case is UpcastInst, + is UncheckedRefCastInst, + is MoveValueInst, + is EndInitLetRefInst: + if let ecm = findEndOfInitialization(of: user as! SingleValueInstruction) { return ecm } case let ecm as EndCOWMutationInst: + if ecm.doKeepUnique { + return nil + } return ecm + case let store as StoreInst: + if let ga = store.destination as? GlobalAddrInst, + ga.global.isLet, + ga.parentFunction.initializedGlobal == ga.global + { + return store + } default: break } @@ -101,8 +128,9 @@ private func findEndCOWMutation(of object: Value) -> EndCOWMutationInst? { return nil } -private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassFields: [StoreInst], - storesToTailElements: [StoreInst])? { +private func getInitialization(of allocRef: AllocRefInstBase, ignore ignoreInst: Instruction) + -> (storesToClassFields: [StoreInst], storesToTailElements: [StoreInst])? +{ guard let numTailElements = allocRef.numTailElements else { return nil } @@ -115,9 +143,10 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF // store %0 to %3 // %4 = tuple_element_addr %2, 1 // store %1 to %4 - var tailStores = Array(repeating: nil, count: numTailElements * allocRef.numStoresPerTailElement) + let tailCount = numTailElements != 0 ? numTailElements * allocRef.numStoresPerTailElement : 0 + var tailStores = Array(repeating: nil, count: tailCount) - if !findInitStores(of: allocRef, &fieldStores, &tailStores) { + if !findInitStores(of: allocRef, &fieldStores, &tailStores, ignore: ignoreInst) { return nil } @@ -130,19 +159,17 @@ private func getInitialization(of allocRef: AllocRefInstBase) -> (storesToClassF private func findInitStores(of object: Value, _ fieldStores: inout [StoreInst?], - _ tailStores: inout [StoreInst?]) -> Bool { + _ tailStores: inout [StoreInst?], + ignore ignoreInst: Instruction) -> Bool { for use in object.uses { - switch use.instruction { - case let uci as UpcastInst: - if !findInitStores(of: uci, &fieldStores, &tailStores) { - return false - } - case let urci as UncheckedRefCastInst: - if !findInitStores(of: urci, &fieldStores, &tailStores) { - return false - } - case let mvi as MoveValueInst: - if !findInitStores(of: mvi, &fieldStores, &tailStores) { + let user = use.instruction + switch user { + case is UpcastInst, + is UncheckedRefCastInst, + is MoveValueInst, + is EndInitLetRefInst, + is BeginBorrowInst: + if !findInitStores(of: user as! SingleValueInstruction, &fieldStores, &tailStores, ignore: ignoreInst) { return false } case let rea as RefElementAddrInst: @@ -153,6 +180,9 @@ private func findInitStores(of object: Value, if !findStores(toTailAddress: rta, tailElementIndex: 0, stores: &tailStores) { return false } + case ignoreInst, + is EndBorrowInst: + break default: if !isValidUseOfObject(use) { return false @@ -243,8 +273,7 @@ private func isValidUseOfObject(_ use: Operand) -> Bool { is DeallocStackRefInst, is StrongRetainInst, is StrongReleaseInst, - is FixLifetimeInst, - is EndCOWMutationInst: + is FixLifetimeInst: return true case let mdi as MarkDependenceInst: @@ -314,23 +343,24 @@ private func constructObject(of allocRef: AllocRefInstBase, } let globalBuilder = Builder(staticInitializerOf: global, context) - // Create the initializers for the tail elements. - let numTailTupleElems = allocRef.numStoresPerTailElement - if numTailTupleElems > 1 { - // The elements are tuples: combine numTailTupleElems elements to a single tuple instruction. - for elementIdx in 0.. 1 { + // The elements are tuples: combine numTailTupleElems elements to a single tuple instruction. + for elementIdx in 0..) // CHECK-NEXT: } +// CHECK-LABEL: sil_global private @init_gobjTv_ : $Obj = { +// CHECK-NEXT: %0 = integer_literal $Builtin.Int64, 27 +// CHECK-NEXT: %1 = struct $Int64 (%0 : $Builtin.Int64) +// CHECK-NEXT: %initval = object $Obj (%1 : $Int64) +// CHECK-NEXT: } + // CHECK-LABEL: sil @outline_global_simple // CHECK: [[G:%[0-9]+]] = global_value @outline_global_simpleTv_ : $Obj // CHECK: strong_retain [[G]] : $Obj @@ -146,6 +158,27 @@ bb0: return %r : $() } +// CHECK-LABEL: sil [ossa] @outline_global_with_move_value : +// CHECK: global_value +// CHECK: } // end sil function 'outline_global_with_move_value' +sil [ossa] @outline_global_with_move_value : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Word, 0 + %1 = integer_literal $Builtin.Int64, 1 + %4 = struct $Int64 (%1 : $Builtin.Int64) + %7 = alloc_ref [tail_elems $Int64 * %0 : $Builtin.Word] $Obj + %8 = move_value %7 : $Obj + %9 = begin_borrow %8 : $Obj + %10 = ref_element_addr %9 : $Obj, #Obj.value + store %4 to [trivial] %10 : $*Int64 + end_borrow %9 : $Obj + %11 = end_cow_mutation %8 : $Obj + %12 = begin_dealloc_ref %11 : $Obj of %7 : $Obj + dealloc_ref %12 : $Obj + %r = tuple () + return %r : $() +} + // CHECK-LABEL: sil @outline_global_tailelems // CHECK: [[G:%[0-9]+]] = global_value @outline_global_tailelemsTv_ : $Obj // CHECK: strong_retain [[G]] : $Obj @@ -197,18 +230,35 @@ bb0: return %r : $() } -// CHECK-LABEL: sil @dont_outline_without_tail_elems +// CHECK-LABEL: sil @dont_outline_without_end_cow_mutation // CHECK: alloc_ref // CHECK: store // CHECK: return -sil @dont_outline_without_tail_elems : $@convention(thin) () -> () { +sil @dont_outline_without_end_cow_mutation : $@convention(thin) () -> () { bb0: %1 = integer_literal $Builtin.Int64, 1 %4 = struct $Int64 (%1 : $Builtin.Int64) %7 = alloc_ref $Obj %9 = ref_element_addr %7 : $Obj, #Obj.value store %4 to %9 : $*Int64 - %10 = end_cow_mutation %7 : $Obj + strong_release %7 : $Obj + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil @dont_outline_keep_unique +// CHECK: alloc_ref +// CHECK: store +// CHECK: } // end sil function 'dont_outline_keep_unique' +sil @dont_outline_keep_unique : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Word, 0 + %1 = integer_literal $Builtin.Int64, 1 + %4 = struct $Int64 (%1 : $Builtin.Int64) + %7 = alloc_ref [tail_elems $Int64 * %0 : $Builtin.Word] $Obj + %9 = ref_element_addr %7 : $Obj, #Obj.value + store %4 to %9 : $*Int64 + %10 = end_cow_mutation [keep_unique] %7 : $Obj strong_release %10 : $Obj %r = tuple () return %r : $() @@ -448,3 +498,44 @@ bb0: return %r : $() } +// CHECK-LABEL: sil [global_init_once_fn] @init_gobj : +// CHECK: [[A:%.*]] = global_addr @gobj : $*Obj +// CHECK: [[G:%[0-9]+]] = global_value @init_gobjTv_ : $Obj +// CHECK: strong_retain [[G]] : $Obj +// CHECK: store [[G]] to [[A]] +// CHECK: } // end sil function 'init_gobj' +sil [global_init_once_fn] @init_gobj : $@convention(c) (Builtin.RawPointer) -> () { +bb0(%0 : $Builtin.RawPointer): + alloc_global @gobj + %2 = global_addr @gobj : $*Obj + %3 = integer_literal $Builtin.Int64, 27 + %4 = struct $Int64 (%3 : $Builtin.Int64) + %5 = alloc_ref $Obj + %8 = end_init_let_ref %5 : $Obj + %9 = ref_element_addr %8 : $Obj, #Obj.value + store %4 to %9 : $*Int64 + store %8 to %2 : $*Obj + %12 = tuple () + return %12 : $() +} + +// CHECK-LABEL: sil [global_init_once_fn] @init_gobj_var : +// CHECK: alloc_ref +// CHECK-NOT: global_value +// CHECK: } // end sil function 'init_gobj_var' +sil [global_init_once_fn] @init_gobj_var : $@convention(c) (Builtin.RawPointer) -> () { +bb0(%0 : $Builtin.RawPointer): + alloc_global @gobj_var + %2 = global_addr @gobj_var : $*Obj + %3 = integer_literal $Builtin.Int64, 27 + %4 = struct $Int64 (%3 : $Builtin.Int64) + %5 = alloc_ref $Obj + %8 = end_init_let_ref %5 : $Obj + %9 = ref_element_addr %8 : $Obj, #Obj.value + store %4 to %9 : $*Int64 + store %8 to %2 : $*Obj + %12 = tuple () + return %12 : $() +} + + diff --git a/test/SILOptimizer/static_objects.swift b/test/SILOptimizer/static_objects.swift new file mode 100644 index 0000000000000..b5ee2dc7e03be --- /dev/null +++ b/test/SILOptimizer/static_objects.swift @@ -0,0 +1,56 @@ +// RUN: %target-swift-frontend -parse-as-library %s -O -sil-verify-all -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test -emit-sil | %FileCheck %s +// RUN: %target-swift-frontend -parse-as-library %s -O -sil-verify-all -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test -emit-ir | %FileCheck %s -check-prefix=CHECK-LLVM + +// Also do an end-to-end test to check all components, including IRGen. +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -parse-as-library -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test %s -o %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT +// REQUIRES: executable_test,swift_stdlib_no_asserts,optimized_stdlib +// REQUIRES: CPU=arm64 || CPU=x86_64 +// REQUIRES: swift_in_compiler + + +public class C { + var x: Int + + init(x: Int) { + self.x = x + } +} + +public class D: C {} + +// CHECK-LABEL: sil_global private @$s4test1c_WZTv_ : $C = { +// CHECK-NEXT: %0 = integer_literal $Builtin.Int64, 27 // user: %1 +// CHECK-NEXT: %1 = struct $Int (%0 : $Builtin.Int64) // user: %2 +// CHECK-NEXT: %initval = object $C (%1 : $Int) +// CHECK-NEXT: } + +// CHECK-LLVM-LABEL: @"$s4test1c_WZTv_" ={{.*}} global %{{[a-zA-Z_0-9]*}}c { [1 x i64] zeroinitializer, %{{[a-zA-Z_0-9]*}} <{ %swift.refcounted zeroinitializer, %TSi <{ i64 27 }> } + +public let c = C(x: 27) + + +// CHECK-LABEL: sil [noinline] @$s4test6testitAA1CCyF : $@convention(thin) () -> @owned C { +// CHECK: [[C:%.*]] = global_value @$s4test1c_WZTv_ : $C +// CHECK-NEXT: return [[C]] +// CHECK: } // end sil function '$s4test6testitAA1CCyF' + +// CHECK-LLVM-LABEL: define {{.*}} @"$s4test6testitAA1CCyF" +// CHECK-LLVM: [[C:%.*]] = tail call ptr @swift_initStaticObject({{.*}} getelementptr {{.*}}, ptr @"$s4test1c_WZTv_", i64 0, i32 1)) +// CHECK-LLVM-NEXT: ret ptr [[C]] +// CHECK-LLVM: } +@inline(never) +public func testit() -> C { + return c +} + +@main struct Main { + static func main() { + // CHECK-OUTPUT: c.x=27 + print("c.x=\(testit().x)") + // CHECK-OUTPUT: c=test.C + print("c=\(testit())") + } +} +