mirror of
https://github.com/luxfhe/torus.git
synced 2026-10-30 17:07:26 +00:00
The headers said 'BSD3 License with Lux Industries Exceptions', which is not a licence that exists — the sweep that removed the upstream vendor's name substituted ours into the licence title instead of correcting it. LICENSE.txt is the BSD 3-Clause Clear License and the headers now say so.
1098 lines
39 KiB
C++
1098 lines
39 KiB
C++
// Part of the Torus Compiler Project, under the BSD 3-Clause Clear
|
|
// License. See
|
|
// https://github.com/luxfhe/torus/blob/main/LICENSE.txt
|
|
// for license information.
|
|
|
|
#include "toruslang/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 <err.h>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <llvm/Support/Debug.h>
|
|
#include <memory>
|
|
#include <mlir/Dialect/Arith/Transforms/BufferizableOpInterfaceImpl.h>
|
|
#include <mlir/Dialect/Bufferization/IR/Bufferization.h>
|
|
#include <mlir/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.h>
|
|
#include <mlir/Dialect/Linalg/Transforms/TilingInterfaceImpl.h>
|
|
#include <mlir/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.h>
|
|
#include <mlir/Dialect/Tensor/Transforms/BufferizableOpInterfaceImpl.h>
|
|
#include <optional>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
|
|
#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 "torus-protocol.capnp.h"
|
|
#include "toruslang/Conversion/Utils/GlobalFHEContext.h"
|
|
#include "toruslang/Dialect/Torus/IR/TorusDialect.h"
|
|
#include "toruslang/Dialect/Torus/Transforms/BufferizableOpInterfaceImpl.h"
|
|
#include "toruslang/Dialect/FHE/IR/FHEDialect.h"
|
|
#include "toruslang/Dialect/FHELinalg/IR/FHELinalgDialect.h"
|
|
#include "toruslang/Dialect/Optimizer/IR/OptimizerDialect.h"
|
|
#include "toruslang/Dialect/RT/IR/RTDialect.h"
|
|
#include "toruslang/Dialect/RT/Transforms/BufferizableOpInterfaceImpl.h"
|
|
#include "toruslang/Dialect/SDFG/IR/SDFGDialect.h"
|
|
#include "toruslang/Dialect/SDFG/Transforms/BufferizableOpInterfaceImpl.h"
|
|
#include "toruslang/Dialect/SDFG/Transforms/SDFGConvertibleOpInterfaceImpl.h"
|
|
#include "toruslang/Dialect/TFHE/IR/TFHEDialect.h"
|
|
#include "toruslang/Dialect/Tracing/IR/TracingDialect.h"
|
|
#include "toruslang/Dialect/Tracing/Transforms/BufferizableOpInterfaceImpl.h"
|
|
#include "toruslang/Dialect/TypeInference/IR/TypeInferenceDialect.h"
|
|
#include "toruslang/Support/CompilerEngine.h"
|
|
#include "toruslang/Support/Encodings.h"
|
|
#include "toruslang/Support/Error.h"
|
|
#include "toruslang/Support/LLVMEmitFile.h"
|
|
#include "toruslang/Support/Pipeline.h"
|
|
#include "toruslang/Support/Utils.h"
|
|
#include <toruslang/Runtime/GPUDFG.hpp>
|
|
|
|
namespace {
|
|
/// Returns the path of the shared library
|
|
std::string getSharedLibraryPath(std::string outputDirPath) {
|
|
llvm::SmallString<0> sharedLibraryPath(outputDirPath);
|
|
llvm::sys::path::append(sharedLibraryPath,
|
|
"sharedlib" +
|
|
mlir::toruslang::Library::DOT_SHARED_LIB_EXT);
|
|
return sharedLibraryPath.str().str();
|
|
}
|
|
|
|
/// Returns the path of the static library
|
|
std::string getStaticLibraryPath(std::string outputDirPath) {
|
|
llvm::SmallString<0> staticLibraryPath(outputDirPath);
|
|
llvm::sys::path::append(staticLibraryPath,
|
|
"staticlib" +
|
|
mlir::toruslang::Library::DOT_STATIC_LIB_EXT);
|
|
return staticLibraryPath.str().str();
|
|
}
|
|
|
|
/// Returns the path of the client parameter
|
|
std::string getProgramInfoPath(std::string outputDirPath) {
|
|
llvm::SmallString<0> programInfoPath(outputDirPath);
|
|
llvm::sys::path::append(programInfoPath, "program_info.torus.params.json");
|
|
return programInfoPath.str().str();
|
|
}
|
|
|
|
/// Returns the path of the compiler feedback
|
|
std::string getCompilationFeedbackPath(std::string outputDirPath) {
|
|
llvm::SmallString<0> compilationFeedbackPath(outputDirPath);
|
|
llvm::sys::path::append(compilationFeedbackPath, "compilation_feedback.json");
|
|
return compilationFeedbackPath.str().str();
|
|
}
|
|
} // namespace
|
|
|
|
namespace mlir {
|
|
namespace toruslang {
|
|
|
|
/// Creates a new compilation context that can be shared across
|
|
/// compilation engines and results
|
|
std::shared_ptr<CompilationContext> CompilationContext::createShared() {
|
|
return std::make_shared<CompilationContext>();
|
|
}
|
|
|
|
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::toruslang::TypeInference::TypeInferenceDialect,
|
|
mlir::toruslang::Tracing::TracingDialect,
|
|
mlir::toruslang::Optimizer::OptimizerDialect,
|
|
mlir::toruslang::RT::RTDialect, mlir::toruslang::FHE::FHEDialect,
|
|
mlir::toruslang::TFHE::TFHEDialect,
|
|
mlir::toruslang::FHELinalg::FHELinalgDialect,
|
|
mlir::toruslang::Torus::TorusDialect,
|
|
mlir::toruslang::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);
|
|
Torus::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::toruslang::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<bool(mlir::Pass *)> enablePass) {
|
|
this->enablePass = enablePass;
|
|
}
|
|
|
|
/// Returns the optimizer::Description
|
|
llvm::Expected<std::optional<optimizer::Description>>
|
|
CompilerEngine::getTorusOptimizerDescription(CompilationResult &res) {
|
|
mlir::MLIRContext &mlirContext = *this->compilationContext->getMLIRContext();
|
|
mlir::ModuleOp module = res.mlirModuleRef->get();
|
|
auto config = this->compilerOptions.optimizerConfig;
|
|
// If the values has been overwritten returns
|
|
if (this->overrideMaxEintPrecision.has_value() &&
|
|
this->overrideMaxMANP.has_value()) {
|
|
auto constraint = mlir::toruslang::V0FHEConstraint{
|
|
this->overrideMaxMANP.value(), this->overrideMaxEintPrecision.value()};
|
|
return optimizer::Description{constraint, std::nullopt};
|
|
}
|
|
auto description = mlir::toruslang::pipeline::getFHEContextFromFHE(
|
|
mlirContext, module, config, enablePass);
|
|
if (auto err = description.takeError()) {
|
|
return std::move(err);
|
|
}
|
|
if (!description->has_value()) { // The pass has not been run
|
|
return std::nullopt;
|
|
}
|
|
return description;
|
|
}
|
|
|
|
/// 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::toruslang::V0FHEContext{constraint, v0Params});
|
|
|
|
ProgramCompilationFeedback feedback;
|
|
res.feedback.emplace(feedback);
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
// compute parameters
|
|
else {
|
|
auto descr = getTorusOptimizerDescription(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::toruslang::V0FHEContext{
|
|
descr.get().value().constraint, *expectedSolution});
|
|
res.feedback.emplace(feedback);
|
|
}
|
|
|
|
// we don't need change_partition ops after getting parameters
|
|
mlir::toruslang::pipeline::removeChangePartitionOps(
|
|
res.mlirModuleRef->get());
|
|
|
|
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<std::shared_ptr<Library>>;
|
|
// 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<CompilationResult>
|
|
CompilerEngine::compile(llvm::SourceMgr &sm, Target target, OptionalLib lib) {
|
|
std::unique_ptr<mlir::SourceMgrDiagnosticVerifierHandler> smHandler;
|
|
std::string diagnosticsMsg;
|
|
llvm::raw_string_ostream diagnosticsOS(diagnosticsMsg);
|
|
auto errorDiag =
|
|
[&](std::string prefixMsg) -> llvm::Expected<CompilationResult> {
|
|
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<mlir::SourceMgrDiagnosticVerifierHandler>(
|
|
sm, &mlirContext, diagnosticsOS);
|
|
}
|
|
|
|
mlirContext.printOpOnDiagnostic(false);
|
|
|
|
mlir::OwningOpRef<mlir::ModuleOp> mlirModuleRef =
|
|
mlir::parseSourceFile<mlir::ModuleOp>(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<CompilationResult>
|
|
CompilerEngine::compile(mlir::ModuleOp moduleOp, Target target,
|
|
OptionalLib lib) {
|
|
CompilationResult res(this->compilationContext);
|
|
|
|
CompilationOptions &options = this->compilerOptions;
|
|
|
|
mlir::MLIRContext &mlirContext = *this->compilationContext->getMLIRContext();
|
|
|
|
auto dataflowParallelize =
|
|
options.autoParallelize || options.dataflowParallelize;
|
|
auto loopParallelize = options.autoParallelize || options.loopParallelize;
|
|
|
|
// Sanity checks for enabling GPU usage: the compiler must have been
|
|
// compiled with Cuda support (especially important when building
|
|
// python wheels), and at least one device must be available to
|
|
// execute on.
|
|
if (options.emitGPUOps) {
|
|
// If this compiler is not compiled using Cuda support, then
|
|
// requesting GPU is forbidden - instead of a hard error, issue a
|
|
// warning and disable the GPU option.
|
|
|
|
#ifdef TORUSLANG_CUDA_SUPPORT
|
|
// Ensure that at least one Cuda device is available if GPU option
|
|
// is used
|
|
int num;
|
|
if (cudaGetDeviceCount(&num) != cudaSuccess) {
|
|
warnx("No Cuda device available on this system (either not present or "
|
|
"the driver is not online).\n"
|
|
"Continuing without GPU acceleration.");
|
|
options.emitGPUOps = false;
|
|
options.emitSDFGOps = false;
|
|
options.batchTFHEOps = false;
|
|
}
|
|
#else
|
|
// Allow compilation to complete if only code generation is expected.
|
|
if (target != Target::LIBRARY) {
|
|
warnx("This instance of the Torus compiler does not support GPU "
|
|
"acceleration."
|
|
" Allowing code generation to proceed, but execution will not be "
|
|
"possible.");
|
|
} else {
|
|
warnx("This instance of the Torus compiler does not support GPU "
|
|
"acceleration."
|
|
" If you are using Torus-Python, it means that the module "
|
|
"installed is not GPU enabled.\n"
|
|
"Continuing without GPU acceleration.");
|
|
options.emitGPUOps = false;
|
|
options.emitSDFGOps = false;
|
|
options.batchTFHEOps = false;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Finally for now we cannot allow dataflow parallelization at the
|
|
// same time as GPU usage. This restriction will be relaxed later.
|
|
if (dataflowParallelize) {
|
|
warnx("Dataflow parallelization and GPU offloading have both been "
|
|
"requested. This is not currently supported. Continuing without "
|
|
"dataflow parallelization.");
|
|
dataflowParallelize = false;
|
|
}
|
|
}
|
|
|
|
mlir::OwningOpRef<mlir::ModuleOp> 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::toruslang::encodings::getProgramEncoding(module);
|
|
if (!encodingInfosOrErr) {
|
|
return encodingInfosOrErr.takeError();
|
|
}
|
|
options.encodings = std::move(*encodingInfosOrErr);
|
|
}
|
|
|
|
if (mlir::toruslang::pipeline::transformFHEBoolean(mlirContext, module,
|
|
enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Transforming FHE boolean ops failed");
|
|
}
|
|
|
|
if (options.chunkIntegers) {
|
|
if (mlir::toruslang::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<torusprotocol::IntegerCiphertextEncodingInfo::ChunkedMode>>
|
|
maybeChunkInfo(std::nullopt);
|
|
if (options.chunkIntegers) {
|
|
auto chunkedMode = Message<
|
|
torusprotocol::IntegerCiphertextEncodingInfo::ChunkedMode>();
|
|
chunkedMode.asBuilder().setSize(options.chunkSize);
|
|
chunkedMode.asBuilder().setWidth(options.chunkWidth);
|
|
maybeChunkInfo = chunkedMode;
|
|
}
|
|
mlir::toruslang::encodings::setProgramEncodingModes(
|
|
*options.encodings, maybeChunkInfo, res.fheContext);
|
|
}
|
|
|
|
// FHELinalg tiling
|
|
if (options.fhelinalgTileSizes) {
|
|
if (mlir::toruslang::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::toruslang::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::toruslang::pipeline::tileMarkedLinalg(mlirContext, module,
|
|
enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Tiling of Linalg operations failed");
|
|
}
|
|
|
|
if (mlir::toruslang::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::toruslang::pipeline::autopar(mlirContext, module, enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Dataflow parallelization failed");
|
|
}
|
|
|
|
if (target == Target::FHE_DF_PARALLELIZED)
|
|
return std::move(res);
|
|
|
|
if (mlir::toruslang::pipeline::lowerLinalgToLoops(
|
|
mlirContext, module, enablePass, loopParallelize)
|
|
.failed()) {
|
|
return StreamStringError("Lowering from Linalg Generic to Loops failed");
|
|
}
|
|
|
|
// FHE -> TFHE
|
|
if (mlir::toruslang::pipeline::lowerFHEToTFHE(mlirContext, module,
|
|
res.fheContext, enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Lowering from FHE to TFHE failed");
|
|
}
|
|
|
|
// Optimizing TFHE
|
|
if (this->compilerOptions.optimizeTFHE &&
|
|
mlir::toruslang::pipeline::optimizeTFHE(mlirContext, module,
|
|
this->enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Optimizing TFHE failed");
|
|
}
|
|
|
|
// Transforming TFHE operations
|
|
if (mlir::toruslang::pipeline::transformTFHEOperations(mlirContext, module,
|
|
this->enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Transforming TFHE operations failed");
|
|
}
|
|
|
|
if (target == Target::TFHE)
|
|
return std::move(res);
|
|
|
|
if (mlir::toruslang::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::toruslang::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<torusprotocol::ProgramInfo>();
|
|
programInfo.asBuilder().initCircuits(1);
|
|
programInfo.asBuilder().getCircuits()[0].setName(std::string("main"));
|
|
res.programInfo = programInfo;
|
|
} else {
|
|
auto programInfoOrErr =
|
|
mlir::toruslang::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::toruslang::pipeline::extractTFHEStatistics(
|
|
mlirContext, module, this->enablePass, res.feedback.value())
|
|
.failed()) {
|
|
return StreamStringError("Extracting TFHE statistics failed");
|
|
}
|
|
}
|
|
|
|
if (options.simulate) {
|
|
if (mlir::toruslang::pipeline::simulateTFHE(
|
|
mlirContext, module, res.fheContext,
|
|
options.enableOverflowDetectionInSimulation, this->enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Simulating TFHE failed");
|
|
}
|
|
}
|
|
|
|
if (target == Target::SIMULATED_TFHE)
|
|
return std::move(res);
|
|
|
|
if (options.batchTFHEOps) {
|
|
if (mlir::toruslang::pipeline::batchTFHE(mlirContext, module, enablePass,
|
|
options.maxBatchSize)
|
|
.failed()) {
|
|
return StreamStringError("Batching of TFHE operations");
|
|
}
|
|
}
|
|
|
|
if (target == Target::BATCHED_TFHE)
|
|
return std::move(res);
|
|
|
|
// TFHE -> Torus
|
|
if (mlir::toruslang::pipeline::lowerTFHEToTorus(mlirContext, module,
|
|
this->enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Lowering from TFHE to Torus failed");
|
|
}
|
|
|
|
if (target == Target::TORUS)
|
|
return std::move(res);
|
|
|
|
// Extract SDFG data flow graph from Torus representation
|
|
|
|
if (options.emitSDFGOps) {
|
|
if (mlir::toruslang::pipeline::extractSDFGOps(
|
|
mlirContext, module, enablePass,
|
|
options.unrollLoopsWithSDFGConvertibleOps)
|
|
.failed()) {
|
|
return StreamStringError("Extraction of SDFG operations from Torus "
|
|
"representation failed");
|
|
}
|
|
}
|
|
|
|
if (target == Target::SDFG) {
|
|
return std::move(res);
|
|
}
|
|
|
|
// Add runtime context in Torus
|
|
if (mlir::toruslang::pipeline::addRuntimeContext(mlirContext, module,
|
|
enablePass)
|
|
.failed()) {
|
|
return StreamStringError("Adding Runtime Context failed");
|
|
}
|
|
|
|
// SDFG -> Canonical dialects
|
|
if (mlir::toruslang::pipeline::lowerSDFGToStd(mlirContext, module,
|
|
enablePass)
|
|
.failed()) {
|
|
return StreamStringError(
|
|
"Lowering from SDFG to canonical MLIR dialects failed");
|
|
}
|
|
|
|
// bufferize and related passes
|
|
if (mlir::toruslang::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::toruslang::pipeline::computeMemoryUsage(
|
|
mlirContext, module, this->enablePass, res.feedback.value())
|
|
.failed()) {
|
|
return StreamStringError("Computing memory usage failed");
|
|
}
|
|
}
|
|
|
|
// Restrict direct lowering when already generating GPU code through
|
|
// the SDFG dialect.
|
|
bool lowerDirectlyToGPUOps = (options.emitGPUOps && !options.emitSDFGOps);
|
|
if (mlir::toruslang::pipeline::lowerToCAPI(mlirContext, module, enablePass,
|
|
lowerDirectlyToGPUOps)
|
|
.failed()) {
|
|
return StreamStringError("Failed to lower to CAPI");
|
|
}
|
|
|
|
// MLIR canonical dialects -> LLVM Dialect
|
|
if (mlir::toruslang::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::toruslang::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::toruslang::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<CompilationResult>
|
|
CompilerEngine::compile(llvm::StringRef s, Target target, OptionalLib lib) {
|
|
std::unique_ptr<llvm::MemoryBuffer> 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<CompilationResult>
|
|
CompilerEngine::compile(std::unique_ptr<llvm::MemoryBuffer> buffer,
|
|
Target target, OptionalLib lib) {
|
|
llvm::SourceMgr sm;
|
|
sm.AddNewSourceBuffer(std::move(buffer), llvm::SMLoc());
|
|
|
|
return this->compile(sm, target, lib);
|
|
}
|
|
|
|
llvm::Expected<Library>
|
|
CompilerEngine::compile(std::vector<std::string> inputs,
|
|
std::string outputDirPath,
|
|
std::string runtimeLibraryPath, bool generateSharedLib,
|
|
bool generateStaticLib, bool generateClientParameters,
|
|
bool generateCompilationFeedback) {
|
|
using Library = mlir::toruslang::Library;
|
|
auto outputLib = std::make_shared<Library>(outputDirPath, runtimeLibraryPath);
|
|
auto target = 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 <typename T>
|
|
llvm::Expected<Library>
|
|
compileModuleOrSource(CompilerEngine *engine, T module,
|
|
std::string outputDirPath, std::string runtimeLibraryPath,
|
|
bool generateSharedLib, bool generateStaticLib,
|
|
bool generateClientParameters,
|
|
bool generateCompilationFeedback) {
|
|
using Library = mlir::toruslang::Library;
|
|
auto outputLib = std::make_shared<Library>(outputDirPath, runtimeLibraryPath);
|
|
auto target = 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<Library>
|
|
CompilerEngine::compile(llvm::SourceMgr &sm, std::string outputDirPath,
|
|
std::string runtimeLibraryPath, bool generateSharedLib,
|
|
bool generateStaticLib, bool generateClientParameters,
|
|
bool generateCompilationFeedback) {
|
|
return compileModuleOrSource<llvm::SourceMgr &>(
|
|
this, sm, outputDirPath, runtimeLibraryPath, generateSharedLib,
|
|
generateStaticLib, generateClientParameters, generateCompilationFeedback);
|
|
}
|
|
|
|
llvm::Expected<Library>
|
|
CompilerEngine::compile(mlir::ModuleOp module, std::string outputDirPath,
|
|
std::string runtimeLibraryPath, bool generateSharedLib,
|
|
bool generateStaticLib, bool generateClientParameters,
|
|
bool generateCompilationFeedback) {
|
|
return compileModuleOrSource<mlir::ModuleOp>(
|
|
this, module, outputDirPath, runtimeLibraryPath, generateSharedLib,
|
|
generateStaticLib, generateClientParameters, generateCompilationFeedback);
|
|
}
|
|
|
|
const std::string Library::OBJECT_EXT = ".o";
|
|
const std::string 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 Library::LINKER_SHARED_OPT =
|
|
" -dylib -undefined dynamic_lookup -L "
|
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lSystem "
|
|
"-o ";
|
|
const std::string Library::DOT_SHARED_LIB_EXT = ".dylib";
|
|
#else // Linux
|
|
const std::string Library::LINKER_SHARED_OPT = " --shared -o ";
|
|
const std::string Library::DOT_SHARED_LIB_EXT = ".so";
|
|
#endif
|
|
const std::string Library::AR = "ar";
|
|
const std::string Library::AR_STATIC_OPT = " rcs ";
|
|
const std::string Library::DOT_STATIC_LIB_EXT = ".a";
|
|
|
|
void Library::addExtraObjectFilePath(std::string path) {
|
|
objectsPath.push_back(path);
|
|
}
|
|
|
|
Result<Message<torusprotocol::ProgramInfo>> Library::getProgramInfo() {
|
|
if (!programInfo.has_value()) {
|
|
programInfo = Message<torusprotocol::ProgramInfo>();
|
|
auto path = this->getProgramInfoPath();
|
|
std::ifstream file(path);
|
|
std::string content((std::istreambuf_iterator<char>(file)),
|
|
(std::istreambuf_iterator<char>()));
|
|
if (file.fail()) {
|
|
return StringError("Cannot read program info file...");
|
|
}
|
|
if (programInfo->readJsonFromString(content).has_failure()) {
|
|
return StringError("Program info file corrupted...");
|
|
}
|
|
}
|
|
return programInfo.value();
|
|
}
|
|
|
|
const std::string &Library::getOutputDirPath() const { return outputDirPath; }
|
|
|
|
/// Returns the path of the shared library
|
|
std::string Library::getSharedLibraryPath() const {
|
|
return ::getSharedLibraryPath(getOutputDirPath());
|
|
}
|
|
|
|
/// Returns the path of the static library
|
|
std::string Library::getStaticLibraryPath() const {
|
|
return ::getStaticLibraryPath(getOutputDirPath());
|
|
};
|
|
|
|
/// Returns the path of the program info
|
|
std::string Library::getProgramInfoPath() const {
|
|
return ::getProgramInfoPath(getOutputDirPath());
|
|
};
|
|
|
|
/// Returns the path of the compilation feedback
|
|
std::string Library::getCompilationFeedbackPath() const {
|
|
return ::getCompilationFeedbackPath(getOutputDirPath());
|
|
};
|
|
|
|
llvm::Expected<std::string> 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<std::string> 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<std::string>
|
|
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::toruslang::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, Library::DOT_STATIC_LIB_EXT);
|
|
path = removeDotExt(path, Library::DOT_SHARED_LIB_EXT);
|
|
return path + dotExt;
|
|
}
|
|
|
|
llvm::Expected<std::string>
|
|
Library::emit(std::string path, std::string dotExt, std::string linker,
|
|
std::optional<std::vector<std::string>> extraArgs) {
|
|
auto pathDotExt = ensureLibDotExt(path, dotExt);
|
|
auto error = mlir::toruslang::emitLibrary(objectsPath, pathDotExt, linker,
|
|
extraArgs);
|
|
if (error) {
|
|
return std::move(error);
|
|
}
|
|
return pathDotExt;
|
|
}
|
|
|
|
llvm::Expected<std::string> Library::emitShared() {
|
|
std::vector<std::string> 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/torus/.dylibs/* isn't a dependency in the first place (when not
|
|
// using python).
|
|
if (fixRuntimeDep) {
|
|
std::string fixRuntimeDepCmd = "install_name_tool -change "
|
|
"/DLC/torus/.dylibs/" +
|
|
fullRuntimeLibraryName + " @rpath/" +
|
|
fullRuntimeLibraryName + " " +
|
|
sharedLibraryPath;
|
|
auto error = mlir::toruslang::callCmd(fixRuntimeDepCmd);
|
|
if (error) {
|
|
return std::move(error);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
llvm::Expected<std::string> Library::emitStatic() {
|
|
auto path = emit(::getStaticLibraryPath(outputDirPath), DOT_STATIC_LIB_EXT,
|
|
AR + AR_STATIC_OPT);
|
|
if (path) {
|
|
staticLibraryPath = path.get();
|
|
}
|
|
return path;
|
|
}
|
|
|
|
llvm::Error 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();
|
|
}
|
|
|
|
Library::~Library() {
|
|
if (cleanUp) {
|
|
for (auto path : objectsPath) {
|
|
remove(path.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace toruslang
|
|
} // namespace mlir
|