From ca291fb1471877b015397814508bce09b7b51e23 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 23 Jun 2022 12:05:43 +0200 Subject: [PATCH 1/2] SIL: store IRGenOptions in SILModule SIL optimization passes can use the IRGen options to do target specific optimizations. --- include/swift/AST/SILGenRequests.h | 12 ++++++++---- include/swift/SIL/SILModule.h | 14 ++++++++++++-- include/swift/Subsystems.h | 6 ++++-- lib/FrontendTool/FrontendTool.cpp | 7 +++++-- lib/IRGen/IRGen.cpp | 2 +- lib/SIL/IR/SILModule.cpp | 12 ++++++++---- lib/SILGen/SILGen.cpp | 13 ++++++++----- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/include/swift/AST/SILGenRequests.h b/include/swift/AST/SILGenRequests.h index cb7e8c5825f2c..b5b6821230cb7 100644 --- a/include/swift/AST/SILGenRequests.h +++ b/include/swift/AST/SILGenRequests.h @@ -29,6 +29,7 @@ class LangOptions; class ModuleDecl; class SILModule; class SILOptions; +class IRGenOptions; namespace Lowering { class TypeConverter; @@ -47,6 +48,7 @@ struct ASTLoweringDescriptor { llvm::PointerUnion context; Lowering::TypeConverter &conv; const SILOptions &opts; + const IRGenOptions *irgenOptions; /// A specific set of SILDeclRefs to emit. If set, only these refs will be /// emitted. Otherwise the entire \c context will be emitted. @@ -74,15 +76,17 @@ struct ASTLoweringDescriptor { public: static ASTLoweringDescriptor forFile(FileUnit &sf, Lowering::TypeConverter &conv, const SILOptions &opts, - Optional refsToEmit = None) { - return ASTLoweringDescriptor{&sf, conv, opts, refsToEmit}; + Optional refsToEmit = None, + const IRGenOptions *irgenOptions = nullptr) { + return ASTLoweringDescriptor{&sf, conv, opts, irgenOptions, refsToEmit}; } static ASTLoweringDescriptor forWholeModule(ModuleDecl *mod, Lowering::TypeConverter &conv, const SILOptions &opts, - Optional refsToEmit = None) { - return ASTLoweringDescriptor{mod, conv, opts, refsToEmit}; + Optional refsToEmit = None, + const IRGenOptions *irgenOptions = nullptr) { + return ASTLoweringDescriptor{mod, conv, opts, irgenOptions, refsToEmit}; } /// Retrieves the files to generate SIL for. If the descriptor is configured diff --git a/include/swift/SIL/SILModule.h b/include/swift/SIL/SILModule.h index 2160ec6e23fef..7e727a2833854 100644 --- a/include/swift/SIL/SILModule.h +++ b/include/swift/SIL/SILModule.h @@ -103,6 +103,7 @@ class AnyFunctionType; class ASTContext; class FileUnit; class FuncDecl; +class IRGenOptions; class KeyPathPattern; class ModuleDecl; class SILUndef; @@ -353,6 +354,12 @@ class SILModule { /// The options passed into this SILModule. const SILOptions &Options; + /// IRGen options to be used by target specific SIL optimization passes. + /// + /// Not null, if the module is created by the compiler itself (and not + /// e.g. by lldb). + const IRGenOptions *irgenOptions; + /// The number of functions created in this module, which will be the index of /// the next function. unsigned nextFunctionIndex = 0; @@ -382,7 +389,8 @@ class SILModule { #endif SILModule(llvm::PointerUnion context, - Lowering::TypeConverter &TC, const SILOptions &Options); + Lowering::TypeConverter &TC, const SILOptions &Options, + const IRGenOptions *irgenOptions = nullptr); SILModule(const SILModule&) = delete; void operator=(const SILModule&) = delete; @@ -537,7 +545,8 @@ class SILModule { /// single-file mode, and a ModuleDecl in whole-module mode. static std::unique_ptr createEmptyModule(llvm::PointerUnion context, - Lowering::TypeConverter &TC, const SILOptions &Options); + Lowering::TypeConverter &TC, const SILOptions &Options, + const IRGenOptions *irgenOptions = nullptr); /// Get the Swift module associated with this SIL module. ModuleDecl *getSwiftModule() const { return TheSwiftModule; } @@ -570,6 +579,7 @@ class SILModule { bool isOptimizedOnoneSupportModule() const; const SILOptions &getOptions() const { return Options; } + const IRGenOptions *getIRGenOptionsOrNull() const { return irgenOptions; } using iterator = FunctionListType::iterator; using const_iterator = FunctionListType::const_iterator; diff --git a/include/swift/Subsystems.h b/include/swift/Subsystems.h index e3dcfe4bd7df8..2ab41c13e1120 100644 --- a/include/swift/Subsystems.h +++ b/include/swift/Subsystems.h @@ -179,12 +179,14 @@ namespace swift { /// SIL of all files in the module is present in the SILModule. std::unique_ptr performASTLowering(ModuleDecl *M, Lowering::TypeConverter &TC, - const SILOptions &options); + const SILOptions &options, + const IRGenOptions *irgenOptions = nullptr); /// Turn a source file into SIL IR. std::unique_ptr performASTLowering(FileUnit &SF, Lowering::TypeConverter &TC, - const SILOptions &options); + const SILOptions &options, + const IRGenOptions *irgenOptions = nullptr); using ModuleOrSourceFile = PointerUnion; diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index 62e733bd89b0d..a0b43282c7538 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -766,7 +766,9 @@ bool swift::performCompileStepsPostSema(CompilerInstance &Instance, const PrimarySpecificPaths PSPs = Instance.getPrimarySpecificPathsForWholeModuleOptimizationMode(); SILOptions SILOpts = getSILOptions(PSPs); - auto SM = performASTLowering(mod, Instance.getSILTypes(), SILOpts); + IRGenOptions irgenOpts = Invocation.getIRGenOptions(); + auto SM = performASTLowering(mod, Instance.getSILTypes(), SILOpts, + &irgenOpts); return performCompileStepsPostSILGen(Instance, std::move(SM), mod, PSPs, ReturnValue, observer); } @@ -779,8 +781,9 @@ bool swift::performCompileStepsPostSema(CompilerInstance &Instance, const PrimarySpecificPaths PSPs = Instance.getPrimarySpecificPathsForSourceFile(*PrimaryFile); SILOptions SILOpts = getSILOptions(PSPs); + IRGenOptions irgenOpts = Invocation.getIRGenOptions(); auto SM = performASTLowering(*PrimaryFile, Instance.getSILTypes(), - SILOpts); + SILOpts, &irgenOpts); result |= performCompileStepsPostSILGen(Instance, std::move(SM), PrimaryFile, PSPs, ReturnValue, observer); diff --git a/lib/IRGen/IRGen.cpp b/lib/IRGen/IRGen.cpp index 450e577d8b9a6..78a75899543ff 100644 --- a/lib/IRGen/IRGen.cpp +++ b/lib/IRGen/IRGen.cpp @@ -1059,7 +1059,7 @@ GeneratedModule IRGenRequest::evaluate(Evaluator &evaluator, auto SILMod = std::unique_ptr(desc.SILMod); if (!SILMod) { auto loweringDesc = ASTLoweringDescriptor{ - desc.Ctx, desc.Conv, desc.SILOpts, + desc.Ctx, desc.Conv, desc.SILOpts, nullptr, symsToEmit.map([](const auto &x) { return x.silRefsToEmit; })}; SILMod = llvm::cantFail(Ctx.evaluator(LoweredSILRequest{loweringDesc})); diff --git a/lib/SIL/IR/SILModule.cpp b/lib/SIL/IR/SILModule.cpp index f4107c515c8b7..ffb98490770ec 100644 --- a/lib/SIL/IR/SILModule.cpp +++ b/lib/SIL/IR/SILModule.cpp @@ -90,9 +90,11 @@ class SILModule::SerializationCallback final }; SILModule::SILModule(llvm::PointerUnion context, - Lowering::TypeConverter &TC, const SILOptions &Options) + Lowering::TypeConverter &TC, const SILOptions &Options, + const IRGenOptions *irgenOptions) : Stage(SILStage::Raw), loweredAddresses(!Options.EnableSILOpaqueValues), - indexTrieRoot(new IndexTrieNode()), Options(Options), serialized(false), + indexTrieRoot(new IndexTrieNode()), Options(Options), + irgenOptions(irgenOptions), serialized(false), regDeserializationNotificationHandlerForNonTransparentFuncOME(false), regDeserializationNotificationHandlerForAllFuncOME(false), prespecializedFunctionDeclsImported(false), SerializeSILAction(), @@ -203,8 +205,10 @@ void SILModule::checkForLeaksAfterDestruction() { std::unique_ptr SILModule::createEmptyModule( llvm::PointerUnion context, - Lowering::TypeConverter &TC, const SILOptions &Options) { - return std::unique_ptr(new SILModule(context, TC, Options)); + Lowering::TypeConverter &TC, const SILOptions &Options, + const IRGenOptions *irgenOptions) { + return std::unique_ptr(new SILModule(context, TC, Options, + irgenOptions)); } ASTContext &SILModule::getASTContext() const { diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 34161eba4762b..ebe552849075d 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -2243,7 +2243,7 @@ ASTLoweringRequest::evaluate(Evaluator &evaluator, SILInstruction::resetInstructionCounts(); auto silMod = SILModule::createEmptyModule(desc.context, desc.conv, - desc.opts); + desc.opts, desc.irgenOptions); // If all function bodies are being skipped there's no reason to do any // SIL generation. @@ -2285,15 +2285,18 @@ ASTLoweringRequest::evaluate(Evaluator &evaluator, std::unique_ptr swift::performASTLowering(ModuleDecl *mod, Lowering::TypeConverter &tc, - const SILOptions &options) { - auto desc = ASTLoweringDescriptor::forWholeModule(mod, tc, options); + const SILOptions &options, + const IRGenOptions *irgenOptions) { + auto desc = ASTLoweringDescriptor::forWholeModule(mod, tc, options, + None, irgenOptions); return llvm::cantFail( mod->getASTContext().evaluator(ASTLoweringRequest{desc})); } std::unique_ptr swift::performASTLowering(FileUnit &sf, Lowering::TypeConverter &tc, - const SILOptions &options) { - auto desc = ASTLoweringDescriptor::forFile(sf, tc, options); + const SILOptions &options, + const IRGenOptions *irgenOptions) { + auto desc = ASTLoweringDescriptor::forFile(sf, tc, options, None, irgenOptions); return llvm::cantFail(sf.getASTContext().evaluator(ASTLoweringRequest{desc})); } From f420468b279a2afbd5fd2ee9ea45839087ce1eb4 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 23 Jun 2022 16:45:27 +0200 Subject: [PATCH 2/2] SILOptimizer: add a pass to perform target specific constant folding. TargetConstantFolding performs constant folding for target-specific values: ``` MemoryLayout.size MemoryLayout.alignment MemoryLayout.stride ``` Constant folding those expressions in the middle of the SIL pipeline enables other optimizations to e.g. allow such expressions in statically allocated global variables (done by the GlobalOpt pass). The implementation requires to create a temporary IRGenModule, which is used to get actual constant sizes/alignments from IRGen's type lowering. rdar://94831524 --- .../swift/SILOptimizer/PassManager/Passes.def | 2 + lib/IRGen/CMakeLists.txt | 1 + lib/IRGen/TargetConstantFolding.cpp | 142 ++++++++++++++++++ lib/SILOptimizer/PassManager/PassPipeline.cpp | 1 + .../devirt_protocol_method_invocations.swift | 5 +- test/SILOptimizer/target-const-prop.swift | 81 ++++++++++ 6 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 lib/IRGen/TargetConstantFolding.cpp create mode 100644 test/SILOptimizer/target-const-prop.swift diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index bc4eced69ce30..39823a40b90e9 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -356,6 +356,8 @@ PASS(ReleaseHoisting, "release-hoisting", "SIL release Hoisting") PASS(LateReleaseHoisting, "late-release-hoisting", "Late SIL release Hoisting Preserving Epilogues") +PASS(TargetConstantFolding, "target-constant-folding", + "Target specific constant folding") IRGEN_PASS(LoadableByAddress, "loadable-address", "SIL Large Loadable type by-address lowering.") PASS(MandatorySILLinker, "mandatory-linker", diff --git a/lib/IRGen/CMakeLists.txt b/lib/IRGen/CMakeLists.txt index 1661286be3b29..2cb17afc39eee 100644 --- a/lib/IRGen/CMakeLists.txt +++ b/lib/IRGen/CMakeLists.txt @@ -57,6 +57,7 @@ add_swift_host_library(swiftIRGen STATIC Outlining.cpp StructLayout.cpp SwiftTargetInfo.cpp + TargetConstantFolding.cpp TypeLayout.cpp TypeLayoutDumper.cpp TypeLayoutVerifier.cpp diff --git a/lib/IRGen/TargetConstantFolding.cpp b/lib/IRGen/TargetConstantFolding.cpp new file mode 100644 index 0000000000000..51cf7ddaaf957 --- /dev/null +++ b/lib/IRGen/TargetConstantFolding.cpp @@ -0,0 +1,142 @@ +//===--- TargetConstantFolding.cpp ----------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 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 +// +// This pass lowers loadable SILTypes. On completion, the SILType of every +// function argument is an address instead of the type itself. +// This reduces the code size. +// Consequently, this pass is required for IRGen. +// It is a mandatory IRGen preparation pass (not a diagnostic pass). +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "target-constant-folding" +#include "IRGenModule.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "llvm/Support/Debug.h" + +using namespace swift; +using namespace swift::irgen; + +namespace { + +/// Performs constant folding for target-specific values. +/// +/// Specifically, this optimization constant folds +/// ``` +/// MemoryLayout.size +/// MemoryLayout.alignment +/// MemoryLayout.stride +/// ``` +/// Constant folding those expressions in the middle of the SIL pipeline +/// enables other optimizations to e.g. allow such expressions in statically +/// allocated global variables (done by the GlobalOpt pass). +class TargetConstantFolding : public SILModuleTransform { +private: + /// The entry point to the transformation. + void run() override { + auto *irgenOpts = getModule()->getIRGenOptionsOrNull(); + if (!irgenOpts) + return; + + // We need an IRGenModule to get the actual sizes from type lowering. + // Creating an IRGenModule involves some effort. Therefore this is a + // module pass rather than a function pass so that this one-time setup + // only needs to be done once and not for all functions in a module. + IRGenerator irgen(*irgenOpts, *getModule()); + auto targetMachine = irgen.createTargetMachine(); + if (!targetMachine) + return; + IRGenModule IGM(irgen, std::move(targetMachine)); + + // Scan all instructions in the module for constant foldable instructions. + for (SILFunction &function : *getModule()) { + + if (!function.shouldOptimize()) + continue; + + bool changed = false; + for (SILBasicBlock &block : function) { + InstructionDeleter deleter; + + for (SILInstruction *inst : deleter.updatingRange(&block)) { + if (constFold(inst, IGM)) { + deleter.forceDelete(inst); + changed = true; + } + } + deleter.cleanupDeadInstructions(); + } + if (changed) { + invalidateAnalysis(&function, SILAnalysis::InvalidationKind::Instructions); + } + } + } + + /// Constant fold a single instruction. + /// + /// Returns true if `inst` was replaced and can be deleted. + bool constFold(SILInstruction *inst, IRGenModule &IGM) { + auto *bi = dyn_cast(inst); + if (!bi) + return false; + + llvm::Constant *c = nullptr; + uint64_t offset = 0; + + switch (bi->getBuiltinInfo().ID) { + case BuiltinValueKind::Sizeof: + c = getTypeInfoOfBuiltin(bi, IGM).getStaticSize(IGM); + break; + case BuiltinValueKind::Alignof: + c = getTypeInfoOfBuiltin(bi, IGM).getStaticAlignmentMask(IGM); + // The constant is the alignment _mask_. We get the actual alignment by + // adding 1. + offset = 1; + break; + case BuiltinValueKind::Strideof: + c = getTypeInfoOfBuiltin(bi, IGM).getStaticStride(IGM); + break; + default: + return false; + } + auto *intConst = dyn_cast_or_null(c); + if (!intConst) + return false; + + APInt value = intConst->getValue(); + value += APInt(value.getBitWidth(), offset); + auto intTy = bi->getType().getAs(); + if (!intTy) + return false; + + value = value.sextOrTrunc(intTy->getGreatestWidth()); + + // Replace the builtin by an integer literal. + SILBuilderWithScope builder(bi); + auto *intLit = builder.createIntegerLiteral(bi->getLoc(), bi->getType(), + value); + bi->replaceAllUsesWith(intLit); + return true; + } + + const TypeInfo &getTypeInfoOfBuiltin(BuiltinInst *bi, IRGenModule &IGM) { + SubstitutionMap subs = bi->getSubstitutions(); + SILType lowered = IGM.getLoweredType(subs.getReplacementTypes()[0]); + return IGM.getTypeInfo(lowered); + } +}; + +} // end anonymous namespace + +SILTransform *swift::createTargetConstantFolding() { + return new TargetConstantFolding(); +} diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index ab808d7983d6f..e324e7663356a 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -666,6 +666,7 @@ static void addMidLevelFunctionPipeline(SILPassPipelinePlan &P) { static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) { P.startPipeline("ClosureSpecialize"); P.addDeadFunctionAndGlobalElimination(); + P.addTargetConstantFolding(); P.addDeadStoreElimination(); P.addDeadObjectElimination(); diff --git a/test/SILOptimizer/devirt_protocol_method_invocations.swift b/test/SILOptimizer/devirt_protocol_method_invocations.swift index 932f624584232..68dd15084c47b 100644 --- a/test/SILOptimizer/devirt_protocol_method_invocations.swift +++ b/test/SILOptimizer/devirt_protocol_method_invocations.swift @@ -138,9 +138,8 @@ public func test_devirt_protocol_extension_method_invocation_with_self_return_ty // CHECK: return [[T1]] // CHECK: sil @$s34devirt_protocol_method_invocations14testExMetatypeSiyF -// CHECK: [[T0:%.*]] = builtin "sizeof" -// CHECK: [[T1:%.*]] = builtin {{.*}}([[T0]] -// CHECK: [[T2:%.*]] = struct $Int ([[T1]] : {{.*}}) +// CHECK: [[T0:%.*]] = integer_literal +// CHECK: [[T2:%.*]] = struct $Int ([[T0]] : {{.*}}) // CHECK: return [[T2]] : $Int @inline(never) diff --git a/test/SILOptimizer/target-const-prop.swift b/test/SILOptimizer/target-const-prop.swift new file mode 100644 index 0000000000000..86be93071bb4c --- /dev/null +++ b/test/SILOptimizer/target-const-prop.swift @@ -0,0 +1,81 @@ +// RUN: %target-swift-frontend -primary-file %s -O -sil-verify-all -module-name=test -emit-sil | %FileCheck %s + +// Make a runtime test to check that the values are correct. +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -O -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 + + +struct S { + var i: Int + var b: Int8 + + // CHECK-LABEL: sil_global hidden [let] @$s4test1SV4sizeSivpZ : $Int = { + // CHECK-NEXT: integer_literal + static let size = MemoryLayout.size + + // CHECK-LABEL: sil_global hidden [let] @$s4test1SV9alignmentSivpZ : $Int = { + // CHECK-NEXT: integer_literal + static let alignment = MemoryLayout.alignment + + // CHECK-LABEL: sil_global hidden [let] @$s4test1SV6strideSivpZ : $Int = { + // CHECK-NEXT: integer_literal + static let stride = MemoryLayout.stride + + static let doubleSize = MemoryLayout.size * 2 +} + +// CHECK-LABEL: sil @$s4test14noConstantSizeySixmlF +// CHECK: builtin "sizeof" +// CHECK: } // end sil function '$s4test14noConstantSizeySixmlF' +public func noConstantSize(_ t: T.Type) -> Int { + return MemoryLayout.size +} + +// Check that there is not constant propagation if optimizations are disabled. +// This is important for the runtime check to make sure that we are comparing +// SIL constant propagated values with IRGen values. + +// CHECK-LABEL: sil {{.*}} @$s4test7getSizeSiyF +// CHECK: builtin "sizeof" +// CHECK: } // end sil function '$s4test7getSizeSiyF' +@_optimize(none) +func getSize() -> Int { + return MemoryLayout.size +} + +// CHECK-LABEL: sil {{.*}} @$s4test12getAlignmentSiyF +// CHECK: builtin "alignof" +// CHECK: } // end sil function '$s4test12getAlignmentSiyF' +@_optimize(none) +func getAlignment() -> Int { + return MemoryLayout.alignment +} + +// CHECK-LABEL: sil {{.*}} @$s4test9getStrideSiyF +// CHECK: builtin "strideof" +// CHECK: } // end sil function '$s4test9getStrideSiyF' +@_optimize(none) +func getStride() -> Int { + return MemoryLayout.stride +} + +@inline(never) +func testit() { + // CHECK-OUTPUT: size: true + print("size: \(S.size == getSize())") + + // CHECK-OUTPUT: alignment: true + print("alignment: \(S.alignment == getAlignment())") + + // CHECK-OUTPUT: stride: true + print("stride: \(S.stride == getStride())") + + // CHECK-OUTPUT: doubleSize: true + print("doubleSize: \(S.doubleSize == getSize() * 2)") +} + +testit() +