2020-05-21 09:48:53 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// Part of the LLVM Project, 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 "PassDetail.h"
|
2020-10-07 07:14:37 +08:00
|
|
|
#include "npcomp/RefBackend/RefBackend.h"
|
2020-05-21 09:48:53 +08:00
|
|
|
|
2021-06-26 08:25:09 +08:00
|
|
|
#include "mlir/Conversion/LinalgToLLVM/LinalgToLLVM.h"
|
2020-05-21 09:48:53 +08:00
|
|
|
#include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h"
|
|
|
|
#include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVMPass.h"
|
|
|
|
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
|
2021-02-23 04:08:17 +08:00
|
|
|
#include "mlir/Dialect/Math/Transforms/Passes.h"
|
2020-09-25 08:14:21 +08:00
|
|
|
#include "mlir/Dialect/StandardOps/Transforms/Passes.h"
|
2020-05-21 09:48:53 +08:00
|
|
|
#include "mlir/Transforms/DialectConversion.h"
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
2020-10-08 08:12:52 +08:00
|
|
|
#include "npcomp/Dialect/Refbackrt/IR/RefbackrtDialect.h"
|
|
|
|
#include "npcomp/Dialect/Refbackrt/IR/RefbackrtOps.h"
|
2020-05-21 09:48:53 +08:00
|
|
|
|
|
|
|
using namespace mlir;
|
|
|
|
using namespace mlir::NPCOMP;
|
2021-01-06 08:12:11 +08:00
|
|
|
using mlir::LLVM::LLVMArrayType;
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
using mlir::LLVM::LLVMFuncOp;
|
2021-01-06 08:12:11 +08:00
|
|
|
using mlir::LLVM::LLVMFunctionType;
|
|
|
|
using mlir::LLVM::LLVMPointerType;
|
|
|
|
using mlir::LLVM::LLVMStructType;
|
|
|
|
using mlir::LLVM::LLVMVoidType;
|
2020-05-21 09:48:53 +08:00
|
|
|
|
2020-07-11 08:31:24 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
2020-07-11 08:50:55 +08:00
|
|
|
// Descriptor types shared with the runtime.
|
|
|
|
//
|
|
|
|
// These correspond to the types in CompilerDataStructures.h
|
2020-07-11 08:31:24 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2021-03-11 01:53:03 +08:00
|
|
|
// MaxRank that the refbackrt ABI lowering is capable of handling
|
|
|
|
// NOTE: This parameter must stay consistent with
|
|
|
|
// `lib/RefBackend/LowerToRefbackrtABI.cpp`
|
|
|
|
static constexpr int kMaxRank = 6;
|
|
|
|
|
2021-01-06 08:12:11 +08:00
|
|
|
static LLVMPointerType getInt8PointerType(MLIRContext *context) {
|
2021-01-09 06:05:16 +08:00
|
|
|
return LLVMPointerType::get(IntegerType::get(context, 8));
|
2020-07-11 08:50:55 +08:00
|
|
|
}
|
|
|
|
|
2021-03-11 01:53:03 +08:00
|
|
|
static LLVMPointerType getInt32PointerType(MLIRContext *context) {
|
|
|
|
return LLVMPointerType::get(IntegerType::get(context, 32));
|
|
|
|
}
|
|
|
|
|
|
|
|
static LLVMStructType getInputDescriptorTy(MLIRContext *context) {
|
|
|
|
return LLVMStructType::getLiteral(
|
|
|
|
context, {
|
|
|
|
// ArgType
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// ElementType
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Rank
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Extents
|
|
|
|
LLVMPointerType::get(IntegerType::get(context, 32)),
|
|
|
|
// IsStatic
|
|
|
|
// IntegerType::get(context, 32),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
static LLVMStructType getOutputDescriptorTy(MLIRContext *context) {
|
|
|
|
return LLVMStructType::getLiteral(
|
|
|
|
context, {
|
|
|
|
// ArgType
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// ElementType
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Rank
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Extents
|
|
|
|
LLVMPointerType::get(IntegerType::get(context, 32)),
|
|
|
|
// IsStatic
|
|
|
|
// IntegerType::get(context, 32),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-06 08:12:11 +08:00
|
|
|
// Get the LLVM type for refbackrt::FuncDescriptor.
|
|
|
|
static LLVMStructType getFuncDescriptorTy(MLIRContext *context) {
|
2021-03-11 01:53:03 +08:00
|
|
|
return LLVMStructType::getLiteral(
|
|
|
|
context, {
|
|
|
|
// Name length.
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Name chars.
|
|
|
|
getInt8PointerType(context),
|
|
|
|
// Type-erased function pointer.
|
|
|
|
getInt8PointerType(context),
|
|
|
|
// Number of inputs.
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Number of outputs.
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
// Argument descriptors
|
|
|
|
LLVMPointerType::get(getInputDescriptorTy(context)),
|
|
|
|
// Result Descriptors
|
|
|
|
LLVMPointerType::get(getOutputDescriptorTy(context)),
|
|
|
|
});
|
2021-01-06 08:12:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the LLVM type for refbackrt::ModuleDescriptor.
|
|
|
|
static LLVMStructType getModuleDescriptorTy(MLIRContext *context) {
|
|
|
|
return LLVMStructType::getLiteral(
|
|
|
|
context, {
|
|
|
|
// std::int32_t numFuncDescriptors;
|
2021-01-09 06:05:16 +08:00
|
|
|
IntegerType::get(context, 32),
|
2021-01-06 08:12:11 +08:00
|
|
|
// FuncDescriptor *functionDescriptors;
|
|
|
|
LLVMPointerType::get(getFuncDescriptorTy(context)),
|
|
|
|
});
|
2020-07-11 08:50:55 +08:00
|
|
|
}
|
2020-07-11 08:31:24 +08:00
|
|
|
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Compiler runtime functions.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
template <typename T>
|
|
|
|
class TrivialCompilerRuntimeLowering : public OpConversionPattern<T> {
|
|
|
|
public:
|
|
|
|
TrivialCompilerRuntimeLowering(LLVM::LLVMFuncOp backingFunc)
|
|
|
|
: OpConversionPattern<T>(backingFunc.getContext()),
|
|
|
|
backingFunc(backingFunc) {}
|
|
|
|
LogicalResult
|
|
|
|
matchAndRewrite(T op, ArrayRef<Value> operands,
|
|
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
|
|
rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, backingFunc, operands);
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
LLVM::LLVMFuncOp backingFunc;
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2020-09-17 08:31:40 +08:00
|
|
|
static LLVM::GlobalOp createGlobalString(ModuleOp module, StringAttr msg,
|
|
|
|
OpBuilder &builder, Location loc) {
|
|
|
|
// TODO: Deduplicate strings.
|
2020-09-18 10:03:33 +08:00
|
|
|
std::string msgNulTerminated = msg.getValue().str();
|
|
|
|
msgNulTerminated.push_back('\0');
|
2021-03-11 01:53:03 +08:00
|
|
|
auto arrayTy = LLVMArrayType::get(IntegerType::get(module.getContext(), 8),
|
|
|
|
msgNulTerminated.size());
|
2020-09-17 08:31:40 +08:00
|
|
|
OpBuilder::InsertionGuard guard(builder);
|
|
|
|
builder.setInsertionPointToStart(module.getBody());
|
2020-09-18 10:03:33 +08:00
|
|
|
|
2020-09-17 08:31:40 +08:00
|
|
|
// To get a unique symbol name, use a suffix derived from the current number
|
|
|
|
// of ops in the module.
|
|
|
|
// We can't use the SymbolTable's logic for this because the module
|
|
|
|
// transiently contains a `func` and `llvm.func` with the same name during
|
|
|
|
// conversion, preventing us from instantiating a SymbolTable.
|
|
|
|
std::string symbolName =
|
|
|
|
(Twine("__npcomp_string_") +
|
|
|
|
Twine(llvm::size(llvm::to_vector<6>(module.getOps<LLVM::GlobalOp>()))))
|
|
|
|
.str();
|
2020-09-18 10:03:33 +08:00
|
|
|
auto globalOp = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, arrayTy, /*isConstant=*/true, LLVM::Linkage::Internal, symbolName,
|
|
|
|
builder.getStringAttr(msgNulTerminated));
|
2020-09-17 08:31:40 +08:00
|
|
|
return globalOp;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
class AbortIfOpCompilerRuntimeLowering
|
2020-10-08 08:12:52 +08:00
|
|
|
: public OpConversionPattern<refbackrt::AbortIfOp> {
|
2020-09-17 08:31:40 +08:00
|
|
|
public:
|
|
|
|
AbortIfOpCompilerRuntimeLowering(LLVM::LLVMFuncOp backingFunc)
|
2020-10-08 08:12:52 +08:00
|
|
|
: OpConversionPattern<refbackrt::AbortIfOp>(backingFunc.getContext()),
|
2020-09-17 08:31:40 +08:00
|
|
|
backingFunc(backingFunc) {}
|
|
|
|
LogicalResult
|
2020-10-08 08:12:52 +08:00
|
|
|
matchAndRewrite(refbackrt::AbortIfOp op, ArrayRef<Value> operands,
|
2020-09-17 08:31:40 +08:00
|
|
|
ConversionPatternRewriter &rewriter) const override {
|
2020-10-08 08:12:52 +08:00
|
|
|
refbackrt::AbortIfOp::Adaptor adaptor(operands);
|
2020-09-17 08:31:40 +08:00
|
|
|
auto *context = op.getContext();
|
|
|
|
|
|
|
|
// Create the global string, take its address, and gep to get an `i8*`.
|
2020-12-15 06:30:51 +08:00
|
|
|
auto globalOp = createGlobalString(op->getParentOfType<ModuleOp>(),
|
2020-09-17 08:31:40 +08:00
|
|
|
op.msgAttr(), rewriter, op.getLoc());
|
|
|
|
auto msgArray = rewriter.create<LLVM::AddressOfOp>(op.getLoc(), globalOp);
|
2021-03-11 01:53:03 +08:00
|
|
|
auto c0 = rewriter.create<LLVM::ConstantOp>(op.getLoc(),
|
|
|
|
IntegerType::get(context, 32),
|
|
|
|
rewriter.getI32IntegerAttr(0));
|
2021-01-06 08:12:11 +08:00
|
|
|
auto msg =
|
|
|
|
rewriter.create<LLVM::GEPOp>(op.getLoc(), getInt8PointerType(context),
|
|
|
|
msgArray, ValueRange({c0, c0}));
|
2020-09-17 08:31:40 +08:00
|
|
|
rewriter.replaceOpWithNewOp<LLVM::CallOp>(
|
|
|
|
op, backingFunc, ValueRange({adaptor.pred(), msg}));
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
LLVM::LLVMFuncOp backingFunc;
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2020-10-08 08:12:52 +08:00
|
|
|
// Create the LLVM runtime function backing the refbackrt op with name `name`
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// and requiring `type`.
|
2021-01-06 08:12:11 +08:00
|
|
|
static LLVMFuncOp createCompilerRuntimeFuncDecl(StringRef name, Type type,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
OpBuilder &builder,
|
|
|
|
Location loc) {
|
2021-01-06 08:12:11 +08:00
|
|
|
assert(type.isa<LLVMFunctionType>());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
std::string symbolName = (Twine("__npcomp_compiler_rt_") + name).str();
|
|
|
|
return builder.create<LLVM::LLVMFuncOp>(loc, symbolName, type,
|
|
|
|
LLVM::Linkage::External);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void populateCompilerRuntimePatterns(ModuleOp module,
|
2021-03-24 05:16:23 +08:00
|
|
|
RewritePatternSet &patterns,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
LLVMTypeConverter &typeConverter) {
|
2020-08-28 06:09:10 +08:00
|
|
|
auto *context = module.getContext();
|
2020-05-21 09:48:53 +08:00
|
|
|
OpBuilder builder(module.getBodyRegion());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
|
|
|
{
|
2021-01-06 08:12:11 +08:00
|
|
|
auto abortIfFuncTy = LLVMFunctionType::get(
|
|
|
|
LLVMVoidType::get(context),
|
2021-01-09 06:05:16 +08:00
|
|
|
{IntegerType::get(context, 1), getInt8PointerType(context)},
|
2020-09-17 08:31:40 +08:00
|
|
|
/*isVarArg=*/false);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
LLVMFuncOp abortIfFunc = createCompilerRuntimeFuncDecl(
|
|
|
|
"abort_if", abortIfFuncTy, builder, module.getLoc());
|
2021-03-24 05:16:23 +08:00
|
|
|
patterns.add<AbortIfOpCompilerRuntimeLowering>(abortIfFunc);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Lowering for module metadata
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
static LLVM::GlobalOp
|
2020-10-08 08:12:52 +08:00
|
|
|
createFuncDescriptorArray(ArrayRef<refbackrt::FuncMetadataOp> funcMetadatas,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
OpBuilder &builder, Location loc) {
|
2021-01-09 06:05:16 +08:00
|
|
|
auto llvmI32Ty = IntegerType::get(builder.getContext(), 32);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
|
|
|
DenseMap<StringRef, LLVM::GlobalOp> globalsByName;
|
2021-03-11 01:53:03 +08:00
|
|
|
DenseMap<StringRef, LLVM::GlobalOp> inputDescriptorsByName;
|
|
|
|
DenseMap<StringRef, LLVM::GlobalOp> outputDescriptorsByName;
|
|
|
|
DenseMap<StringRef, LLVM::GlobalOp> inputShapesByName;
|
|
|
|
DenseMap<StringRef, LLVM::GlobalOp> outputShapesByName;
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
for (auto funcMetadata : funcMetadatas) {
|
2021-03-11 01:53:03 +08:00
|
|
|
auto arrayTy = LLVMArrayType::get(IntegerType::get(builder.getContext(), 8),
|
|
|
|
funcMetadata.funcName().size());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
std::string llvmSymbolName =
|
|
|
|
(Twine("__npcomp_internal_constant_") + funcMetadata.funcName()).str();
|
|
|
|
auto global = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, arrayTy, /*isConstant=*/true, LLVM::Linkage::Internal,
|
|
|
|
llvmSymbolName, builder.getStringAttr(funcMetadata.funcName()));
|
|
|
|
globalsByName[funcMetadata.funcName()] = global;
|
2021-03-11 01:53:03 +08:00
|
|
|
|
|
|
|
// Create constants for the input / output shapes
|
|
|
|
if (funcMetadata.inputShapes().hasValue()) {
|
|
|
|
auto i32ArrayInputSymbolName =
|
|
|
|
(Twine("__npcomp_internal_constant_input_shapes_") +
|
|
|
|
funcMetadata.funcName())
|
|
|
|
.str();
|
|
|
|
auto inputNumElements = funcMetadata.inputShapes()->getNumElements();
|
|
|
|
auto inputI32ArrayTy =
|
|
|
|
LLVMArrayType::get(builder.getIntegerType(32), inputNumElements);
|
|
|
|
auto inputShapesGlobal = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, inputI32ArrayTy, /*isConstant=*/true, LLVM::Linkage::Internal,
|
|
|
|
i32ArrayInputSymbolName,
|
|
|
|
/*value=*/funcMetadata.inputShapes().getValue());
|
|
|
|
|
|
|
|
inputShapesByName[funcMetadata.funcName()] = inputShapesGlobal;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (funcMetadata.outputShapes().hasValue()) {
|
|
|
|
auto i32ArrayOutputSymbolName =
|
|
|
|
(Twine("__npcomp_internal_constant_output_shapes_") +
|
|
|
|
funcMetadata.funcName())
|
|
|
|
.str();
|
|
|
|
auto outputNumElements = funcMetadata.outputShapes()->getNumElements();
|
|
|
|
auto outputI32ArrayTy =
|
|
|
|
LLVMArrayType::get(builder.getIntegerType(32), outputNumElements);
|
|
|
|
auto outputShapesGlobal = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, outputI32ArrayTy, /*isConstant=*/true, LLVM::Linkage::Internal,
|
|
|
|
i32ArrayOutputSymbolName,
|
|
|
|
/*value=*/funcMetadata.outputShapes().getValue());
|
|
|
|
|
|
|
|
outputShapesByName[funcMetadata.funcName()] = outputShapesGlobal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto updateDescriptor = [&](Value &descriptor, Value value,
|
|
|
|
std::initializer_list<int32_t> position) {
|
|
|
|
descriptor = builder.create<LLVM::InsertValueOp>(
|
|
|
|
loc, descriptor, value,
|
|
|
|
/*position=*/builder.getI32ArrayAttr(position));
|
|
|
|
};
|
|
|
|
auto updateDescriptorWithI32Attr =
|
|
|
|
[&](Value &descriptor, Attribute attr,
|
|
|
|
std::initializer_list<int32_t> position) {
|
|
|
|
auto constant = builder.create<LLVM::ConstantOp>(loc, llvmI32Ty, attr);
|
|
|
|
updateDescriptor(descriptor, constant, position);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create global input descriptors
|
|
|
|
for (auto funcMetadata : funcMetadatas) {
|
|
|
|
std::string llvmInputSymbolName =
|
|
|
|
(Twine("__npcomp_input_descriptors_") + funcMetadata.funcName()).str();
|
|
|
|
auto inputDescriptorTy = getInputDescriptorTy(builder.getContext());
|
|
|
|
auto inputDescriptorArrayTy =
|
|
|
|
LLVMArrayType::get(inputDescriptorTy, funcMetadata.numInputs());
|
|
|
|
auto inputDescriptorArrayGlobal = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, inputDescriptorArrayTy, /*isConstant=*/true,
|
|
|
|
LLVM::Linkage::Internal, llvmInputSymbolName, /*value=*/Attribute());
|
|
|
|
|
|
|
|
OpBuilder::InsertionGuard guard(builder);
|
|
|
|
builder.createBlock(&inputDescriptorArrayGlobal.initializer());
|
|
|
|
|
|
|
|
auto c0 = builder.create<LLVM::ConstantOp>(loc, llvmI32Ty,
|
|
|
|
builder.getI32IntegerAttr(0));
|
|
|
|
|
|
|
|
Value inputDescriptorArray =
|
|
|
|
builder.create<LLVM::UndefOp>(loc, inputDescriptorArrayTy);
|
|
|
|
|
2021-03-24 05:28:39 +08:00
|
|
|
for (int i = 0, e = funcMetadata.numInputs(); i < e; i++) {
|
2021-03-11 01:53:03 +08:00
|
|
|
// Arg Type
|
|
|
|
if (!funcMetadata.inputArgTypes().hasValue())
|
|
|
|
funcMetadata.emitError()
|
|
|
|
<< "numInputs > 0 but there are no inputArgTypes?";
|
|
|
|
updateDescriptorWithI32Attr(inputDescriptorArray,
|
|
|
|
funcMetadata.inputArgTypes()->getValue(i),
|
|
|
|
{i, 0});
|
|
|
|
// Element Type
|
|
|
|
updateDescriptorWithI32Attr(inputDescriptorArray,
|
|
|
|
funcMetadata.inputElementTypes()->getValue(i),
|
|
|
|
{i, 1});
|
|
|
|
|
|
|
|
// Rank
|
|
|
|
// auto inputShapesType =
|
|
|
|
// funcMetadata.inputShapes()->getType().dyn_cast<ShapedType>();
|
|
|
|
auto rank = funcMetadata.inputRanks()->getValue(i);
|
|
|
|
updateDescriptorWithI32Attr(inputDescriptorArray, rank, {i, 2});
|
|
|
|
|
|
|
|
// Shape
|
|
|
|
// Each shape array is derived by offseting of kMaxRank * arg index
|
|
|
|
auto extentsArray = builder.create<LLVM::AddressOfOp>(
|
|
|
|
loc, inputShapesByName[funcMetadata.funcName()]);
|
|
|
|
auto cShapeOffset = builder.create<LLVM::ConstantOp>(
|
|
|
|
loc, IntegerType::get(builder.getContext(), 32),
|
|
|
|
builder.getI32IntegerAttr(i * kMaxRank));
|
|
|
|
auto extentsArrayPtr = builder.create<LLVM::GEPOp>(
|
|
|
|
loc, getInt32PointerType(builder.getContext()), extentsArray,
|
|
|
|
ValueRange({c0, cShapeOffset}));
|
|
|
|
updateDescriptor(inputDescriptorArray, extentsArrayPtr, {i, 3});
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.create<LLVM::ReturnOp>(loc, inputDescriptorArray);
|
|
|
|
|
|
|
|
inputDescriptorsByName[funcMetadata.funcName()] =
|
|
|
|
std::move(inputDescriptorArrayGlobal);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create global output descriptors
|
|
|
|
for (auto funcMetadata : funcMetadatas) {
|
|
|
|
std::string llvmOutputSymbolName =
|
|
|
|
(Twine("__npcomp_output_descriptors_") + funcMetadata.funcName()).str();
|
|
|
|
auto outputDescriptorTy = getOutputDescriptorTy(builder.getContext());
|
|
|
|
auto outputDescriptorArrayTy =
|
|
|
|
LLVMArrayType::get(outputDescriptorTy, funcMetadata.numOutputs());
|
|
|
|
auto outputDescriptorArrayGlobal = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, outputDescriptorArrayTy, /*isConstant=*/true,
|
|
|
|
LLVM::Linkage::Internal, llvmOutputSymbolName, /*value=*/Attribute());
|
|
|
|
|
|
|
|
OpBuilder::InsertionGuard guard(builder);
|
|
|
|
builder.createBlock(&outputDescriptorArrayGlobal.initializer());
|
|
|
|
|
|
|
|
auto c0 = builder.create<LLVM::ConstantOp>(loc, llvmI32Ty,
|
|
|
|
builder.getI32IntegerAttr(0));
|
|
|
|
|
|
|
|
Value outputDescriptorArray =
|
|
|
|
builder.create<LLVM::UndefOp>(loc, outputDescriptorArrayTy);
|
|
|
|
|
2021-03-24 05:28:39 +08:00
|
|
|
for (int i = 0, e = funcMetadata.numOutputs(); i < e; i++) {
|
2021-03-11 01:53:03 +08:00
|
|
|
if (!funcMetadata.outputArgTypes().hasValue())
|
|
|
|
funcMetadata.emitError()
|
|
|
|
<< "numOutputs > 0 but there are no outputArgTypes?";
|
|
|
|
// Arg Type
|
|
|
|
updateDescriptorWithI32Attr(outputDescriptorArray,
|
|
|
|
funcMetadata.outputArgTypes()->getValue(i),
|
|
|
|
{i, 0});
|
|
|
|
// Element Type
|
|
|
|
updateDescriptorWithI32Attr(
|
|
|
|
outputDescriptorArray, funcMetadata.outputElementTypes()->getValue(i),
|
|
|
|
{i, 1});
|
|
|
|
|
|
|
|
// Rank
|
|
|
|
// auto outputShapesType =
|
|
|
|
// funcMetadata.outputShapes()->getType().dyn_cast<ShapedType>();
|
|
|
|
auto rank = funcMetadata.outputRanks()->getValue(i);
|
|
|
|
updateDescriptorWithI32Attr(outputDescriptorArray, rank, {i, 2});
|
|
|
|
|
|
|
|
// Shapes
|
|
|
|
// Offset by kMaxRank * arg index
|
|
|
|
auto extentsArray = builder.create<LLVM::AddressOfOp>(
|
|
|
|
loc, outputShapesByName[funcMetadata.funcName()]);
|
|
|
|
auto cShapeOffset = builder.create<LLVM::ConstantOp>(
|
|
|
|
loc, IntegerType::get(builder.getContext(), 32),
|
|
|
|
builder.getI32IntegerAttr(i * kMaxRank));
|
|
|
|
auto extentsArrayPtr = builder.create<LLVM::GEPOp>(
|
|
|
|
loc, getInt32PointerType(builder.getContext()), extentsArray,
|
|
|
|
ValueRange({c0, cShapeOffset}));
|
|
|
|
updateDescriptor(outputDescriptorArray, extentsArrayPtr, {i, 3});
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.create<LLVM::ReturnOp>(loc, outputDescriptorArray);
|
|
|
|
|
|
|
|
outputDescriptorsByName[funcMetadata.funcName()] =
|
|
|
|
outputDescriptorArrayGlobal;
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// This must match FuncDescriptor in the runtime.
|
2020-08-28 06:09:10 +08:00
|
|
|
auto funcDescriptorTy = getFuncDescriptorTy(builder.getContext());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
auto funcDescriptorArrayTy =
|
2021-01-06 08:12:11 +08:00
|
|
|
LLVMArrayType::get(funcDescriptorTy, funcMetadatas.size());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
auto funcDescriptorArrayGlobal = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, funcDescriptorArrayTy, /*isConstant=*/true, LLVM::Linkage::Internal,
|
|
|
|
"__npcomp_func_descriptors",
|
|
|
|
/*value=*/Attribute());
|
2021-03-11 01:53:03 +08:00
|
|
|
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
OpBuilder::InsertionGuard guard(builder);
|
|
|
|
builder.createBlock(&funcDescriptorArrayGlobal.initializer());
|
|
|
|
|
2021-03-11 01:53:03 +08:00
|
|
|
auto c0 = builder.create<LLVM::ConstantOp>(loc, llvmI32Ty,
|
|
|
|
builder.getI32IntegerAttr(0));
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// Build the initializer.
|
|
|
|
Value funcDescriptorArray =
|
|
|
|
builder.create<LLVM::UndefOp>(loc, funcDescriptorArrayTy);
|
2021-03-11 01:53:03 +08:00
|
|
|
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
for (auto funcMetadataAndIndex : llvm::enumerate(funcMetadatas)) {
|
|
|
|
auto funcMetadata = funcMetadataAndIndex.value();
|
|
|
|
int32_t index = funcMetadataAndIndex.index();
|
|
|
|
|
|
|
|
// Name length.
|
|
|
|
updateDescriptorWithI32Attr(
|
2021-03-11 01:53:03 +08:00
|
|
|
funcDescriptorArray,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
builder.getI32IntegerAttr(funcMetadata.funcName().size()), {index, 0});
|
|
|
|
|
|
|
|
// Name chars.
|
|
|
|
auto funcNameArray = builder.create<LLVM::AddressOfOp>(
|
|
|
|
loc, globalsByName[funcMetadata.funcName()]);
|
2020-08-28 06:09:10 +08:00
|
|
|
auto funcNamePtr = builder.create<LLVM::GEPOp>(
|
2021-01-06 08:12:11 +08:00
|
|
|
loc, getInt8PointerType(builder.getContext()), funcNameArray,
|
2020-08-28 06:09:10 +08:00
|
|
|
ValueRange({c0, c0}));
|
2021-03-11 01:53:03 +08:00
|
|
|
updateDescriptor(funcDescriptorArray, funcNamePtr, {index, 1});
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
|
|
|
// Function pointer.
|
|
|
|
//
|
|
|
|
// We create this reference to the original function (and use a dummy i8*
|
|
|
|
// type). We will fix this up after conversion to point at wrapper
|
|
|
|
// functions that satisfy the ABI requirements.
|
|
|
|
// The bitcast is required so that after conversion the inserted value is an
|
|
|
|
// i8* as expected by the descriptor struct.
|
|
|
|
auto funcAddress = builder.create<LLVM::AddressOfOp>(
|
2021-01-06 08:12:11 +08:00
|
|
|
loc, getInt8PointerType(builder.getContext()), funcMetadata.funcName());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
auto typeErasedFuncAddress = builder.create<LLVM::BitcastOp>(
|
2021-01-06 08:12:11 +08:00
|
|
|
loc, getInt8PointerType(builder.getContext()), funcAddress);
|
2021-03-11 01:53:03 +08:00
|
|
|
updateDescriptor(funcDescriptorArray, typeErasedFuncAddress, {index, 2});
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
|
|
|
// Number of inputs.
|
2021-03-11 01:53:03 +08:00
|
|
|
updateDescriptorWithI32Attr(funcDescriptorArray,
|
|
|
|
funcMetadata.numInputsAttr(), {index, 3});
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
|
|
|
// Number of outputs.
|
2021-03-11 01:53:03 +08:00
|
|
|
updateDescriptorWithI32Attr(funcDescriptorArray,
|
|
|
|
funcMetadata.numOutputsAttr(), {index, 4});
|
|
|
|
|
|
|
|
// Input descriptors
|
|
|
|
auto inputDescriptorsArrayAddress = builder.create<LLVM::AddressOfOp>(
|
|
|
|
loc, inputDescriptorsByName[funcMetadata.funcName()]);
|
|
|
|
auto rawInputDescriptorsPtr = builder.create<LLVM::BitcastOp>(
|
|
|
|
loc, LLVMPointerType::get(getInputDescriptorTy(builder.getContext())),
|
|
|
|
inputDescriptorsArrayAddress);
|
|
|
|
updateDescriptor(funcDescriptorArray, rawInputDescriptorsPtr, {index, 5});
|
|
|
|
|
|
|
|
// Output descriptors
|
|
|
|
auto outputDescriptorsArrayAddress = builder.create<LLVM::AddressOfOp>(
|
|
|
|
loc, outputDescriptorsByName[funcMetadata.funcName()]);
|
|
|
|
auto rawOutputDescriptorsPtr = builder.create<LLVM::BitcastOp>(
|
|
|
|
loc, LLVMPointerType::get(getOutputDescriptorTy(builder.getContext())),
|
|
|
|
outputDescriptorsArrayAddress);
|
|
|
|
updateDescriptor(funcDescriptorArray, rawOutputDescriptorsPtr, {index, 6});
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
builder.create<LLVM::ReturnOp>(loc, funcDescriptorArray);
|
|
|
|
|
|
|
|
return funcDescriptorArrayGlobal;
|
|
|
|
}
|
|
|
|
|
|
|
|
LLVM::GlobalOp createModuleDescriptor(LLVM::GlobalOp funcDescriptorArray,
|
|
|
|
OpBuilder &builder, Location loc) {
|
2021-01-09 06:05:16 +08:00
|
|
|
auto llvmI32Ty = IntegerType::get(builder.getContext(), 32);
|
2020-08-28 06:09:10 +08:00
|
|
|
auto moduleDescriptorTy = getModuleDescriptorTy(builder.getContext());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// TODO: Ideally this symbol name would somehow be related to the module
|
|
|
|
// name, if we could consistently assume we had one.
|
|
|
|
// TODO: We prepend _mlir so that mlir::ExecutionEngine's lookup logic (which
|
|
|
|
// is typically only mean for function pointers) will find this raw symbol.
|
|
|
|
auto moduleDescriptorGlobal = builder.create<LLVM::GlobalOp>(
|
|
|
|
loc, moduleDescriptorTy, /*isConstant=*/true, LLVM::Linkage::External,
|
|
|
|
"_mlir___npcomp_module_descriptor",
|
|
|
|
/*value=*/Attribute());
|
|
|
|
OpBuilder::InsertionGuard guard(builder);
|
|
|
|
builder.createBlock(&moduleDescriptorGlobal.initializer());
|
|
|
|
|
|
|
|
Value moduleDescriptor =
|
|
|
|
builder.create<LLVM::UndefOp>(loc, moduleDescriptorTy);
|
|
|
|
|
|
|
|
auto updateDescriptor = [&](Value value,
|
|
|
|
std::initializer_list<int32_t> position) {
|
|
|
|
moduleDescriptor = builder.create<LLVM::InsertValueOp>(
|
|
|
|
loc, moduleDescriptor, value,
|
|
|
|
/*position=*/builder.getI32ArrayAttr(position));
|
|
|
|
};
|
|
|
|
|
2021-01-06 08:12:11 +08:00
|
|
|
updateDescriptor(builder.create<LLVM::ConstantOp>(
|
|
|
|
loc, llvmI32Ty,
|
|
|
|
builder.getI32IntegerAttr(funcDescriptorArray.getType()
|
|
|
|
.cast<LLVMArrayType>()
|
|
|
|
.getNumElements())),
|
|
|
|
{0});
|
2020-07-11 08:50:55 +08:00
|
|
|
|
|
|
|
auto funcDecriptorArrayAddress =
|
|
|
|
builder.create<LLVM::AddressOfOp>(loc, funcDescriptorArray);
|
|
|
|
auto rawFuncDescriptorPtr = builder.create<LLVM::BitcastOp>(
|
2021-01-06 08:12:11 +08:00
|
|
|
loc, LLVMPointerType::get(getFuncDescriptorTy(builder.getContext())),
|
2020-07-11 08:50:55 +08:00
|
|
|
funcDecriptorArrayAddress);
|
|
|
|
updateDescriptor(rawFuncDescriptorPtr, {1});
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
builder.create<LLVM::ReturnOp>(loc, moduleDescriptor);
|
|
|
|
|
|
|
|
return moduleDescriptorGlobal;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
class LowerModuleMetadata
|
2020-10-08 08:12:52 +08:00
|
|
|
: public OpConversionPattern<refbackrt::ModuleMetadataOp> {
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
public:
|
|
|
|
using OpConversionPattern::OpConversionPattern;
|
|
|
|
LogicalResult
|
2020-10-08 08:12:52 +08:00
|
|
|
matchAndRewrite(refbackrt::ModuleMetadataOp op, ArrayRef<Value> operands,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
ConversionPatternRewriter &rewriter) const override {
|
|
|
|
auto funcMetadatas =
|
2020-10-08 08:12:52 +08:00
|
|
|
llvm::to_vector<6>(op.metadatas().getOps<refbackrt::FuncMetadataOp>());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
auto funcDescriptorArray =
|
|
|
|
createFuncDescriptorArray(funcMetadatas, rewriter, op.getLoc());
|
|
|
|
auto moduleDescriptor =
|
|
|
|
createModuleDescriptor(funcDescriptorArray, rewriter, op.getLoc());
|
|
|
|
|
|
|
|
// TODO: create get module descriptor wrapper (or upgrade
|
|
|
|
// mlir::ExecutionEngine to allow raw symbol lookup)
|
|
|
|
(void)moduleDescriptor;
|
|
|
|
|
|
|
|
rewriter.eraseOp(op);
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
// Performs the calculation:
|
|
|
|
// ```
|
|
|
|
// ty *f(void **voidStarStar, int32_t i) {
|
|
|
|
// return reinterpret_cast<ty *>(voidStarStar[i]);
|
|
|
|
// }
|
|
|
|
// ```
|
|
|
|
static Value getTypedAddressFromVoidStarStar(Value voidStarStar, int32_t index,
|
2021-01-06 08:12:11 +08:00
|
|
|
Type ty, OpBuilder &builder,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
Location loc) {
|
|
|
|
Value ci = builder.create<LLVM::ConstantOp>(
|
2021-01-09 06:05:16 +08:00
|
|
|
loc, IntegerType::get(builder.getContext(), 32),
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
builder.getI32IntegerAttr(index));
|
[RefBackend] Fix leaks related to ABI boundaries.
Best as I can tell (e.g. from LeakSanitizer), this fixes all the leaks
except for those due to buffers created internally to the codegenned
code itself (up next I'll add the buffer deallocation pass to fix
those).
The main change is that instead of attempting to pass `refbackrt::Tensor`
to the codegenned function directly, we make all the ABI types be
UnrankedMemRef which gets passed awkwardly (but workably) as a
`{size_t rank, void *ptrToDescriptor}` on the ABI. The reason why
refbackrt::Tensor wasn't workable is that is that MLIR doesn't really
have a way to deal with the lifetime of unranked memref descriptors that
happen inside the function, which is inevitably what would happen in the
old code that would emit runtime calls to
`refbackrt.to_memref/refbackrt.from_memref` to convert back and forth to
`refbackrt::Tensor` inside the codegenned code.
So, instead of the `refbackrt.to_memref/refbackrt.from_memref` with no
real sound basis for valid lifetime management, we now have a lovely
piece of code in `refbackrt::invoke` in `Runtime.cpp` that just barely
seems to be sound. We rely on the codegenned code having these
properties, which it seems to have:
- it won't free memref descriptors or their backing buffer for arguments
of UnrankedMemRef type.
- it will allocate a separate memref descriptor for each result
UnrankedMemRef (which is ensured by having a separate memref_cast for
each)
- we can sniff the `allocatedPtr`'s (i.e. the backing buffer pointers)
to avoid double-freeing in the case of aliasing of the backing buffer
(including backing buffers for arguments feeding into results)
- to catch the case of statically allocated data (which we need to avoid
passing to `free`) , check if the `allocatedPtr` is (no joke) equal to
`0xDEADBEEF`, because there is otherwise no way to distinguish
statically allocated from malloc'ed data... (std.global_memref lowering
to LLVM by happenstance sets the allocatedPtr equal to `0xDEADBEEF`,
presumably mainly as a debugging thing)
Even with all this, we *still* need to (internally to refbackrt::invoke)
make copies of all inputs/outputs! And the details of how the LLVM-level
ABI gets laid out for e.g. function arguments/returns is still super
tricky.
This really highlights how deficient memref is as the general runtime
type for our use case. It's stewing in my mind how best to improve the
situation. My general gut feeling is that IREE's abstractions for this
are "right", but I need to think more how to distill those aspects of
IREE's design in a "reference" way for RefBackend.
Some implementation notes:
- In terms of how this is implemented, this did catch a bug in our ABI
wrapper functions in LowerToLLVM.cpp, which I had to fix (it happened to
work before through some combination of npcomprt::Tensor being passed as
a single pointer + probably me infinite-monkey-ing it until it worked)
- This actually removes 2 out of the 3 compiler runtime functions (the
only one left is "abort_if". (most of the memref descriptor code moved
from CopmilerRuntime.cpp to Runtime.cpp)
- this also means deleting `refbackrt.from_memref` and
`refbackrt.to_memref`
2020-11-25 09:18:57 +08:00
|
|
|
|
|
|
|
// Do `voidStarStar[i]` as a gep + load.
|
|
|
|
auto inputPtrAddr = builder.create<LLVM::GEPOp>(
|
2021-01-06 08:12:11 +08:00
|
|
|
loc, LLVMPointerType::get(getInt8PointerType(builder.getContext())),
|
[RefBackend] Fix leaks related to ABI boundaries.
Best as I can tell (e.g. from LeakSanitizer), this fixes all the leaks
except for those due to buffers created internally to the codegenned
code itself (up next I'll add the buffer deallocation pass to fix
those).
The main change is that instead of attempting to pass `refbackrt::Tensor`
to the codegenned function directly, we make all the ABI types be
UnrankedMemRef which gets passed awkwardly (but workably) as a
`{size_t rank, void *ptrToDescriptor}` on the ABI. The reason why
refbackrt::Tensor wasn't workable is that is that MLIR doesn't really
have a way to deal with the lifetime of unranked memref descriptors that
happen inside the function, which is inevitably what would happen in the
old code that would emit runtime calls to
`refbackrt.to_memref/refbackrt.from_memref` to convert back and forth to
`refbackrt::Tensor` inside the codegenned code.
So, instead of the `refbackrt.to_memref/refbackrt.from_memref` with no
real sound basis for valid lifetime management, we now have a lovely
piece of code in `refbackrt::invoke` in `Runtime.cpp` that just barely
seems to be sound. We rely on the codegenned code having these
properties, which it seems to have:
- it won't free memref descriptors or their backing buffer for arguments
of UnrankedMemRef type.
- it will allocate a separate memref descriptor for each result
UnrankedMemRef (which is ensured by having a separate memref_cast for
each)
- we can sniff the `allocatedPtr`'s (i.e. the backing buffer pointers)
to avoid double-freeing in the case of aliasing of the backing buffer
(including backing buffers for arguments feeding into results)
- to catch the case of statically allocated data (which we need to avoid
passing to `free`) , check if the `allocatedPtr` is (no joke) equal to
`0xDEADBEEF`, because there is otherwise no way to distinguish
statically allocated from malloc'ed data... (std.global_memref lowering
to LLVM by happenstance sets the allocatedPtr equal to `0xDEADBEEF`,
presumably mainly as a debugging thing)
Even with all this, we *still* need to (internally to refbackrt::invoke)
make copies of all inputs/outputs! And the details of how the LLVM-level
ABI gets laid out for e.g. function arguments/returns is still super
tricky.
This really highlights how deficient memref is as the general runtime
type for our use case. It's stewing in my mind how best to improve the
situation. My general gut feeling is that IREE's abstractions for this
are "right", but I need to think more how to distill those aspects of
IREE's design in a "reference" way for RefBackend.
Some implementation notes:
- In terms of how this is implemented, this did catch a bug in our ABI
wrapper functions in LowerToLLVM.cpp, which I had to fix (it happened to
work before through some combination of npcomprt::Tensor being passed as
a single pointer + probably me infinite-monkey-ing it until it worked)
- This actually removes 2 out of the 3 compiler runtime functions (the
only one left is "abort_if". (most of the memref descriptor code moved
from CopmilerRuntime.cpp to Runtime.cpp)
- this also means deleting `refbackrt.from_memref` and
`refbackrt.to_memref`
2020-11-25 09:18:57 +08:00
|
|
|
voidStarStar, ValueRange(ci));
|
|
|
|
auto inputPtr = builder.create<LLVM::LoadOp>(loc, inputPtrAddr);
|
2021-01-06 08:12:11 +08:00
|
|
|
return builder.create<LLVM::BitcastOp>(loc, LLVMPointerType::get(ty),
|
|
|
|
inputPtr);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
}
|
|
|
|
|
2021-01-06 08:12:11 +08:00
|
|
|
static SmallVector<Value, 6> loadCallArgs(Value inputsPtrPtr,
|
|
|
|
LLVMFunctionType funcTy,
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
OpBuilder &builder, Location loc) {
|
|
|
|
SmallVector<Value, 6> callArgs;
|
|
|
|
// For each void* in the void**, cast it to the right type and load it.
|
2021-01-06 08:12:11 +08:00
|
|
|
for (int i = 0, e = funcTy.getNumParams(); i < e; i++) {
|
|
|
|
auto paramTy = funcTy.getParamType(i);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
auto addr =
|
|
|
|
getTypedAddressFromVoidStarStar(inputsPtrPtr, i, paramTy, builder, loc);
|
|
|
|
callArgs.push_back(builder.create<LLVM::LoadOp>(loc, addr));
|
|
|
|
}
|
|
|
|
return callArgs;
|
|
|
|
}
|
|
|
|
|
2021-01-06 08:12:11 +08:00
|
|
|
static Type getUnrankedMemrefDescriptorType(MLIRContext *context) {
|
[RefBackend] Fix leaks related to ABI boundaries.
Best as I can tell (e.g. from LeakSanitizer), this fixes all the leaks
except for those due to buffers created internally to the codegenned
code itself (up next I'll add the buffer deallocation pass to fix
those).
The main change is that instead of attempting to pass `refbackrt::Tensor`
to the codegenned function directly, we make all the ABI types be
UnrankedMemRef which gets passed awkwardly (but workably) as a
`{size_t rank, void *ptrToDescriptor}` on the ABI. The reason why
refbackrt::Tensor wasn't workable is that is that MLIR doesn't really
have a way to deal with the lifetime of unranked memref descriptors that
happen inside the function, which is inevitably what would happen in the
old code that would emit runtime calls to
`refbackrt.to_memref/refbackrt.from_memref` to convert back and forth to
`refbackrt::Tensor` inside the codegenned code.
So, instead of the `refbackrt.to_memref/refbackrt.from_memref` with no
real sound basis for valid lifetime management, we now have a lovely
piece of code in `refbackrt::invoke` in `Runtime.cpp` that just barely
seems to be sound. We rely on the codegenned code having these
properties, which it seems to have:
- it won't free memref descriptors or their backing buffer for arguments
of UnrankedMemRef type.
- it will allocate a separate memref descriptor for each result
UnrankedMemRef (which is ensured by having a separate memref_cast for
each)
- we can sniff the `allocatedPtr`'s (i.e. the backing buffer pointers)
to avoid double-freeing in the case of aliasing of the backing buffer
(including backing buffers for arguments feeding into results)
- to catch the case of statically allocated data (which we need to avoid
passing to `free`) , check if the `allocatedPtr` is (no joke) equal to
`0xDEADBEEF`, because there is otherwise no way to distinguish
statically allocated from malloc'ed data... (std.global_memref lowering
to LLVM by happenstance sets the allocatedPtr equal to `0xDEADBEEF`,
presumably mainly as a debugging thing)
Even with all this, we *still* need to (internally to refbackrt::invoke)
make copies of all inputs/outputs! And the details of how the LLVM-level
ABI gets laid out for e.g. function arguments/returns is still super
tricky.
This really highlights how deficient memref is as the general runtime
type for our use case. It's stewing in my mind how best to improve the
situation. My general gut feeling is that IREE's abstractions for this
are "right", but I need to think more how to distill those aspects of
IREE's design in a "reference" way for RefBackend.
Some implementation notes:
- In terms of how this is implemented, this did catch a bug in our ABI
wrapper functions in LowerToLLVM.cpp, which I had to fix (it happened to
work before through some combination of npcomprt::Tensor being passed as
a single pointer + probably me infinite-monkey-ing it until it worked)
- This actually removes 2 out of the 3 compiler runtime functions (the
only one left is "abort_if". (most of the memref descriptor code moved
from CopmilerRuntime.cpp to Runtime.cpp)
- this also means deleting `refbackrt.from_memref` and
`refbackrt.to_memref`
2020-11-25 09:18:57 +08:00
|
|
|
LLVMTypeConverter converter(context);
|
|
|
|
// LLVMTypeConverter doesn't directly expose the struct type used to represent
|
|
|
|
// unranked memrefs on ABI boundaries. To get that type, we convert
|
|
|
|
// an unranked memref type and see what it produces.
|
|
|
|
//
|
|
|
|
// An unranked memref is just a size_t for the rank and an void* pointer to
|
|
|
|
// descriptor, so the choice of element type here is arbitrary -- it all
|
|
|
|
// converts to the same thing.
|
2021-01-06 08:12:11 +08:00
|
|
|
return converter.convertType(
|
|
|
|
UnrankedMemRefType::get(Float32Type::get(context),
|
|
|
|
/*memorySpace=*/0));
|
[RefBackend] Fix leaks related to ABI boundaries.
Best as I can tell (e.g. from LeakSanitizer), this fixes all the leaks
except for those due to buffers created internally to the codegenned
code itself (up next I'll add the buffer deallocation pass to fix
those).
The main change is that instead of attempting to pass `refbackrt::Tensor`
to the codegenned function directly, we make all the ABI types be
UnrankedMemRef which gets passed awkwardly (but workably) as a
`{size_t rank, void *ptrToDescriptor}` on the ABI. The reason why
refbackrt::Tensor wasn't workable is that is that MLIR doesn't really
have a way to deal with the lifetime of unranked memref descriptors that
happen inside the function, which is inevitably what would happen in the
old code that would emit runtime calls to
`refbackrt.to_memref/refbackrt.from_memref` to convert back and forth to
`refbackrt::Tensor` inside the codegenned code.
So, instead of the `refbackrt.to_memref/refbackrt.from_memref` with no
real sound basis for valid lifetime management, we now have a lovely
piece of code in `refbackrt::invoke` in `Runtime.cpp` that just barely
seems to be sound. We rely on the codegenned code having these
properties, which it seems to have:
- it won't free memref descriptors or their backing buffer for arguments
of UnrankedMemRef type.
- it will allocate a separate memref descriptor for each result
UnrankedMemRef (which is ensured by having a separate memref_cast for
each)
- we can sniff the `allocatedPtr`'s (i.e. the backing buffer pointers)
to avoid double-freeing in the case of aliasing of the backing buffer
(including backing buffers for arguments feeding into results)
- to catch the case of statically allocated data (which we need to avoid
passing to `free`) , check if the `allocatedPtr` is (no joke) equal to
`0xDEADBEEF`, because there is otherwise no way to distinguish
statically allocated from malloc'ed data... (std.global_memref lowering
to LLVM by happenstance sets the allocatedPtr equal to `0xDEADBEEF`,
presumably mainly as a debugging thing)
Even with all this, we *still* need to (internally to refbackrt::invoke)
make copies of all inputs/outputs! And the details of how the LLVM-level
ABI gets laid out for e.g. function arguments/returns is still super
tricky.
This really highlights how deficient memref is as the general runtime
type for our use case. It's stewing in my mind how best to improve the
situation. My general gut feeling is that IREE's abstractions for this
are "right", but I need to think more how to distill those aspects of
IREE's design in a "reference" way for RefBackend.
Some implementation notes:
- In terms of how this is implemented, this did catch a bug in our ABI
wrapper functions in LowerToLLVM.cpp, which I had to fix (it happened to
work before through some combination of npcomprt::Tensor being passed as
a single pointer + probably me infinite-monkey-ing it until it worked)
- This actually removes 2 out of the 3 compiler runtime functions (the
only one left is "abort_if". (most of the memref descriptor code moved
from CopmilerRuntime.cpp to Runtime.cpp)
- this also means deleting `refbackrt.from_memref` and
`refbackrt.to_memref`
2020-11-25 09:18:57 +08:00
|
|
|
}
|
|
|
|
|
2021-03-11 01:53:03 +08:00
|
|
|
static Type getFloatType(MLIRContext *context) {
|
|
|
|
LLVMTypeConverter converter(context);
|
|
|
|
return converter.convertType(FloatType::getF32(context));
|
|
|
|
}
|
|
|
|
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// Writes out the logical results of the wrapper function through the void**
|
|
|
|
// passed on the ABI boundary. Because LLVM (and hence llvm.func)
|
|
|
|
// only supports a single return type (or void/no results), the logic here needs
|
|
|
|
// to be aware of the convention used in the Std to LLVM conversion to map
|
|
|
|
// multiple return types. The details of this are in the function
|
|
|
|
// packFunctionResults and its callers:
|
|
|
|
// https://github.com/llvm/llvm-project/blob/fad9cba8f58ba9979f390a49cf174ec9fcec29a6/mlir/lib/Conversion/StandardToLLVM/StandardToLLVM.cpp#L282
|
|
|
|
static void storeWrapperResults(LLVM::CallOp callToWrapped, Value resultsPtrPtr,
|
|
|
|
OpBuilder &builder, Location loc) {
|
|
|
|
// 0 results. Nothing to do.
|
|
|
|
if (callToWrapped.getNumResults() == 0)
|
|
|
|
return;
|
|
|
|
Value result = callToWrapped.getResult(0);
|
2021-01-06 08:12:11 +08:00
|
|
|
auto ty = result.getType();
|
2021-03-11 01:53:03 +08:00
|
|
|
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// 1 logical result.
|
[RefBackend] Fix leaks related to ABI boundaries.
Best as I can tell (e.g. from LeakSanitizer), this fixes all the leaks
except for those due to buffers created internally to the codegenned
code itself (up next I'll add the buffer deallocation pass to fix
those).
The main change is that instead of attempting to pass `refbackrt::Tensor`
to the codegenned function directly, we make all the ABI types be
UnrankedMemRef which gets passed awkwardly (but workably) as a
`{size_t rank, void *ptrToDescriptor}` on the ABI. The reason why
refbackrt::Tensor wasn't workable is that is that MLIR doesn't really
have a way to deal with the lifetime of unranked memref descriptors that
happen inside the function, which is inevitably what would happen in the
old code that would emit runtime calls to
`refbackrt.to_memref/refbackrt.from_memref` to convert back and forth to
`refbackrt::Tensor` inside the codegenned code.
So, instead of the `refbackrt.to_memref/refbackrt.from_memref` with no
real sound basis for valid lifetime management, we now have a lovely
piece of code in `refbackrt::invoke` in `Runtime.cpp` that just barely
seems to be sound. We rely on the codegenned code having these
properties, which it seems to have:
- it won't free memref descriptors or their backing buffer for arguments
of UnrankedMemRef type.
- it will allocate a separate memref descriptor for each result
UnrankedMemRef (which is ensured by having a separate memref_cast for
each)
- we can sniff the `allocatedPtr`'s (i.e. the backing buffer pointers)
to avoid double-freeing in the case of aliasing of the backing buffer
(including backing buffers for arguments feeding into results)
- to catch the case of statically allocated data (which we need to avoid
passing to `free`) , check if the `allocatedPtr` is (no joke) equal to
`0xDEADBEEF`, because there is otherwise no way to distinguish
statically allocated from malloc'ed data... (std.global_memref lowering
to LLVM by happenstance sets the allocatedPtr equal to `0xDEADBEEF`,
presumably mainly as a debugging thing)
Even with all this, we *still* need to (internally to refbackrt::invoke)
make copies of all inputs/outputs! And the details of how the LLVM-level
ABI gets laid out for e.g. function arguments/returns is still super
tricky.
This really highlights how deficient memref is as the general runtime
type for our use case. It's stewing in my mind how best to improve the
situation. My general gut feeling is that IREE's abstractions for this
are "right", but I need to think more how to distill those aspects of
IREE's design in a "reference" way for RefBackend.
Some implementation notes:
- In terms of how this is implemented, this did catch a bug in our ABI
wrapper functions in LowerToLLVM.cpp, which I had to fix (it happened to
work before through some combination of npcomprt::Tensor being passed as
a single pointer + probably me infinite-monkey-ing it until it worked)
- This actually removes 2 out of the 3 compiler runtime functions (the
only one left is "abort_if". (most of the memref descriptor code moved
from CopmilerRuntime.cpp to Runtime.cpp)
- this also means deleting `refbackrt.from_memref` and
`refbackrt.to_memref`
2020-11-25 09:18:57 +08:00
|
|
|
if (ty == getUnrankedMemrefDescriptorType(ty.getContext())) {
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
Value addr =
|
|
|
|
getTypedAddressFromVoidStarStar(resultsPtrPtr, 0, ty, builder, loc);
|
|
|
|
builder.create<LLVM::StoreOp>(loc, result, addr);
|
|
|
|
return;
|
2021-03-11 01:53:03 +08:00
|
|
|
} else if (ty == getFloatType(ty.getContext())) {
|
|
|
|
Value addr =
|
|
|
|
getTypedAddressFromVoidStarStar(resultsPtrPtr, 0, ty, builder, loc);
|
|
|
|
builder.create<LLVM::StoreOp>(loc, result, addr);
|
|
|
|
return;
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
}
|
2021-01-06 08:12:11 +08:00
|
|
|
assert(ty.isa<LLVMStructType>() && "must be a multi-result packed struct!");
|
|
|
|
auto structType = ty.cast<LLVMStructType>();
|
[RefBackend] Fix leaks related to ABI boundaries.
Best as I can tell (e.g. from LeakSanitizer), this fixes all the leaks
except for those due to buffers created internally to the codegenned
code itself (up next I'll add the buffer deallocation pass to fix
those).
The main change is that instead of attempting to pass `refbackrt::Tensor`
to the codegenned function directly, we make all the ABI types be
UnrankedMemRef which gets passed awkwardly (but workably) as a
`{size_t rank, void *ptrToDescriptor}` on the ABI. The reason why
refbackrt::Tensor wasn't workable is that is that MLIR doesn't really
have a way to deal with the lifetime of unranked memref descriptors that
happen inside the function, which is inevitably what would happen in the
old code that would emit runtime calls to
`refbackrt.to_memref/refbackrt.from_memref` to convert back and forth to
`refbackrt::Tensor` inside the codegenned code.
So, instead of the `refbackrt.to_memref/refbackrt.from_memref` with no
real sound basis for valid lifetime management, we now have a lovely
piece of code in `refbackrt::invoke` in `Runtime.cpp` that just barely
seems to be sound. We rely on the codegenned code having these
properties, which it seems to have:
- it won't free memref descriptors or their backing buffer for arguments
of UnrankedMemRef type.
- it will allocate a separate memref descriptor for each result
UnrankedMemRef (which is ensured by having a separate memref_cast for
each)
- we can sniff the `allocatedPtr`'s (i.e. the backing buffer pointers)
to avoid double-freeing in the case of aliasing of the backing buffer
(including backing buffers for arguments feeding into results)
- to catch the case of statically allocated data (which we need to avoid
passing to `free`) , check if the `allocatedPtr` is (no joke) equal to
`0xDEADBEEF`, because there is otherwise no way to distinguish
statically allocated from malloc'ed data... (std.global_memref lowering
to LLVM by happenstance sets the allocatedPtr equal to `0xDEADBEEF`,
presumably mainly as a debugging thing)
Even with all this, we *still* need to (internally to refbackrt::invoke)
make copies of all inputs/outputs! And the details of how the LLVM-level
ABI gets laid out for e.g. function arguments/returns is still super
tricky.
This really highlights how deficient memref is as the general runtime
type for our use case. It's stewing in my mind how best to improve the
situation. My general gut feeling is that IREE's abstractions for this
are "right", but I need to think more how to distill those aspects of
IREE's design in a "reference" way for RefBackend.
Some implementation notes:
- In terms of how this is implemented, this did catch a bug in our ABI
wrapper functions in LowerToLLVM.cpp, which I had to fix (it happened to
work before through some combination of npcomprt::Tensor being passed as
a single pointer + probably me infinite-monkey-ing it until it worked)
- This actually removes 2 out of the 3 compiler runtime functions (the
only one left is "abort_if". (most of the memref descriptor code moved
from CopmilerRuntime.cpp to Runtime.cpp)
- this also means deleting `refbackrt.from_memref` and
`refbackrt.to_memref`
2020-11-25 09:18:57 +08:00
|
|
|
// >=2 logical results. The convention linked above will create a struct
|
|
|
|
// wrapping.
|
2021-01-06 08:12:11 +08:00
|
|
|
for (int i = 0, e = structType.getBody().size(); i < e; i++) {
|
|
|
|
auto elementTy = structType.getBody()[i];
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
Value addr = getTypedAddressFromVoidStarStar(resultsPtrPtr, i, elementTy,
|
|
|
|
builder, loc);
|
|
|
|
int32_t i32I = i;
|
|
|
|
Value value = builder.create<LLVM::ExtractValueOp>(
|
|
|
|
loc, elementTy, result, builder.getI32ArrayAttr({i32I}));
|
|
|
|
builder.create<LLVM::StoreOp>(loc, value, addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct a wrapper function.
|
|
|
|
// For an externally visible function f(T1, T2) -> T3, T4, we create a
|
|
|
|
// wrapper
|
2020-10-08 08:12:52 +08:00
|
|
|
// __refbackrt_wrapper_f(void **inputs, void ** outputs) {
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// T3 t3;
|
|
|
|
// T4 t4;
|
|
|
|
// (t3, t4) = f(*cast<T1*>(inputs[0]), *cast<T2*>(inputs[1]));
|
|
|
|
// *cast<T3*>(outputs[0]) = t3;
|
|
|
|
// *cast<T4*>(outputs[1]) = t4;
|
|
|
|
// }
|
|
|
|
// This is very similar to MLIR's "packed" convention, but supporting
|
|
|
|
// outputs.
|
|
|
|
// TODO: Extend MLIR's void** wrappers to have outputs in this way.
|
|
|
|
static LLVMFuncOp createWrapperFunc(LLVMFuncOp func) {
|
2020-08-28 06:09:10 +08:00
|
|
|
auto *context = func.getContext();
|
2021-01-06 08:12:11 +08:00
|
|
|
LLVMFunctionType funcTy = func.getType();
|
|
|
|
auto voidStarTy = getInt8PointerType(context);
|
|
|
|
auto voidStarStarTy = LLVMPointerType::get(voidStarTy);
|
|
|
|
auto wrapperTy = LLVMFunctionType::get(LLVMVoidType::get(context),
|
|
|
|
{voidStarStarTy, voidStarStarTy},
|
|
|
|
/*isVarArg=*/false);
|
2020-10-08 08:12:52 +08:00
|
|
|
constexpr char kRefbackrtWrapperPrefix[] = "__refbackrt_wrapper_";
|
|
|
|
auto wrapperName = (Twine(kRefbackrtWrapperPrefix) + func.getName()).str();
|
2020-12-15 06:30:51 +08:00
|
|
|
OpBuilder moduleBuilder(func->getParentRegion());
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
LLVMFuncOp wrapper = moduleBuilder.create<LLVMFuncOp>(
|
|
|
|
func.getLoc(), wrapperName, wrapperTy, LLVM::Linkage::External);
|
|
|
|
|
|
|
|
// Create the function body.
|
|
|
|
Block &body = *wrapper.addEntryBlock();
|
|
|
|
auto builder = OpBuilder::atBlockBegin(&body);
|
|
|
|
auto callArgs =
|
|
|
|
loadCallArgs(body.getArgument(0), funcTy, builder, func.getLoc());
|
|
|
|
auto call = builder.create<LLVM::CallOp>(func.getLoc(), func, callArgs);
|
|
|
|
storeWrapperResults(call, body.getArgument(1), builder, func.getLoc());
|
|
|
|
builder.create<LLVM::ReturnOp>(func.getLoc(), ValueRange());
|
|
|
|
return wrapper;
|
2020-05-21 09:48:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
class LowerToLLVM : public LowerToLLVMBase<LowerToLLVM> {
|
2020-09-10 15:23:46 +08:00
|
|
|
void getDependentDialects(DialectRegistry ®istry) const override {
|
|
|
|
registry.insert<LLVM::LLVMDialect>();
|
|
|
|
}
|
|
|
|
|
|
|
|
void runOnOperation() override {
|
2020-05-21 09:48:53 +08:00
|
|
|
auto module = getOperation();
|
|
|
|
auto *context = &getContext();
|
|
|
|
|
|
|
|
LLVMTypeConverter converter(context);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
|
2021-03-24 05:16:23 +08:00
|
|
|
RewritePatternSet patterns(context);
|
2020-05-21 09:48:53 +08:00
|
|
|
LLVMConversionTarget target(*context);
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
populateCompilerRuntimePatterns(module, patterns, converter);
|
2021-04-03 04:38:20 +08:00
|
|
|
target.addLegalOp<ModuleOp>();
|
2020-05-21 09:48:53 +08:00
|
|
|
populateStdToLLVMConversionPatterns(converter, patterns);
|
2021-03-24 05:16:23 +08:00
|
|
|
patterns.add<LowerModuleMetadata>(context);
|
2020-05-21 09:48:53 +08:00
|
|
|
|
2020-09-25 08:14:21 +08:00
|
|
|
// TODO: Move these "std to std" legalizations to their own pass if we grow
|
|
|
|
// lots of these patterns.
|
2021-03-24 05:16:23 +08:00
|
|
|
populateExpandTanhPattern(patterns);
|
2021-06-26 08:25:09 +08:00
|
|
|
populateLinalgToLLVMConversionPatterns(converter, patterns);
|
2020-09-25 08:14:21 +08:00
|
|
|
|
2020-10-30 06:25:55 +08:00
|
|
|
if (failed(applyFullConversion(module, target, std::move(patterns)))) {
|
2020-05-21 09:48:53 +08:00
|
|
|
return signalPassFailure();
|
|
|
|
}
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
// Rewrite llvm.mlir.addressof ops that reference the original exported
|
|
|
|
// functions from the module to instead refer to wrapper functions.
|
|
|
|
// These wrapper functions have a fixed ABI
|
|
|
|
// (`void f(void **inputs, void **results)`) which we can interface to from
|
|
|
|
// external code without dealing with platform-dependent
|
|
|
|
// register-level calling conventions. We embed enough information in the
|
|
|
|
// module metadata to make sure that calling code can e.g. preallocate
|
|
|
|
// enough outputs and with the right types to safely funnel through this
|
|
|
|
// convention.
|
|
|
|
module.walk([&](LLVM::AddressOfOp op) {
|
|
|
|
auto originalFunc =
|
|
|
|
module.lookupSymbol<LLVM::LLVMFuncOp>(op.global_name());
|
|
|
|
if (!originalFunc)
|
|
|
|
return;
|
|
|
|
auto wrapper = createWrapperFunc(originalFunc);
|
2021-01-06 08:12:11 +08:00
|
|
|
op.getResult().setType(LLVMPointerType::get(wrapper.getType()));
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
Builder builder(op.getContext());
|
2020-12-15 06:30:51 +08:00
|
|
|
op->setAttr("global_name", builder.getSymbolRefAttr(wrapper.getName()));
|
Rework e2e flow to use new "npcomprt"
This ~totally reworks the existing "runtime" stuff to be more
principled and usable, such as from Python. It's still not fully
production-quality, mainly in the department of memory management (e.g.
it currently leaks memory; we need to figure out "who frees memrefs" +
the analysis and transformation needed to do that (maybe use upstream
buffer allocation pass?)).
The user API is in include/npcomp/runtime/UserAPI.h, though
include/npcomp/JITRuntime/JITModule.h is a friendlier wrapper.
The stuff under {include,lib}/runtime is totally firewalled from the
compiler and tiny (<6kB, though no attention has gone into optimizing
that size). For example, we don't link in libSupport into the runtime,
instead having our own bare bones replacements for basics like ArrayRef
(the JITRuntime helps with bridging that gap, since it *can* depend on
all common LLVM utilities).
The overall features of npcomprt is that it exposes a module that
with multiple function entry points. Each function has arguments and
results that are tensor-valued, and npcomprt::Tensor is the runtime type
that is used to interact with that (and a npcomprt::Ref<T>
reference-counting wrapper is provided to wrap npcomprt::Tensor in the
common case).
From an implementation perspective, an npcomprt module at the
LLVM/object/binary level exposes a single module descriptor struct that
has pointers to other metadata (currently just a list of function
metadata descriptors). All interactions with the npcomp runtime are
keyed off of that module descriptor, including function lookups and
dispatching. This is done to dodge platform ABI issues and also allow
enough reflection to e.g. verify provided arguments.
Most of the compiler-side work here was in LowerToNpcomprtABI and
LowerToLLVM.
Also,
- Rename npcomp_rt/NpcompRt to npcomprt/Npcomprt; it was getting
annoying to type the underscores/caps.
- misc improvements to bash_helpers.sh
2020-07-09 08:15:40 +08:00
|
|
|
});
|
2020-05-21 09:48:53 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
std::unique_ptr<OperationPass<ModuleOp>> mlir::NPCOMP::createLowerToLLVMPass() {
|
|
|
|
return std::make_unique<LowerToLLVM>();
|
|
|
|
}
|