blob: 21947c9139e69322ea60f0065ae0e25ff81a6b8a [file]
// Copyright 2021 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/compiler/Bindings/Native/Transforms/Passes.h"
#include "iree/compiler/ConstEval/PassDetail.h"
#include "iree/compiler/ConstEval/Passes.h"
#include "iree/compiler/ConstEval/Runtime.h"
#include "iree/compiler/Dialect/Flow/Transforms/Passes.h"
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
#include "iree/compiler/Dialect/Stream/Transforms/Passes.h"
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "iree/compiler/Dialect/Util/Transforms/Passes.h"
#include "iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.h"
#include "iree/compiler/Dialect/VM/Transforms/Passes.h"
#include "iree/compiler/Utils/PassUtils.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/BlockAndValueMapping.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/SymbolTable.h"
#define DEBUG_TYPE "iree-const-eval"
using llvm::dbgs;
namespace mlir {
namespace iree_compiler {
namespace ConstEval {
namespace {
struct ProgramExtractor {
public:
ProgramExtractor(Operation *sourceModuleOp, Operation *targetModuleOp)
: sourceSymbolTable(sourceModuleOp),
targetSymbolTable(targetModuleOp),
builder(OpBuilder::atBlockEnd(&targetModuleOp->getRegion(0).front())) {}
// Creates an accessor function to load the given global value.
// Returns the created symbol name.
StringAttr createAccessor(IREE::Util::GlobalOp globalOp) {
Location loc = globalOp.getLoc();
std::string name = (llvm::Twine("get$") + globalOp.getSymbolName()).str();
Type globalType = globalOp.type();
auto funcType =
builder.getType<FunctionType>(TypeRange{}, TypeRange{globalType});
auto funcOp = FuncOp::create(loc, name, funcType);
StringAttr funcSymbolName = targetSymbolTable.insert(funcOp);
Block *entryBlock = funcOp.addEntryBlock();
OpBuilder funcBuilder = OpBuilder::atBlockEnd(entryBlock);
Value loadValue =
funcBuilder.create<IREE::Util::GlobalLoadOp>(loc, globalOp);
funcBuilder.create<ReturnOp>(loc, ValueRange{loadValue});
return funcSymbolName;
}
// Imports an op from the source module into the target. Cannot be used to
// import symbols.
void importOperation(Operation *sourceOp) {
Operation *targetOp = sourceOp->clone();
builder.insert(targetOp);
scanDependentSymbols(targetOp);
}
// Imports any dependencies. Should be called after all user-required imports
// are completed.
LogicalResult importDependencies() {
SmallVector<StringAttr> iterWorklist;
while (!symbolImportWorklist.empty()) {
iterWorklist.clear();
iterWorklist.swap(symbolImportWorklist);
for (StringAttr symbolRef : iterWorklist) {
if (targetSymbolTable.lookup(symbolRef)) continue;
Operation *sourceOp = sourceSymbolTable.lookup(symbolRef);
if (!sourceOp) {
return mlir::emitError(targetSymbolTable.getOp()->getLoc())
<< "symbol not found while building jit-eval module: "
<< symbolRef;
}
// Insert at top as ordering is respected.
auto ip = targetSymbolTable.getOp()->getRegion(0).front().begin();
Operation *targetOp = sourceOp->clone();
targetSymbolTable.insert(targetOp, ip);
scanDependentSymbols(targetOp);
}
}
return success();
}
void scanDependentSymbols(Operation *parentOp) {
// Find any global accessors and note their dependent symbols.
parentOp->walk([&](IREE::Util::GlobalAccessorOpInterface accessorOp) {
FlatSymbolRefAttr refAttr = accessorOp.getGlobalRefAttr();
symbolImportWorklist.push_back(refAttr.getAttr());
});
// TODO: Scan for functions, etc.
}
private:
SymbolTable sourceSymbolTable;
SymbolTable targetSymbolTable;
OpBuilder builder;
SmallVector<StringAttr> symbolImportWorklist;
};
// These options structs are not copy-constructable so we have to allocate them
// shared.
// TODO: See if we can make them copyable?
struct CompileOptions {
IREE::Flow::TransformOptions flowOptions;
IREE::HAL::TargetOptions executableOptions;
IREE::Stream::TransformOptions streamOptions;
IREE::VM::TargetOptions targetOptions;
};
struct JitGlobalsPass : public JitGlobalsBase<JitGlobalsPass> {
JitGlobalsPass()
: options(std::make_shared<CompileOptions>()),
compilePipeline("builtin.module") {
// Invoke IREE compilation flow.
// TODO: Find a better place for this canonical list of passes?
// TODO: Something better?
options->executableOptions.targets.push_back("vmvx");
options->targetOptions.i64Extension = true;
options->targetOptions.f32Extension = true;
options->targetOptions.f64Extension = true;
IREE::ABI::buildTransformPassPipeline(compilePipeline);
IREE::Flow::buildFlowTransformPassPipeline(compilePipeline,
options->flowOptions);
IREE::Stream::buildStreamTransformPassPipeline(compilePipeline,
options->streamOptions);
IREE::HAL::buildHALTransformPassPipeline(compilePipeline,
options->executableOptions);
IREE::VM::buildVMTransformPassPipeline(compilePipeline,
options->targetOptions);
}
void getDependentDialects(DialectRegistry &registry) const override {
compilePipeline.getDependentDialects(registry);
}
void runOnOperation() override {
auto outerModule = getOperation();
SymbolTable outerSymbolTable(outerModule);
OpBuilder builder = OpBuilder::atBlockEnd(outerModule.getBody());
auto innerModule = builder.create<ModuleOp>(outerModule.getLoc());
ProgramExtractor extractor(outerModule, innerModule);
SmallVector<Operation *> pruneOps;
// Import initializers.
for (auto childOp : outerModule.getOps<IREE::Util::InitializerOp>()) {
extractor.importOperation(childOp);
pruneOps.push_back(childOp);
}
// Transitively import any dependencies.
if (failed(extractor.importDependencies())) {
signalPassFailure();
}
// Find any globals that we pulled in which lack an initializer. These
// are the ones we will try to eval. Stash {func_symbol, global_symbol}
// pairs for later.
SmallVector<std::pair<StringAttr, StringAttr>> uninitializedGlobals;
for (Operation &childOp : *innerModule.getBody()) {
auto globalOp = llvm::dyn_cast<IREE::Util::GlobalOp>(childOp);
if (!globalOp) continue;
if (globalOp.getInitialValueAttr()) continue;
// Only generate an accessor for types our runtime bridge knows how to
// handle.
Type type = globalOp.type();
if (!CompiledBinary::isSupportedResultType(type)) {
LLVM_DEBUG(dbgs() << "JitGlobals: unsupported global type " << type);
continue;
}
StringAttr funcSymbol = extractor.createAccessor(globalOp);
uninitializedGlobals.emplace_back(funcSymbol, globalOp.sym_nameAttr());
}
// Early exit without compiling if no entry-points (this is not just an
// optimization: the low level compiler will fail on an empty module).
if (uninitializedGlobals.empty()) {
LLVM_DEBUG(dbgs() << "Not JIT'ing globals: no undefined globals found\n");
innerModule.erase();
return;
}
// Run the IREE compiler, transforming the inner module into a vm.module.
LLVM_DEBUG(dbgs() << "JIT'ing " << uninitializedGlobals.size()
<< " uninitialized globals\n");
if (failed(runPipeline(compilePipeline, innerModule))) {
return signalPassFailure();
}
// Generate a binary.
InMemoryCompiledBinary binary;
if (failed(binary.translateFromModule(innerModule))) {
return signalPassFailure();
}
// Kill the temporary program we constructed.
innerModule.erase();
bool modified = false;
for (auto &it : uninitializedGlobals) {
StringAttr funcSymbol = it.first;
StringAttr globalSymbol = it.second;
auto targetGlobal = llvm::cast<IREE::Util::GlobalOp>(
outerSymbolTable.lookup(globalSymbol));
Location loc = targetGlobal->getLoc();
Attribute value =
binary.invokeNullaryAsAttribute(loc, funcSymbol.strref());
if (!value) {
return signalPassFailure();
}
modified = true;
targetGlobal.setInitialValue(value);
}
// Delete any ops noted for pruning.
for (Operation *op : pruneOps) {
op->erase();
}
// Signal any outer fixed point iterator that we have modified
// globals and need another pass.
if (modified) {
signalFixedPointModified(outerModule);
}
}
std::shared_ptr<CompileOptions> options;
OpPassManager compilePipeline;
};
} // namespace
std::unique_ptr<OperationPass<ModuleOp>> createJitGlobalsPass() {
return std::make_unique<JitGlobalsPass>();
}
} // namespace ConstEval
} // namespace iree_compiler
} // namespace mlir