// Part of the Concrete Compiler Project, under the BSD3 License with Zama // Exceptions. See // https://github.com/zama-ai/concrete/blob/main/LICENSE.txt // for license information. #include "concretelang/Support/V0Parameters.h" #include "mlir/Dialect/Arith/Transforms/BufferizableOpInterfaceImpl.h" #include "mlir/Dialect/Bufferization/IR/Bufferization.h" #include "mlir/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.h" #include "mlir/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.h" #include "mlir/Dialect/Tensor/Transforms/BufferizableOpInterfaceImpl.h" #include "llvm/Support/Debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mlir/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/Linalg/IR/Linalg.h" #include "mlir/Dialect/MemRef/IR/MemRef.h" #include "mlir/Dialect/OpenMP/OpenMPDialect.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/ExecutionEngine/OptUtils.h" #include "mlir/Parser/Parser.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/SMLoc.h" #include "concrete-protocol.capnp.h" #include "concretelang/Conversion/Utils/GlobalFHEContext.h" #include "concretelang/Dialect/Concrete/IR/ConcreteDialect.h" #include "concretelang/Dialect/Concrete/Transforms/BufferizableOpInterfaceImpl.h" #include "concretelang/Dialect/FHE/IR/FHEDialect.h" #include "concretelang/Dialect/FHELinalg/IR/FHELinalgDialect.h" #include "concretelang/Dialect/Optimizer/IR/OptimizerDialect.h" #include "concretelang/Dialect/RT/IR/RTDialect.h" #include "concretelang/Dialect/RT/Transforms/BufferizableOpInterfaceImpl.h" #include "concretelang/Dialect/SDFG/IR/SDFGDialect.h" #include "concretelang/Dialect/SDFG/Transforms/BufferizableOpInterfaceImpl.h" #include "concretelang/Dialect/SDFG/Transforms/SDFGConvertibleOpInterfaceImpl.h" #include "concretelang/Dialect/TFHE/IR/TFHEDialect.h" #include "concretelang/Dialect/Tracing/IR/TracingDialect.h" #include "concretelang/Dialect/Tracing/Transforms/BufferizableOpInterfaceImpl.h" #include "concretelang/Dialect/TypeInference/IR/TypeInferenceDialect.h" #include "concretelang/Runtime/DFRuntime.hpp" #include "concretelang/Support/CompilerEngine.h" #include "concretelang/Support/Encodings.h" #include "concretelang/Support/Error.h" #include "concretelang/Support/LLVMEmitFile.h" #include "concretelang/Support/Pipeline.h" #include "concretelang/Support/Utils.h" namespace mlir { namespace concretelang { // TODO: should be removed when bufferization is not related to CAPI lowering // Control whether we should call a cpu of gpu function when lowering // to CAPI static bool EMIT_GPU_OPS; bool getEmitGPUOption() { return EMIT_GPU_OPS; } /// Creates a new compilation context that can be shared across /// compilation engines and results std::shared_ptr CompilationContext::createShared() { return std::make_shared(); } CompilationContext::CompilationContext() : mlirContext(nullptr), llvmContext(nullptr) {} CompilationContext::~CompilationContext() { delete this->mlirContext; delete this->llvmContext; } /// Returns the MLIR context for a compilation context. Creates and /// initializes a new MLIR context if necessary. mlir::MLIRContext *CompilationContext::getMLIRContext() { if (this->mlirContext == nullptr) { mlir::DialectRegistry registry; registry.insert< mlir::concretelang::TypeInference::TypeInferenceDialect, mlir::concretelang::Tracing::TracingDialect, mlir::concretelang::Optimizer::OptimizerDialect, mlir::concretelang::RT::RTDialect, mlir::concretelang::FHE::FHEDialect, mlir::concretelang::TFHE::TFHEDialect, mlir::concretelang::FHELinalg::FHELinalgDialect, mlir::concretelang::Concrete::ConcreteDialect, mlir::concretelang::SDFG::SDFGDialect, mlir::func::FuncDialect, mlir::memref::MemRefDialect, mlir::linalg::LinalgDialect, mlir::LLVM::LLVMDialect, mlir::scf::SCFDialect, mlir::omp::OpenMPDialect, mlir::bufferization::BufferizationDialect>(); Tracing::registerBufferizableOpInterfaceExternalModels(registry); Concrete::registerBufferizableOpInterfaceExternalModels(registry); SDFG::registerSDFGConvertibleOpInterfaceExternalModels(registry); SDFG::registerBufferizableOpInterfaceExternalModels(registry); arith::registerBufferizableOpInterfaceExternalModels(registry); bufferization::func_ext::registerBufferizableOpInterfaceExternalModels( registry); scf::registerBufferizableOpInterfaceExternalModels(registry); tensor::registerBufferizableOpInterfaceExternalModels(registry); linalg::registerBufferizableOpInterfaceExternalModels(registry); RT::registerBufferizableOpInterfaceExternalModels(registry); mlir::linalg::registerTilingInterfaceExternalModels(registry); this->mlirContext = new mlir::MLIRContext(); this->mlirContext->appendDialectRegistry(registry); this->mlirContext->loadAllAvailableDialects(); this->mlirContext->disableMultithreading(); } return this->mlirContext; } /// Returns the LLVM context for a compilation context. Creates and /// initializes a new LLVM context if necessary. llvm::LLVMContext *CompilationContext::getLLVMContext() { if (this->llvmContext == nullptr) this->llvmContext = new llvm::LLVMContext(); return this->llvmContext; } /// Sets the FHE constraints for the compilation. Overrides any /// automatically detected configuration and prevents the autodetection /// pass from running. void CompilerEngine::setFHEConstraints( const mlir::concretelang::V0FHEConstraint &c) { this->overrideMaxEintPrecision = c.p; this->overrideMaxMANP = c.norm2; } void CompilerEngine::setGenerateProgramInfo(bool v) { this->generateProgramInfo = v; } void CompilerEngine::setMaxEintPrecision(size_t v) { this->overrideMaxEintPrecision = v; } void CompilerEngine::setMaxMANP(size_t v) { this->overrideMaxMANP = v; } void CompilerEngine::setEnablePass( std::function enablePass) { this->enablePass = enablePass; } /// Returns the optimizer::Description llvm::Expected> CompilerEngine::getConcreteOptimizerDescription(CompilationResult &res) { mlir::MLIRContext &mlirContext = *this->compilationContext->getMLIRContext(); mlir::ModuleOp module = res.mlirModuleRef->get(); // If the values has been overwritten returns if (this->overrideMaxEintPrecision.has_value() && this->overrideMaxMANP.has_value()) { auto constraint = mlir::concretelang::V0FHEConstraint{ this->overrideMaxMANP.value(), this->overrideMaxEintPrecision.value()}; return optimizer::Description{constraint, std::nullopt}; } auto config = this->compilerOptions.optimizerConfig; auto descriptions = mlir::concretelang::pipeline::getFHEContextFromFHE( mlirContext, module, config, enablePass); if (auto err = descriptions.takeError()) { return std::move(err); } if (descriptions->empty()) { // The pass has not been run return std::nullopt; } if (descriptions->size() > 1 && config.strategy != mlir::concretelang::optimizer::V0) { // Multi circuits without V0 return StreamStringError( "Multi-circuits is only supported for V0 optimization."); } if (descriptions->size() > 1) { auto iter = descriptions->begin(); auto desc = std::move(iter->second); if (!desc.has_value()) { return StreamStringError("Expected description."); } if (!desc.value().dag.has_value()) { return StreamStringError("Expected dag in description."); } iter++; while (iter != descriptions->end()) { if (!iter->second.has_value()) { return StreamStringError("Expected description."); } if (!iter->second.value().dag.has_value()) { return StreamStringError("Expected dag in description."); } desc->dag.value()->concat(*iter->second.value().dag.value()); iter++; } return std::move(desc); } return std::move(descriptions->begin()->second); } /// set the fheContext field if the v0Constraint can be computed /// set the fheContext field if the v0Constraint can be computed llvm::Error CompilerEngine::determineFHEParameters(CompilationResult &res) { if (compilerOptions.v0Parameter.has_value()) { // parameters come from the compiler options auto v0Params = compilerOptions.v0Parameter.value(); if (compilerOptions.largeIntegerParameter.has_value()) { v0Params.largeInteger = compilerOptions.largeIntegerParameter; } V0FHEConstraint constraint; if (compilerOptions.v0FHEConstraints.has_value()) { constraint = compilerOptions.v0FHEConstraints.value(); } res.fheContext.emplace( mlir::concretelang::V0FHEContext{constraint, v0Params}); ProgramCompilationFeedback feedback; res.feedback.emplace(feedback); return llvm::Error::success(); } // compute parameters else { auto descr = getConcreteOptimizerDescription(res); if (auto err = descr.takeError()) { return err; } if (!descr.get().has_value()) { return llvm::Error::success(); } ProgramCompilationFeedback feedback; // Make sure to use the gpu constraint of the optimizer if we use gpu // backend. compilerOptions.optimizerConfig.use_gpu_constraints = compilerOptions.emitGPUOps; auto expectedSolution = getSolution(descr.get().value(), feedback, compilerOptions.optimizerConfig); if (auto err = expectedSolution.takeError()) { return err; } res.fheContext.emplace(mlir::concretelang::V0FHEContext{ descr.get().value().constraint, *expectedSolution}); res.feedback.emplace(feedback); } return llvm::Error::success(); } mlir::LogicalResult CompilerEngine::materializeOptimizerPartitionFrontiers(CompilationResult &res) { mlir::ModuleOp module = res.mlirModuleRef->get(); if (res.fheContext.has_value()) { return pipeline::materializeOptimizerPartitionFrontiers( *module.getContext(), module, res.fheContext, enablePass); } return mlir::success(); } using OptionalLib = std::optional>; // Compile the sources managed by the source manager `sm` to the // target dialect `target`. If successful, the result can be retrieved // using `getModule()` and `getLLVMModule()`, respectively depending // on the target dialect. llvm::Expected CompilerEngine::compile(llvm::SourceMgr &sm, Target target, OptionalLib lib) { std::unique_ptr smHandler; std::string diagnosticsMsg; llvm::raw_string_ostream diagnosticsOS(diagnosticsMsg); auto errorDiag = [&](std::string prefixMsg) -> llvm::Expected { return StreamStringError(prefixMsg + "\n" + diagnosticsOS.str()); }; CompilationResult res(this->compilationContext); CompilationOptions &options = this->compilerOptions; mlir::MLIRContext &mlirContext = *this->compilationContext->getMLIRContext(); if (options.verifyDiagnostics) { // Only build diagnostics verifier handler if diagnostics should // be verified in order to avoid diagnostic messages to be // consumed when they should appear on stderr. smHandler = std::make_unique( sm, &mlirContext, diagnosticsOS); } mlirContext.printOpOnDiagnostic(false); mlir::OwningOpRef mlirModuleRef = mlir::parseSourceFile(sm, &mlirContext); if (options.verifyDiagnostics) { if (smHandler->verify().failed()) return StreamStringError("Verification of diagnostics failed"); else return std::move(res); } if (!mlirModuleRef) { return errorDiag("Could not parse source"); } return compile(mlirModuleRef.release(), target, lib); } llvm::Expected CompilerEngine::compile(mlir::ModuleOp moduleOp, Target target, OptionalLib lib) { CompilationResult res(this->compilationContext); CompilationOptions &options = this->compilerOptions; mlir::MLIRContext &mlirContext = *this->compilationContext->getMLIRContext(); // enable/disable usage of gpu functions during bufferization EMIT_GPU_OPS = options.emitGPUOps; auto dataflowParallelize = options.autoParallelize || options.dataflowParallelize; if (options.optimizerConfig.strategy == optimizer::Strategy::DAG_MULTI && dataflowParallelize == true) { // FIXME: DF is not currently compatible with multi-parameters as // the generation of dataflow tasks obfuscates the code before the // analysis can be done. Until this is fixed we cannot allow both. dataflowParallelize = false; warnx("WARNING: dataflow parallelization is not compatible with the " "optimizer strategy [dag-multi]. Continuing with dataflow " "parallelization disabled."); } auto loopParallelize = options.autoParallelize || options.loopParallelize; if (loopParallelize) mlir::concretelang::dfr::_dfr_set_use_omp(true); if (dataflowParallelize) mlir::concretelang::dfr::_dfr_set_required(true); mlir::OwningOpRef mlirModuleRef(moduleOp); res.mlirModuleRef = std::move(mlirModuleRef); mlir::ModuleOp module = res.mlirModuleRef->get(); if (target == Target::ROUND_TRIP) return std::move(res); // Retrieves the encoding informations before any transformation is performed // on the `FHE` dialect. if ((this->generateProgramInfo || target == Target::LIBRARY) && !options.encodings) { auto encodingInfosOrErr = mlir::concretelang::encodings::getProgramEncoding(module); if (!encodingInfosOrErr) { return encodingInfosOrErr.takeError(); } options.encodings = std::move(*encodingInfosOrErr); } if (mlir::concretelang::pipeline::transformFHEBoolean(mlirContext, module, enablePass) .failed()) { return StreamStringError("Transforming FHE boolean ops failed"); } if (options.chunkIntegers) { if (mlir::concretelang::pipeline::transformFHEBigInt( mlirContext, module, enablePass, options.chunkSize, options.chunkWidth) .failed()) { return StreamStringError("Transforming FHE big integer ops failed"); } } // FHE High level pass to determine FHE parameters if (auto err = this->determineFHEParameters(res)) return std::move(err); if (this->materializeOptimizerPartitionFrontiers(res).failed()) { return StreamStringError( "Could not materialize explicit optimizer partition frontiers"); } // Now that FHE Parameters were computed, we can set the encoding mode of // integer ciphered inputs. if ((this->generateProgramInfo || target == Target::LIBRARY)) { std::optional< Message> maybeChunkInfo(std::nullopt); if (options.chunkIntegers) { auto chunkedMode = Message< concreteprotocol::IntegerCiphertextEncodingInfo::ChunkedMode>(); chunkedMode.asBuilder().setSize(options.chunkSize); chunkedMode.asBuilder().setWidth(options.chunkWidth); maybeChunkInfo = chunkedMode; } mlir::concretelang::encodings::setProgramEncodingModes( *options.encodings, maybeChunkInfo, res.fheContext); } // FHELinalg tiling if (options.fhelinalgTileSizes) { if (mlir::concretelang::pipeline::markFHELinalgForTiling( mlirContext, module, *options.fhelinalgTileSizes, enablePass) .failed()) return StreamStringError( "Marking of FHELinalg operations for tiling failed"); } if (target == Target::FHE) return std::move(res); // FHELinalg -> FHE if (mlir::concretelang::pipeline::lowerFHELinalgToLinalg(mlirContext, module, enablePass) .failed()) { return StreamStringError("Lowering from FHELinalg to Linalg failed"); } if (target == Target::FHE_LINALG_GENERIC) return std::move(res); if (mlir::concretelang::pipeline::tileMarkedLinalg(mlirContext, module, enablePass) .failed()) { return StreamStringError("Tiling of Linalg operations failed"); } if (mlir::concretelang::pipeline::transformHighLevelFHEOps(mlirContext, module, enablePass) .failed()) { return StreamStringError("Rewriting of high level fhe ops failed"); } // TODO: bring determineFHEParameters call here after the FHELinalg -> FHE // lowering // require to first support linalg.genric in the Optimizer Dag creation // FHE High level pass to determine FHE parameters // if (auto err = this->determineFHEParameters(res)) // return std::move(err); if (target == Target::FHE_NO_LINALG) return std::move(res); // Dataflow parallelization if (dataflowParallelize && mlir::concretelang::pipeline::autopar(mlirContext, module, enablePass) .failed()) { return StreamStringError("Dataflow parallelization failed"); } if (target == Target::FHE_DF_PARALLELIZED) return std::move(res); if (mlir::concretelang::pipeline::lowerLinalgToLoops( mlirContext, module, enablePass, loopParallelize) .failed()) { return StreamStringError("Lowering from Linalg Generic to Loops failed"); } // FHE -> TFHE if (mlir::concretelang::pipeline::lowerFHEToTFHE(mlirContext, module, res.fheContext, enablePass) .failed()) { return StreamStringError("Lowering from FHE to TFHE failed"); } // Optimizing TFHE if (this->compilerOptions.optimizeTFHE && mlir::concretelang::pipeline::optimizeTFHE(mlirContext, module, this->enablePass) .failed()) { return StreamStringError("Optimizing TFHE failed"); } if (target == Target::TFHE) return std::move(res); if (mlir::concretelang::pipeline::parametrizeTFHE(mlirContext, module, res.fheContext, enablePass) .failed()) { return StreamStringError("Parametrization of TFHE operations failed"); } if (target == Target::PARAMETRIZED_TFHE) return std::move(res); // Normalize TFHE keys if (mlir::concretelang::pipeline::normalizeTFHEKeys(mlirContext, module, this->enablePass) .failed()) { return StreamStringError("Normalizing TFHE keys failed"); } // Generate client parameters if requested if (this->generateProgramInfo) { if (!res.fheContext.has_value()) { return StreamStringError( "Cannot generate client parameters, the fhe context is empty"); } } // Generate program info if requested if (this->generateProgramInfo || target == Target::LIBRARY) { if (!res.fheContext.has_value()) { // Some tests involve call a to non encrypted functions auto programInfo = Message(); programInfo.asBuilder().initCircuits(1); programInfo.asBuilder().getCircuits()[0].setName(std::string("main")); res.programInfo = programInfo; } else { auto programInfoOrErr = mlir::concretelang::createProgramInfoFromTfheDialect( module, options.optimizerConfig.security, options.encodings.value(), options.compressEvaluationKeys, options.compressInputCiphertexts); if (!programInfoOrErr) return programInfoOrErr.takeError(); res.programInfo = std::move(*programInfoOrErr); res.feedback->fillFromProgramInfo(*res.programInfo); } } if (target == Target::NORMALIZED_TFHE) return std::move(res); if (res.feedback) { if (mlir::concretelang::pipeline::extractTFHEStatistics( mlirContext, module, this->enablePass, res.feedback.value()) .failed()) { return StreamStringError("Extracting TFHE statistics failed"); } } if (options.simulate) { if (mlir::concretelang::pipeline::simulateTFHE(mlirContext, module, this->enablePass) .failed()) { return StreamStringError("Simulating TFHE failed"); } } if (target == Target::SIMULATED_TFHE) return std::move(res); if (options.batchTFHEOps) { if (mlir::concretelang::pipeline::batchTFHE(mlirContext, module, enablePass, options.maxBatchSize) .failed()) { return StreamStringError("Batching of TFHE operations"); } } if (target == Target::BATCHED_TFHE) return std::move(res); // TFHE -> Concrete if (mlir::concretelang::pipeline::lowerTFHEToConcrete(mlirContext, module, this->enablePass) .failed()) { return StreamStringError("Lowering from TFHE to Concrete failed"); } if (target == Target::CONCRETE) return std::move(res); // Extract SDFG data flow graph from Concrete representation if (options.emitSDFGOps) { if (mlir::concretelang::pipeline::extractSDFGOps( mlirContext, module, enablePass, options.unrollLoopsWithSDFGConvertibleOps) .failed()) { return StreamStringError("Extraction of SDFG operations from Concrete " "representation failed"); } } if (target == Target::SDFG) { return std::move(res); } // Add runtime context in Concrete if (mlir::concretelang::pipeline::addRuntimeContext(mlirContext, module, enablePass) .failed()) { return StreamStringError("Adding Runtime Context failed"); } // SDFG -> Canonical dialects if (mlir::concretelang::pipeline::lowerSDFGToStd(mlirContext, module, enablePass) .failed()) { return StreamStringError( "Lowering from SDFG to canonical MLIR dialects failed"); } // bufferize and related passes if (mlir::concretelang::pipeline::lowerToStd(mlirContext, module, enablePass, loopParallelize) .failed()) { return StreamStringError("Failed to lower to std"); } if (target == Target::STD) return std::move(res); if (res.feedback) { if (mlir::concretelang::pipeline::computeMemoryUsage( mlirContext, module, this->enablePass, res.feedback.value()) .failed()) { return StreamStringError("Computing memory usage failed"); } } if (mlir::concretelang::pipeline::lowerToCAPI(mlirContext, module, enablePass, options.emitGPUOps) .failed()) { return StreamStringError("Failed to lower to CAPI"); } // MLIR canonical dialects -> LLVM Dialect if (mlir::concretelang::pipeline::lowerStdToLLVMDialect(mlirContext, module, enablePass) .failed()) { return StreamStringError("Failed to lower to LLVM dialect"); } if (target == Target::LLVM) return std::move(res); // Lowering to actual LLVM IR (i.e., not the LLVM dialect) llvm::LLVMContext &llvmContext = *this->compilationContext->getLLVMContext(); res.llvmModule = mlir::concretelang::pipeline::lowerLLVMDialectToLLVMIR( mlirContext, llvmContext, module); if (!res.llvmModule) return StreamStringError("Failed to convert from LLVM dialect to LLVM IR"); if (target == Target::LLVM_IR) return std::move(res); if (mlir::concretelang::pipeline::optimizeLLVMModule(llvmContext, *res.llvmModule) .failed()) { return StreamStringError("Failed to optimize LLVM IR"); } if (target == Target::OPTIMIZED_LLVM_IR) return std::move(res); if (target == Target::LIBRARY) { if (!lib) { return StreamStringError( "Internal Error: Please provide a library parameter"); } auto objPath = lib.value()->setCompilationResult(res); if (!objPath) { return StreamStringError(llvm::toString(objPath.takeError())); } return std::move(res); } return std::move(res); } /// Compile the source `s` to the target dialect `target`. If successful, the /// result can be retrieved using `getModule()` and `getLLVMModule()`, /// respectively depending on the target dialect. llvm::Expected CompilerEngine::compile(llvm::StringRef s, Target target, OptionalLib lib) { std::unique_ptr mb = llvm::MemoryBuffer::getMemBuffer(s); return this->compile(std::move(mb), target, lib); } /// Compile the contained in `buffer` to the target dialect /// `target`. If successful, the result can be retrieved using /// `getModule()` and `getLLVMModule()`, respectively depending on the /// target dialect. llvm::Expected CompilerEngine::compile(std::unique_ptr buffer, Target target, OptionalLib lib) { llvm::SourceMgr sm; sm.AddNewSourceBuffer(std::move(buffer), llvm::SMLoc()); return this->compile(sm, target, lib); } llvm::Expected CompilerEngine::compile(std::vector inputs, std::string outputDirPath, std::string runtimeLibraryPath, bool generateSharedLib, bool generateStaticLib, bool generateClientParameters, bool generateCompilationFeedback) { using Library = mlir::concretelang::CompilerEngine::Library; auto outputLib = std::make_shared(outputDirPath, runtimeLibraryPath); auto target = CompilerEngine::Target::LIBRARY; for (auto input : inputs) { auto compilation = compile(input, target, outputLib); if (!compilation) { return compilation.takeError(); } } if (auto err = outputLib->emitArtifacts(generateSharedLib, generateStaticLib, generateClientParameters, generateCompilationFeedback)) { return StreamStringError("Can't emit artifacts: ") << llvm::toString(std::move(err)); } return *outputLib.get(); } template llvm::Expected compileModuleOrSource(CompilerEngine *engine, T module, std::string outputDirPath, std::string runtimeLibraryPath, bool generateSharedLib, bool generateStaticLib, bool generateClientParameters, bool generateCompilationFeedback) { using Library = mlir::concretelang::CompilerEngine::Library; auto outputLib = std::make_shared(outputDirPath, runtimeLibraryPath); auto target = CompilerEngine::Target::LIBRARY; auto compilation = engine->compile(module, target, outputLib); if (!compilation) { return compilation.takeError(); } if (auto err = outputLib->emitArtifacts(generateSharedLib, generateStaticLib, generateClientParameters, generateCompilationFeedback)) { return StreamStringError("Can't emit artifacts: ") << llvm::toString(std::move(err)); } return *outputLib.get(); } llvm::Expected CompilerEngine::compile(llvm::SourceMgr &sm, std::string outputDirPath, std::string runtimeLibraryPath, bool generateSharedLib, bool generateStaticLib, bool generateClientParameters, bool generateCompilationFeedback) { return compileModuleOrSource( this, sm, outputDirPath, runtimeLibraryPath, generateSharedLib, generateStaticLib, generateClientParameters, generateCompilationFeedback); } llvm::Expected CompilerEngine::compile(mlir::ModuleOp module, std::string outputDirPath, std::string runtimeLibraryPath, bool generateSharedLib, bool generateStaticLib, bool generateClientParameters, bool generateCompilationFeedback) { return compileModuleOrSource( this, module, outputDirPath, runtimeLibraryPath, generateSharedLib, generateStaticLib, generateClientParameters, generateCompilationFeedback); } /// Returns the path of the shared library std::string CompilerEngine::Library::getSharedLibraryPath(std::string outputDirPath) { llvm::SmallString<0> sharedLibraryPath(outputDirPath); llvm::sys::path::append(sharedLibraryPath, "sharedlib" + DOT_SHARED_LIB_EXT); return sharedLibraryPath.str().str(); } /// Returns the path of the static library std::string CompilerEngine::Library::getStaticLibraryPath(std::string outputDirPath) { llvm::SmallString<0> staticLibraryPath(outputDirPath); llvm::sys::path::append(staticLibraryPath, "staticlib" + DOT_STATIC_LIB_EXT); return staticLibraryPath.str().str(); } /// Returns the path of the client parameter std::string CompilerEngine::Library::getProgramInfoPath(std::string outputDirPath) { llvm::SmallString<0> programInfoPath(outputDirPath); llvm::sys::path::append(programInfoPath, "program_info.concrete.params.json"); return programInfoPath.str().str(); } /// Returns the path of the compiler feedback std::string CompilerEngine::Library::getCompilationFeedbackPath(std::string outputDirPath) { llvm::SmallString<0> compilationFeedbackPath(outputDirPath); llvm::sys::path::append(compilationFeedbackPath, "compilation_feedback.json"); return compilationFeedbackPath.str().str(); } const std::string CompilerEngine::Library::OBJECT_EXT = ".o"; const std::string CompilerEngine::Library::LINKER = "ld"; #ifdef __APPLE__ // We need to tell the linker that some symbols will be missing during // linking, this symbols should be available during runtime however. // Starting from Mac 11 (Big Sur), it appears we need to add -L // /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem for // the sharedlib to link properly. const std::string CompilerEngine::Library::LINKER_SHARED_OPT = " -dylib -undefined dynamic_lookup -L " "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem " "-o "; const std::string CompilerEngine::Library::DOT_SHARED_LIB_EXT = ".dylib"; #else // Linux const std::string CompilerEngine::Library::LINKER_SHARED_OPT = " --shared -o "; const std::string CompilerEngine::Library::DOT_SHARED_LIB_EXT = ".so"; #endif const std::string CompilerEngine::Library::AR = "ar"; const std::string CompilerEngine::Library::AR_STATIC_OPT = " rcs "; const std::string CompilerEngine::Library::DOT_STATIC_LIB_EXT = ".a"; void CompilerEngine::Library::addExtraObjectFilePath(std::string path) { objectsPath.push_back(path); } Message CompilerEngine::Library::getProgramInfo() const { return programInfo; } const std::string &CompilerEngine::Library::getOutputDirPath() const { return outputDirPath; } llvm::Expected CompilerEngine::Library::emitProgramInfoJSON() { auto programInfoPath = getProgramInfoPath(outputDirPath); std::error_code error; llvm::raw_fd_ostream out(programInfoPath, error); auto maybeJson = programInfo.writeJsonToString(); if (maybeJson.has_failure()) { return StreamStringError(maybeJson.as_failure().error().mesg); } auto json = maybeJson.value(); out << json; out.close(); return programInfoPath; } llvm::Expected CompilerEngine::Library::emitCompilationFeedbackJSON() { auto path = getCompilationFeedbackPath(outputDirPath); llvm::json::Value value(compilationFeedback); std::error_code error; llvm::raw_fd_ostream out(path, error); if (error) { return StreamStringError("cannot emit client parameters, error: ") << error.message(); } out << llvm::formatv("{0:2}", value); out.close(); return path; } CompilationOptions currentCompilationOptions; void setCurrentCompilationOptions(CompilationOptions options) { currentCompilationOptions = options; } CompilationOptions getCurrentCompilationOptions() { return currentCompilationOptions; } void printTluFusing(mlir::Value v1, mlir::Value v2, mlir::Value v1v2) { llvm::outs() << "INFO: Fused `" << v1 << "` and `" << v2 << "` into `" << v1v2 << "`\n"; } llvm::Expected CompilerEngine::Library::setCompilationResult(CompilationResult &compilation) { llvm::Module *module = compilation.llvmModule.get(); auto sourceName = module->getSourceFileName(); if (sourceName == "" || sourceName == "LLVMDialectModule") { sourceName = this->outputDirPath + "/program.module-" + std::to_string(objectsPath.size()) + ".mlir"; } auto objectPath = sourceName + OBJECT_EXT; if (auto error = mlir::concretelang::emitObject(*module, objectPath)) { return std::move(error); } addExtraObjectFilePath(objectPath); if (compilation.programInfo) { programInfo = *compilation.programInfo; } if (compilation.feedback.has_value()) { compilationFeedback = compilation.feedback.value(); } return objectPath; } bool stringEndsWith(std::string path, std::string requiredExt) { return path.substr(path.size() - requiredExt.size()) == requiredExt; } std::string removeDotExt(std::string path, std::string dotExt) { return (stringEndsWith(path, dotExt)) ? path.substr(0, path.size() - dotExt.size()) : path; } std::string ensureLibDotExt(std::string path, std::string dotExt) { path = removeDotExt(path, CompilerEngine::Library::DOT_STATIC_LIB_EXT); path = removeDotExt(path, CompilerEngine::Library::DOT_SHARED_LIB_EXT); return path + dotExt; } llvm::Expected CompilerEngine::Library::emit( std::string path, std::string dotExt, std::string linker, std::optional> extraArgs) { auto pathDotExt = ensureLibDotExt(path, dotExt); auto error = mlir::concretelang::emitLibrary(objectsPath, pathDotExt, linker, extraArgs); if (error) { return std::move(error); } return pathDotExt; } llvm::Expected CompilerEngine::Library::emitShared() { std::vector extraArgs; std::string fullRuntimeLibraryName = ""; #ifdef __APPLE__ // to issue the command for fixing the runtime dependency of the generated // lib bool fixRuntimeDep = false; #endif if (!runtimeLibraryPath.empty()) { // Getting the parent dir should work on Linux and Mac std::size_t rpathLastPos = runtimeLibraryPath.find_last_of("/"); std::string rpath = ""; std::string runtimeLibraryName = ""; if (rpathLastPos != std::string::npos) { rpath = runtimeLibraryPath.substr(0, rpathLastPos); fullRuntimeLibraryName = runtimeLibraryPath.substr( rpathLastPos + 1, runtimeLibraryPath.length()); // runtimeLibraryName is part of fullRuntimeLibraryName = // lib(runtimeLibraryName).dylib runtimeLibraryName = removeDotExt(fullRuntimeLibraryName, DOT_SHARED_LIB_EXT); if (runtimeLibraryName.rfind("lib", 0) == 0) { // starts with lib runtimeLibraryName = runtimeLibraryName.substr(3, runtimeLibraryName.length()); } } #ifdef __APPLE__ if (!rpath.empty() && !runtimeLibraryName.empty()) { fixRuntimeDep = true; extraArgs.push_back("-l" + runtimeLibraryName); extraArgs.push_back("-L" + rpath); extraArgs.push_back("-rpath " + rpath); } #else // Linux extraArgs.push_back(runtimeLibraryPath); if (!rpath.empty()) { extraArgs.push_back("-rpath=" + rpath); // Use RPATH instead of RUNPATH for transitive dependencies extraArgs.push_back("--disable-new-dtags"); } #endif } auto path = emit(getSharedLibraryPath(outputDirPath), DOT_SHARED_LIB_EXT, LINKER + LINKER_SHARED_OPT, extraArgs); if (path) { sharedLibraryPath = path.get(); #ifdef __APPLE__ // when dellocate is used to include dependencies in python wheels, the // runtime library will have an id that is prefixed with /DLC, and that // path doesn't exist. So when generated libraries won't be able to find // it during load time. To solve this, we change the dep in the generated // library to be relative to the rpath which should be set correctly // during linking. This shouldn't have an impact when // /DLC/concrete/.dylibs/* isn't a dependency in the first place (when not // using python). if (fixRuntimeDep) { std::string fixRuntimeDepCmd = "install_name_tool -change " "/DLC/concrete/.dylibs/" + fullRuntimeLibraryName + " @rpath/" + fullRuntimeLibraryName + " " + sharedLibraryPath; auto error = mlir::concretelang::callCmd(fixRuntimeDepCmd); if (error) { return std::move(error); } } #endif } return path; } llvm::Expected CompilerEngine::Library::emitStatic() { auto path = emit(getStaticLibraryPath(outputDirPath), DOT_STATIC_LIB_EXT, AR + AR_STATIC_OPT); if (path) { staticLibraryPath = path.get(); } return path; } llvm::Error CompilerEngine::Library::emitArtifacts(bool sharedLib, bool staticLib, bool clientParameters, bool compilationFeedback) { // Create output directory if doesn't exist llvm::sys::fs::create_directory(outputDirPath); if (sharedLib) { if (auto err = emitShared().takeError()) { return err; } } if (staticLib) { if (auto err = emitStatic().takeError()) { return err; } } if (clientParameters) { if (auto err = emitProgramInfoJSON().takeError()) { return err; } } if (compilationFeedback) { if (auto err = emitCompilationFeedbackJSON().takeError()) { return err; } } return llvm::Error::success(); } CompilerEngine::Library::~Library() { if (cleanUp) { for (auto path : objectsPath) { remove(path.c_str()); } } } } // namespace concretelang } // namespace mlir