diff --git a/Sources/LLVM/PassManager.swift b/Sources/LLVM/PassManager.swift index b41b431a..7d30cf2d 100644 --- a/Sources/LLVM/PassManager.swift +++ b/Sources/LLVM/PassManager.swift @@ -3,7 +3,7 @@ import cllvm #endif /// A subset of supported LLVM IR optimizer passes. -public enum FunctionPass { +public enum Pass { /// This pass uses the SSA based Aggressive DCE algorithm. This algorithm /// assumes instructions are dead until proven otherwise, which makes /// it more successful are removing non-obviously dead instructions. @@ -44,6 +44,20 @@ public enum FunctionPass { /// %Z = add int 2, %X /// ``` case instructionCombining + /// Working in conjunction with the linker, iterate through all functions and + /// global values in the module and attempt to change their linkage from + /// external to internal. + /// + /// To preserve the linkage of a global value, return `true` from the given + /// callback. + case internalize(mustPreserve: (IRGlobal) -> Bool) + /// Working in conjunction with the linker, iterate through all functions and + /// global values in the module and attempt to change their linkage from + /// external to internal. + /// + /// When a function with the name "main" is encountered, if the value of + /// `preserveMain` is `true`, "main" will not be internalized. + case internalizeAll(preserveMain: Bool) /// Thread control through mult-pred/multi-succ blocks where some preds /// always go to some succ. Thresholds other than minus one override the /// internal BB duplication default threshold. @@ -75,7 +89,7 @@ public enum FunctionPass { /// This pass converts SwitchInst instructions into a sequence of chained /// binary branch instructions. case lowerSwitch - /// This pass is used to promote memory references to + /// This pass is used to promote memory references to /// be register references. A simple example of the transformation performed /// by this pass is going from code like this: /// @@ -92,6 +106,10 @@ public enum FunctionPass { /// ret i32 42 /// ``` case promoteMemoryToRegister + /// Adds DWARF discriminators to the IR. Discriminators are + /// used to decide what CFG path was taken inside sub-graphs whose instructions + /// share the same line and column number information. + case addDiscriminators /// This pass reassociates commutative expressions in an order that /// is designed to promote better constant propagation, GCSE, LICM, PRE, etc. /// @@ -102,12 +120,6 @@ public enum FunctionPass { case reassociate /// Sparse conditional constant propagation. case sccp - /// Replace aggregates or pieces of aggregates with scalar SSA values. - case scalarReplAggregates - /// Replace aggregates or pieces of aggregates with scalar SSA values. - case scalarReplAggregatesSSA - /// Tries to inline the fast path of library calls such as sqrt. - case simplifyLibCalls /// This pass eliminates call instructions to the current function which occur /// immediately before return instructions. case tailCallElimination @@ -170,6 +182,8 @@ public enum FunctionPass { /// Return a new pass object which transforms invoke instructions into calls, /// if the callee can *not* unwind the stack. case pruneEH + + case scalarReplacementOfAggregates /// This pass removes any function declarations (prototypes) that are not used. case stripDeadPrototypes /// These functions removes symbols from functions and modules without @@ -181,73 +195,26 @@ public enum FunctionPass { /// This pass performs a superword-level parallelism pass to combine /// similar independent instructions into vector instructions. case slpVectorize + /// An invalid pass that crashes when added to the pass manager. + case invalid(reason: String) +} + +extension Pass { + @available(*, deprecated, message: "Pass has been removed") + static let simplifyLibCalls: Pass = .invalid(reason: "Pass has been removed") + @available(*, deprecated, message: "Use the scalarReplacementOfAggregates instead") + static let scalarReplAggregates: Pass = .invalid(reason: "Pass has been renamed to 'scalarReplacementOfAggregates'") + @available(*, deprecated, message: "Use the scalarReplacementOfAggregates instead") + static let scalarReplAggregatesSSA: Pass = .invalid(reason: "Pass has been renamed to 'scalarReplacementOfAggregates'") } /// A `FunctionPassManager` is an object that collects a sequence of passes /// which run over a particular IR construct, and runs each of them in sequence /// over each such construct. +@available(*, deprecated, message: "Use the PassPipeliner instead") public class FunctionPassManager { internal let llvm: LLVMPassManagerRef - - private static let passMapping: [FunctionPass: (LLVMPassManagerRef) -> Void] = [ - .aggressiveDCE: LLVMAddAggressiveDCEPass, - .bitTrackingDCE: LLVMAddBitTrackingDCEPass, - .alignmentFromAssumptions: LLVMAddAlignmentFromAssumptionsPass, - .cfgSimplification: LLVMAddCFGSimplificationPass, - .deadStoreElimination: LLVMAddDeadStoreEliminationPass, - .scalarizer: LLVMAddScalarizerPass, - .mergedLoadStoreMotion: LLVMAddMergedLoadStoreMotionPass, - .gvn: LLVMAddGVNPass, - .indVarSimplify: LLVMAddIndVarSimplifyPass, - .instructionCombining: LLVMAddInstructionCombiningPass, - .jumpThreading: LLVMAddJumpThreadingPass, - .licm: LLVMAddLICMPass, - .loopDeletion: LLVMAddLoopDeletionPass, - .loopIdiom: LLVMAddLoopIdiomPass, - .loopRotate: LLVMAddLoopRotatePass, - .loopReroll: LLVMAddLoopRerollPass, - .loopUnroll: LLVMAddLoopUnrollPass, - .loopUnrollAndJam: LLVMAddLoopUnrollAndJamPass, - .loopUnswitch: LLVMAddLoopUnswitchPass, - .lowerAtomic: LLVMAddLowerAtomicPass, - .memCpyOpt: LLVMAddMemCpyOptPass, - .partiallyInlineLibCalls: LLVMAddPartiallyInlineLibCallsPass, - .lowerSwitch: LLVMAddLowerSwitchPass, - .promoteMemoryToRegister: LLVMAddPromoteMemoryToRegisterPass, - .reassociate: LLVMAddReassociatePass, - .sccp: LLVMAddSCCPPass, - .scalarReplAggregates: LLVMAddScalarReplAggregatesPass, - .scalarReplAggregatesSSA: LLVMAddScalarReplAggregatesPassSSA, - .simplifyLibCalls: LLVMAddSimplifyLibCallsPass, - .tailCallElimination: LLVMAddTailCallEliminationPass, - .constantPropagation: LLVMAddConstantPropagationPass, - .demoteMemoryToRegister: LLVMAddDemoteMemoryToRegisterPass, - .verifier: LLVMAddVerifierPass, - .correlatedValuePropagation: LLVMAddCorrelatedValuePropagationPass, - .earlyCSE: LLVMAddEarlyCSEPass, - .lowerExpectIntrinsic: LLVMAddLowerExpectIntrinsicPass, - .typeBasedAliasAnalysis: LLVMAddTypeBasedAliasAnalysisPass, - .scopedNoAliasAA: LLVMAddScopedNoAliasAAPass, - .basicAliasAnalysis: LLVMAddBasicAliasAnalysisPass, - .unifyFunctionExitNodes: LLVMAddUnifyFunctionExitNodesPass, - .alwaysInliner: LLVMAddAlwaysInlinerPass, - .argumentPromotion: LLVMAddArgumentPromotionPass, - .constantMerge: LLVMAddConstantMergePass, - .deadArgElimination: LLVMAddDeadArgEliminationPass, - .functionAttrs: LLVMAddFunctionAttrsPass, - .functionInlining: LLVMAddFunctionInliningPass, - .globalDCE: LLVMAddGlobalDCEPass, - .globalOptimizer: LLVMAddGlobalOptimizerPass, - .ipConstantPropagation: LLVMAddIPConstantPropagationPass, - .ipscc: LLVMAddIPSCCPPass, - .pruneEH: LLVMAddPruneEHPass, - .stripDeadPrototypes: LLVMAddStripDeadPrototypesPass, - .stripSymbols: LLVMAddStripSymbolsPass, - .loopVectorize: LLVMAddLoopVectorizePass, - .slpVectorize: LLVMAddSLPVectorizePass, - // .internalize: LLVMAddInternalizePass, - // .sroaWithThreshhold: LLVMAddScalarReplAggregatesPassWithThreshold, - ] + var alivePassObjects = [Any]() /// Creates a `FunctionPassManager` bound to the given module's IR. public init(module: Module) { @@ -259,9 +226,9 @@ public class FunctionPassManager { /// /// - parameter passes: A list of function passes to add to the pass manager's /// list of passes to run. - public func add(_ passes: FunctionPass...) { + public func add(_ passes: Pass...) { for pass in passes { - FunctionPassManager.passMapping[pass]!(llvm) + PassPipeliner.configurePass(pass, passManager: llvm, keepalive: &alivePassObjects) } } @@ -272,3 +239,7 @@ public class FunctionPassManager { LLVMRunFunctionPassManager(llvm, function.asLLVM()) } } + +@available(*, deprecated, renamed: "Pass") +public typealias FunctionPass = Pass + diff --git a/Sources/LLVM/PassPipeliner.swift b/Sources/LLVM/PassPipeliner.swift new file mode 100644 index 00000000..5039f7df --- /dev/null +++ b/Sources/LLVM/PassPipeliner.swift @@ -0,0 +1,403 @@ +#if SWIFT_PACKAGE +import cllvm +import llvmshims +#endif + +/// Implements a pass manager, pipeliner, and executor for a set of +/// user-provided optimization passes. +/// +/// A `PassPipeliner` handles the creation of a related set of optimization +/// passes called a "pipeline". Grouping passes is done for multiple reasons, +/// chief among them is that optimizer passes are extremely sensitive to their +/// ordering relative to other passses. In addition, pass groupings allow for +/// the clean segregation of otherwise unrelated passes. For example, a +/// pipeline might consist of "mandatory" passes such as Jump Threading, LICM, +/// and DCE in one pipeline and "diagnostic" passes in another. +public final class PassPipeliner { + private enum PipelinePlan { + case builtinPasses([Pass]) + case functionPassManager(LLVMPassManagerRef) + case modulePassManager(LLVMPassManagerRef) + } + + /// The module for this pass pipeline. + public let module: Module + /// The pipeline stages registered with this pass pipeliner. + public private(set) var stages: [String] + + private var stageMapping: [String: PipelinePlan] + private var frozen: Bool = false + + public final class Builder { + fileprivate var passes: [Pass] = [] + + fileprivate init() {} + + /// Appends a pass to the current pipeline. + public func add(_ type: Pass) { + self.passes.append(type) + } + } + + /// Initializes a new, empty pipeliner. + /// + /// - Parameter module: The module the pipeliner will run over. + public init(module: Module) { + self.module = module + self.stages = [] + self.stageMapping = [:] + } + + deinit { + for stage in stageMapping.values { + switch stage { + case let .functionPassManager(pm): + LLVMDisposePassManager(pm) + case let .modulePassManager(pm): + LLVMDisposePassManager(pm) + case .builtinPasses(_): + continue + } + } + } + + /// Appends a stage to the pipeliner. + /// + /// The staging function provides a `Builder` object into which the types + /// of passes for a given pipeline are inserted. + /// + /// - Parameters: + /// - name: The name of the pipeline stage. + /// - stager: A builder function. + public func addStage(_ name: String, _ stager: (Builder) -> Void) { + precondition(!self.frozen, "Cannot add new stages to a frozen pipeline!") + + self.frozen = true + defer { self.frozen = false } + + self.stages.append(name) + let builder = Builder() + stager(builder) + self.stageMapping[name] = .builtinPasses(builder.passes) + } + + /// Executes the entirety of the pass pipeline. + /// + /// Execution of passes is done in a loop that is divided into two phases. + /// The first phase aggregates all local passes and stops aggregation when + /// it encounters a module-level pass. This group of local passes + /// is then run one at a time on the same scope. The second phase is entered + /// and the module pass is run. The first phase is then re-entered until all + /// local passes have run on all local scopes and all intervening module + /// passes have been run. + /// + /// The same pipeline may be repeatedly re-executed, but pipeline execution + /// is not re-entrancy safe. + /// + /// - Parameter pipelineMask: Describes the subset of pipelines that should + /// be executed. If the mask is empty, all pipelines will be executed. + public func execute(mask pipelineMask: Set = []) { + precondition(!self.frozen, "Cannot execute a frozen pipeline!") + + self.frozen = true + defer { self.frozen = false } + + stageLoop: for stage in self.stages { + guard pipelineMask.isEmpty || pipelineMask.contains(stage) else { + continue + } + + guard let pipeline = self.stageMapping[stage] else { + fatalError("Unregistered pass stage?") + } + + switch pipeline { + case let .builtinPasses(passTypes): + guard !passTypes.isEmpty else { + continue stageLoop + } + self.runPasses(passTypes) + case let .functionPassManager(pm): + self.runFunctionPasses([], pm) + case let .modulePassManager(pm): + LLVMRunPassManager(pm, self.module.llvm) + } + } + } + + private func runFunctionPasses(_ passes: [Pass], _ pm: LLVMPassManagerRef) { + var keepalive = [Any]() + LLVMInitializeFunctionPassManager(pm) + + for pass in passes { + PassPipeliner.configurePass(pass, passManager: pm, keepalive: &keepalive) + } + + for function in self.module.functions { + LLVMRunFunctionPassManager(pm, function.asLLVM()) + } + } + + private func runPasses(_ passes: [Pass]) { + var keepalive = [Any]() + let pm = LLVMCreatePassManager()! + for pass in passes { + PassPipeliner.configurePass(pass, passManager: pm, keepalive: &keepalive) + } + LLVMRunPassManager(pm, self.module.llvm) + LLVMDisposePassManager(pm) + } +} + +// MARK: Standard Pass Pipelines + +extension PassPipeliner { + /// Adds a pipeline stage populated with function passes that LLVM considers + /// standard for languages like C and C++. Additional parameters are + /// available to tune the overall behavior of the optimization pipeline at a + /// macro level. + /// + /// - Parameters: + /// - name: The name of the pipeline stage. + /// - optimization: The level of optimization. + /// - size: The level of size optimization. + public func addStandardFunctionPipeline( + _ name: String, + optimization: CodeGenOptLevel = .`default`, + size: CodeGenOptLevel = .none + ) { + let passBuilder = self.configurePassBuilder(optimization, size) + let functionPasses = + LLVMCreateFunctionPassManagerForModule(self.module.llvm)! + LLVMPassManagerBuilderPopulateFunctionPassManager(passBuilder, + functionPasses) + LLVMPassManagerBuilderDispose(passBuilder) + self.stages.append(name) + self.stageMapping[name] = .functionPassManager(functionPasses) + } + + /// Adds a pipeline stage populated with module passes that LLVM considers + /// standard for languages like C and C++. Additional parameters are + /// available to tune the overall behavior of the optimization pipeline at a + /// macro level. + /// + /// - Parameters: + /// - name: The name of the pipeline stage. + /// - optimization: The level of optimization. + /// - size: The level of size optimization. + public func addStandardModulePipeline( + _ name: String, + optimization: CodeGenOptLevel = .`default`, + size: CodeGenOptLevel = .none + ) { + let passBuilder = self.configurePassBuilder(optimization, size) + let modulePasses = LLVMCreatePassManager()! + LLVMPassManagerBuilderPopulateModulePassManager(passBuilder, modulePasses) + LLVMPassManagerBuilderDispose(passBuilder) + self.stages.append(name) + self.stageMapping[name] = .modulePassManager(modulePasses) + } + + private func configurePassBuilder( + _ opt: CodeGenOptLevel, + _ size: CodeGenOptLevel + ) -> LLVMPassManagerBuilderRef { + let passBuilder = LLVMPassManagerBuilderCreate()! + switch opt { + case .none: + LLVMPassManagerBuilderSetOptLevel(passBuilder, 0) + case .less: + LLVMPassManagerBuilderSetOptLevel(passBuilder, 1) + case .default: + LLVMPassManagerBuilderSetOptLevel(passBuilder, 2) + case .aggressive: + LLVMPassManagerBuilderSetOptLevel(passBuilder, 3) + } + + switch size { + case .none: + LLVMPassManagerBuilderSetSizeLevel(passBuilder, 0) + case .less: + LLVMPassManagerBuilderSetSizeLevel(passBuilder, 1) + case .default: + LLVMPassManagerBuilderSetSizeLevel(passBuilder, 2) + case .aggressive: + LLVMPassManagerBuilderSetSizeLevel(passBuilder, 3) + } + + return passBuilder + } +} + + +extension PassPipeliner { + /// Configures and adds a pass to the given pass manager. + /// + /// - Parameters: + /// - pass: The pass to add to the pass manager. + /// - passManager: The pass manager to which to add a pass. + /// - keepalive: An array that must stay alive for as long as this pass + /// manager stays alive. This array will keep Swift objects + /// that may be passed into closures that will use them at + /// any point in the pass execution line. + static func configurePass( + _ pass: Pass, + passManager: LLVMPassManagerRef, + keepalive: inout [Any]) { + switch pass { + case .invalid(let reason): + fatalError("Cannot configure pass: \(reason)") + case .aggressiveDCE: + LLVMAddAggressiveDCEPass(passManager) + case .bitTrackingDCE: + LLVMAddBitTrackingDCEPass(passManager) + case .alignmentFromAssumptions: + LLVMAddAlignmentFromAssumptionsPass(passManager) + case .cfgSimplification: + LLVMAddCFGSimplificationPass(passManager) + case .deadStoreElimination: + LLVMAddDeadStoreEliminationPass(passManager) + case .scalarizer: + LLVMAddScalarizerPass(passManager) + case .mergedLoadStoreMotion: + LLVMAddMergedLoadStoreMotionPass(passManager) + case .gvn: + LLVMAddGVNPass(passManager) + case .indVarSimplify: + LLVMAddIndVarSimplifyPass(passManager) + case .instructionCombining: + LLVMAddInstructionCombiningPass(passManager) + case .jumpThreading: + LLVMAddJumpThreadingPass(passManager) + case .licm: + LLVMAddLICMPass(passManager) + case .loopDeletion: + LLVMAddLoopDeletionPass(passManager) + case .loopIdiom: + LLVMAddLoopIdiomPass(passManager) + case .loopRotate: + LLVMAddLoopRotatePass(passManager) + case .loopReroll: + LLVMAddLoopRerollPass(passManager) + case .loopUnroll: + LLVMAddLoopUnrollPass(passManager) + case .loopUnrollAndJam: + LLVMAddLoopUnrollAndJamPass(passManager) + case .loopUnswitch: + LLVMAddLoopUnswitchPass(passManager) + case .lowerAtomic: + LLVMAddLowerAtomicPass(passManager) + case .memCpyOpt: + LLVMAddMemCpyOptPass(passManager) + case .partiallyInlineLibCalls: + LLVMAddPartiallyInlineLibCallsPass(passManager) + case .lowerSwitch: + LLVMAddLowerSwitchPass(passManager) + case .promoteMemoryToRegister: + LLVMAddPromoteMemoryToRegisterPass(passManager) + case .addDiscriminators: + LLVMAddAddDiscriminatorsPass(passManager) + case .reassociate: + LLVMAddReassociatePass(passManager) + case .sccp: + LLVMAddSCCPPass(passManager) + case .tailCallElimination: + LLVMAddTailCallEliminationPass(passManager) + case .constantPropagation: + LLVMAddConstantPropagationPass(passManager) + case .demoteMemoryToRegister: + LLVMAddDemoteMemoryToRegisterPass(passManager) + case .verifier: + LLVMAddVerifierPass(passManager) + case .correlatedValuePropagation: + LLVMAddCorrelatedValuePropagationPass(passManager) + case .earlyCSE: + LLVMAddEarlyCSEPass(passManager) + case .lowerExpectIntrinsic: + LLVMAddLowerExpectIntrinsicPass(passManager) + case .typeBasedAliasAnalysis: + LLVMAddTypeBasedAliasAnalysisPass(passManager) + case .scopedNoAliasAA: + LLVMAddScopedNoAliasAAPass(passManager) + case .basicAliasAnalysis: + LLVMAddBasicAliasAnalysisPass(passManager) + case .unifyFunctionExitNodes: + LLVMAddUnifyFunctionExitNodesPass(passManager) + case .alwaysInliner: + LLVMAddAlwaysInlinerPass(passManager) + case .argumentPromotion: + LLVMAddArgumentPromotionPass(passManager) + case .constantMerge: + LLVMAddConstantMergePass(passManager) + case .deadArgElimination: + LLVMAddDeadArgEliminationPass(passManager) + case .functionAttrs: + LLVMAddFunctionAttrsPass(passManager) + case .functionInlining: + LLVMAddFunctionInliningPass(passManager) + case .globalDCE: + LLVMAddGlobalDCEPass(passManager) + case .globalOptimizer: + LLVMAddGlobalOptimizerPass(passManager) + case .ipConstantPropagation: + LLVMAddIPConstantPropagationPass(passManager) + case .ipscc: + LLVMAddIPSCCPPass(passManager) + case .pruneEH: + LLVMAddPruneEHPass(passManager) + case .stripDeadPrototypes: + LLVMAddStripDeadPrototypesPass(passManager) + case .stripSymbols: + LLVMAddStripSymbolsPass(passManager) + case .loopVectorize: + LLVMAddLoopVectorizePass(passManager) + case .slpVectorize: + LLVMAddSLPVectorizePass(passManager) + case .internalizeAll(let preserveMain): + LLVMAddInternalizePass(passManager, preserveMain == false ? 0 : 1) + case .internalize(let pred): + // The lifetime of this callback is must be manually managed to ensure + // it remains alive across the execution of the given pass manager. + + // Create a callback context at +1 + let callbackContext = InternalizeCallbackContext(pred) + // Stick it in the keepalive array, now at +2 + keepalive.append(callbackContext) + // Pass it unmanaged at +2 + let contextPtr = Unmanaged.passUnretained(callbackContext).toOpaque() + LLVMAddInternalizePassWithMustPreservePredicate(passManager, contextPtr) { globalValue, callbackCtx in + guard let globalValue = globalValue, let callbackCtx = callbackCtx else { + fatalError("Global value and context must be non-nil") + } + + let callback = Unmanaged.fromOpaque(callbackCtx).takeUnretainedValue() + return callback.block(realizeGlobalValue(globalValue)).llvm + } + // Context dropped, now at +1 + // When the keepalive array is dropped by the caller, it will drop to +0. + case .scalarReplacementOfAggregates: + LLVMAddScalarReplAggregatesPassWithThreshold(passManager, /*ignored*/ 0) + } + } +} + +private func realizeGlobalValue(_ llvm: LLVMValueRef) -> IRGlobal { + precondition(llvm.isAGlobalValue, "must be a global value") + if llvm.isAFunction { + return Function(llvm: llvm) + } else if llvm.isAGlobalAlias { + return Alias(llvm: llvm) + } else if llvm.isAGlobalVariable { + return Global(llvm: llvm) + } else { + fatalError("unrecognized global value") + } +} + +private class InternalizeCallbackContext { + fileprivate let block: (IRGlobal) -> Bool + + fileprivate init(_ block: @escaping (IRGlobal) -> Bool) { + self.block = block + } +} diff --git a/Sources/llvmshims/include/shim.h b/Sources/llvmshims/include/shim.h index ecd9b6c3..8ba8522b 100644 --- a/Sources/llvmshims/include/shim.h +++ b/Sources/llvmshims/include/shim.h @@ -96,4 +96,10 @@ uint64_t LLVMGlobalGetGUID(LLVMValueRef Global); void LLVMAppendExistingBasicBlock(LLVMValueRef Fn, LLVMBasicBlockRef BB); +void LLVMAddAddDiscriminatorsPass(LLVMPassManagerRef PM); + +void LLVMAddInternalizePassWithMustPreservePredicate( + LLVMPassManagerRef PM, void *Context, + LLVMBool (*MustPreserve)(LLVMValueRef, void *)); + #endif /* LLVMSWIFT_LLVM_SHIM_H */ diff --git a/Sources/llvmshims/src/shim.cpp b/Sources/llvmshims/src/shim.cpp index 85622255..6716d576 100644 --- a/Sources/llvmshims/src/shim.cpp +++ b/Sources/llvmshims/src/shim.cpp @@ -5,10 +5,13 @@ #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LegacyPassManager.h" #include "llvm/Support/ARMTargetParser.h" #include "llvm/Object/MachOUniversal.h" #include "llvm/Object/ObjectFile.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/Transforms/Utils.h" +#include "llvm/Transforms/IPO.h" extern "C" { typedef struct LLVMOpaqueBinary *LLVMBinaryRef; @@ -120,6 +123,14 @@ extern "C" { // https://reviews.llvm.org/D59658 void LLVMAppendExistingBasicBlock(LLVMValueRef Fn, LLVMBasicBlockRef BB); + + // https://reviews.llvm.org/D58624 + void LLVMAddAddDiscriminatorsPass(LLVMPassManagerRef PM); + + // https://reviews.llvm.org/D62456 + void LLVMAddInternalizePassWithMustPreservePredicate( + LLVMPassManagerRef PM, void *Context, + LLVMBool (*MustPreserve)(LLVMValueRef, void *)); } using namespace llvm; @@ -354,3 +365,15 @@ void LLVMAppendExistingBasicBlock(LLVMValueRef Fn, LLVMBasicBlockRef BB) { unwrap(Fn)->getBasicBlockList().push_back(unwrap(BB)); } + +void LLVMAddAddDiscriminatorsPass(LLVMPassManagerRef PM) { + unwrap(PM)->add(createAddDiscriminatorsPass()); +} + +void LLVMAddInternalizePassWithMustPreservePredicate( + LLVMPassManagerRef PM, void *Context, + LLVMBool (*Pred)(LLVMValueRef, void *)) { + unwrap(PM)->add(createInternalizePass([=](const GlobalValue &GV) { + return Pred(wrap(&GV), Context) == 0 ? false : true; + })); +} diff --git a/Tests/LLVMTests/IRPassManagerSpec.swift b/Tests/LLVMTests/IRPassManagerSpec.swift new file mode 100644 index 00000000..71984402 --- /dev/null +++ b/Tests/LLVMTests/IRPassManagerSpec.swift @@ -0,0 +1,383 @@ +import LLVM +import XCTest +import FileCheck +import Foundation + +class IRPassManagerSpec : XCTestCase { + func testEmptyPassPipeliner() { + let module = Module(name: "Test") + let pipeliner = PassPipeliner(module: module) + XCTAssertTrue(pipeliner.stages.isEmpty) + } + + func testAppendStages() { + let module = Module(name: "Test") + let pipeliner = PassPipeliner(module: module) + XCTAssertTrue(pipeliner.stages.isEmpty) + var rng = SystemRandomNumberGenerator() + let stageCount = (rng.next() % 25) + for i in 0.. Void = { (name) in + var fun = module.addFunction(name, type: FunctionType([], VoidType())) + fun.linkage = .external + let block = fun.appendBasicBlock(named: "entry") + builder.positionAtEnd(of: block) + builder.buildRetVoid() + } + for name in [ "a", "b", "c", "d", "e", "f", "g" ] { + addFunction(name) + } + let pipeliner = PassPipeliner(module: module) + pipeliner.addStage("Internalize") { builder in + builder.add(.internalize { g in + print("Internalizing: \(g.name)") + return true + }) + } + + XCTAssertTrue(fileCheckOutput(of: .stdout, withPrefixes: [ "CHECK-IDEMPOTENT-INTERNALIZE" ]) { + for function in module.functions { + XCTAssertTrue(function.linkage == .external) + } + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: a + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: b + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: c + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: d + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: e + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: f + // CHECK-IDEMPOTENT-INTERNALIZE: Internalizing: g + pipeliner.execute() + for function in module.functions { + XCTAssertTrue(function.linkage == .external) + } + }) + } + + func testInternalizeCallback() { + let module = Module(name: "Internalize") + let builder = IRBuilder(module: module) + let addFunction: (String) -> Void = { (name) in + var fun = module.addFunction(name, type: FunctionType([], VoidType())) + fun.linkage = .external + let block = fun.appendBasicBlock(named: "entry") + builder.positionAtEnd(of: block) + builder.buildRetVoid() + } + for name in [ "a", "b", "c", "d", "e", "f", "g" ] { + addFunction(name) + } + let pipeliner = PassPipeliner(module: module) + pipeliner.addStage("Internalize") { builder in + builder.add(.internalize { g in + print("Internalizing: \(g.name)") + return false + }) + } + + XCTAssertTrue(fileCheckOutput(of: .stdout, withPrefixes: [ "CHECK-INTERNALIZE" ]) { + for function in module.functions { + XCTAssertTrue(function.linkage == .external) + } + // CHECK-INTERNALIZE: Internalizing: a + // CHECK-INTERNALIZE: Internalizing: b + // CHECK-INTERNALIZE: Internalizing: c + // CHECK-INTERNALIZE: Internalizing: d + // CHECK-INTERNALIZE: Internalizing: e + // CHECK-INTERNALIZE: Internalizing: f + // CHECK-INTERNALIZE: Internalizing: g + pipeliner.execute() + for function in module.functions { + XCTAssertTrue(function.linkage == .internal) + } + }) + } + + private func createModule() -> Module { + let module = Module(name: "Test") + + let builder = IRBuilder(module: module) + let fun = builder.addFunction("fun", + type: FunctionType([ + IntType.int32, + IntType.int32, + ], IntType.int32)) + let entry = fun.appendBasicBlock(named: "entry") + let block1 = fun.appendBasicBlock(named: "block1") + let block2 = fun.appendBasicBlock(named: "block2") + let merge = fun.appendBasicBlock(named: "merge") + + builder.positionAtEnd(of: entry) + let val1 = builder.buildAlloca(type: IntType.int32, alignment: Alignment(4)) + let val2 = builder.buildAlloca(type: IntType.int32, alignment: Alignment(4)) + let val3 = builder.buildAlloca(type: IntType.int32, alignment: Alignment(4)) + builder.buildStore(fun.parameters[0], to: val1) + builder.buildStore(fun.parameters[1], to: val2) + builder.buildStore(IntType.int32.constant(4), to: val3) + let reloadVal1 = builder.buildLoad(val1, alignment: Alignment(4)) + let cmpVal = builder.buildICmp(reloadVal1, IntType.int32.constant(1), .equal) + builder.buildCondBr(condition: cmpVal, then: block1, else: block2) + + builder.positionAtEnd(of: block1) + let reloadVal2 = builder.buildLoad(val1, alignment: Alignment(4)) + let reloadVal3 = builder.buildLoad(val3, alignment: Alignment(4)) + let sum1 = builder.buildAdd(reloadVal2, reloadVal3, overflowBehavior: .noSignedWrap) + builder.buildStore(sum1, to: val3) + builder.buildBr(merge) + + builder.positionAtEnd(of: block2) + let reloadVal4 = builder.buildLoad(val2, alignment: Alignment(4)) + let reloadVal5 = builder.buildLoad(val3, alignment: Alignment(4)) + let sum2 = builder.buildAdd(reloadVal4, reloadVal5, overflowBehavior: .noSignedWrap) + builder.buildStore(sum2, to: val3) + builder.buildBr(merge) + + builder.positionAtEnd(of: merge) + let reloadVal6 = builder.buildLoad(val3, alignment: Alignment(4)) + builder.buildRet(reloadVal6) + + return module + } + + #if !os(macOS) + static var allTests = testCase([ + ("testEmptyPassPipeliner", testEmptyPassPipeliner), + ("testAppendStages", testAppendStages), + ("testAppendStandardStages", testAppendStandardStages), + ("testExecute", testExecute), + ("testExecuteWithMask", testExecuteWithMask), + ("testIdempotentInternalize", testIdempotentInternalize), + ("testInternalizeCallback", testInternalizeCallback), + ]) + #endif +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 566a4c21..22563cda 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -17,6 +17,7 @@ XCTMain([ IRInstructionSpec.allTests, IRMetadataSpec.allTests, IROperationSpec.allTests, + IRPassManagerSpec.allTests, JITSpec.allTests, ModuleLinkSpec.allTests, ModuleMetadataSpec.allTests,