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/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/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/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/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/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/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})); } 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() +