blob: 124e637da1f9a3f52f45c5837a2c899f9a98a964 [file] [log] [blame]
//===- Builders.h - MLIR Declarative Builder Classes ------------*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// Provides intuitive composable interfaces for building structured MLIR
// snippets in a declarative fashion.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_EDSC_BUILDERS_H_
#define MLIR_EDSC_BUILDERS_H_
#include "mlir/AffineOps/AffineOps.h"
#include "mlir/IR/Builders.h"
#include "mlir/StandardOps/Ops.h"
#include "mlir/VectorOps/VectorOps.h"
namespace mlir {
namespace edsc {
struct index_t {
explicit index_t(int64_t v) : v(v) {}
explicit operator int64_t() { return v; }
int64_t v;
};
class BlockHandle;
class CapturableHandle;
class NestedBuilder;
class ValueHandle;
/// Helper class to transparently handle builder insertion points by RAII.
/// As its name indicates, a ScopedContext is means to be used locally in a
/// scoped fashion. This abstracts away all the boilerplate related to
/// checking proper usage of captures, NestedBuilders as well as handling the
/// setting and restoring of insertion points.
class ScopedContext {
public:
ScopedContext(FuncBuilder &builder, Location location);
/// Sets the insertion point of the builder to 'newInsertPt' for the duration
/// of the scope. The existing insertion point of the builder is restored on
/// destruction.
ScopedContext(FuncBuilder &builder, FuncBuilder::InsertPoint newInsertPt,
Location location);
~ScopedContext();
static MLIRContext *getContext();
static FuncBuilder *getBuilder();
static Location getLocation();
private:
/// Only NestedBuilder (which is used to create an operation with a body)
/// may access private members in order to implement scoping.
friend class NestedBuilder;
ScopedContext() = delete;
ScopedContext(const ScopedContext &) = delete;
ScopedContext &operator=(const ScopedContext &) = delete;
static ScopedContext *&getCurrentScopedContext();
/// Top level FuncBuilder.
FuncBuilder &builder;
/// The previous insertion point of the builder.
llvm::Optional<FuncBuilder::InsertPoint> prevBuilderInsertPoint;
/// Current location.
Location location;
/// Parent context we return into.
ScopedContext *enclosingScopedContext;
/// Defensively keeps track of the current NestedBuilder to ensure proper
/// scoping usage.
NestedBuilder *nestedBuilder;
// TODO: Implement scoping of ValueHandles. To do this we need a proper data
// structure to hold ValueHandle objects. We can emulate one but there should
// already be something available in LLVM for this purpose.
};
/// A NestedBuilder is a scoping abstraction to create an idiomatic syntax
/// embedded in C++ that serves the purpose of building nested MLIR.
/// Nesting and compositionality is obtained by using the strict ordering that
/// exists between object construction and method invocation on said object (in
/// our case, the call to `operator()`).
/// This ordering allows implementing an abstraction that decouples definition
/// from declaration (in a PL sense) on placeholders of type ValueHandle and
/// BlockHandle.
class NestedBuilder {
protected:
NestedBuilder() = default;
NestedBuilder(const NestedBuilder &) = delete;
NestedBuilder(NestedBuilder &&other) : bodyScope(other.bodyScope) {
other.bodyScope = nullptr;
}
NestedBuilder &operator=(const NestedBuilder &) = delete;
NestedBuilder &operator=(NestedBuilder &&other) {
std::swap(bodyScope, other.bodyScope);
return *this;
}
/// Enter an mlir::Block and setup a ScopedContext to insert operations at
/// the end of it. Since we cannot use c++ language-level scoping to implement
/// scoping itself, we use enter/exit pairs of operations.
/// As a consequence we must allocate a new FuncBuilder + ScopedContext and
/// let the escape.
/// Step back "prev" times from the end of the block to set up the insertion
/// point, which is useful for non-empty blocks.
void enter(mlir::Block *block, int prev = 0) {
bodyScope = new ScopedContext(
*ScopedContext::getBuilder(),
FuncBuilder::InsertPoint(block, std::prev(block->end(), prev)),
ScopedContext::getLocation());
bodyScope->nestedBuilder = this;
}
/// Exit the current mlir::Block by explicitly deleting the dynamically
/// allocated FuncBuilder and ScopedContext.
void exit() {
// Reclaim now to exit the scope.
bodyScope->nestedBuilder = nullptr;
delete bodyScope;
bodyScope = nullptr;
}
/// Custom destructor does nothing because we already destroyed bodyScope
/// manually in `exit`. Insert an assertion to defensively guard against
/// improper usage of scoping.
~NestedBuilder() {
assert(!bodyScope &&
"Illegal use of NestedBuilder; must have called exit()");
}
private:
ScopedContext *bodyScope = nullptr;
};
/// A LoopBuilder is a generic NestedBuilder for loop-like MLIR operations.
/// More specifically it is meant to be used as a temporary object for
/// representing any nested MLIR construct that is "related to" an mlir::Value*
/// (for now an induction variable).
/// This is extensible and will evolve in the future as MLIR evolves, hence
/// the name LoopBuilder (as opposed to say ForBuilder or AffineForBuilder).
class LoopBuilder : public NestedBuilder {
public:
/// Constructs a new AffineForOp and captures the associated induction
/// variable. A ValueHandle pointer is passed as the first argument and is the
/// *only* way to capture the loop induction variable.
LoopBuilder(ValueHandle *iv, ArrayRef<ValueHandle> lbHandles,
ArrayRef<ValueHandle> ubHandles, int64_t step);
LoopBuilder(const LoopBuilder &) = delete;
LoopBuilder(LoopBuilder &&) = default;
LoopBuilder &operator=(const LoopBuilder &) = delete;
LoopBuilder &operator=(LoopBuilder &&) = default;
/// The only purpose of this operator is to serve as a sequence point so that
/// the evaluation of `stmts` (which build IR snippets in a scoped fashion) is
/// sequenced strictly after the constructor of LoopBuilder.
/// In order to be admissible in a nested ArrayRef<ValueHandle>, operator()
/// returns a ValueHandle::null() that cannot be captured.
// TODO(ntv): when loops return escaping ssa-values, this should be adapted.
ValueHandle operator()(ArrayRef<CapturableHandle> stmts);
};
/// Explicit nested LoopBuilder. Offers a compressed multi-loop builder to avoid
/// explicitly writing all the loops in a nest. This simple functionality is
/// also useful to write rank-agnostic custom ops.
///
/// Usage:
///
/// ```c++
/// LoopNestBuilder({&i, &j, &k}, {lb, lb, lb}, {ub, ub, ub}, {1, 1, 1})({
/// ...
/// });
/// ```
///
/// ```c++
/// LoopNestBuilder({&i}, {lb}, {ub}, {1})({
/// LoopNestBuilder({&j}, {lb}, {ub}, {1})({
/// LoopNestBuilder({&k}, {lb}, {ub}, {1})({
/// ...
/// }),
/// }),
/// });
/// ```
class LoopNestBuilder {
public:
LoopNestBuilder(ArrayRef<ValueHandle *> ivs, ArrayRef<ValueHandle> lbs,
ArrayRef<ValueHandle> ubs, ArrayRef<int64_t> steps);
// TODO(ntv): when loops return escaping ssa-values, this should be adapted.
ValueHandle operator()(ArrayRef<CapturableHandle> stmts);
private:
SmallVector<LoopBuilder, 4> loops;
};
// This class exists solely to handle the C++ vexing parse case when
// trying to enter a Block that has already been constructed.
class Append {};
/// A BlockBuilder is a NestedBuilder for mlir::Block*.
/// This exists by opposition to LoopBuilder which is not related to an
/// mlir::Block* but to a mlir::Value*.
/// It is meant to be used as a temporary object for representing any nested
/// MLIR construct that is "related to" an mlir::Block*.
class BlockBuilder : public NestedBuilder {
public:
/// Enters the mlir::Block* previously captured by `bh` and sets the insertion
/// point to its end.
BlockBuilder(BlockHandle bh, Append);
/// Constructs a new mlir::Block with argument types derived from `args`.
/// Captures the new block in `bh` and its arguments into `args`.
/// Enters the new mlir::Block* and sets the insertion point to its end.
///
/// Prerequisites:
/// The ValueHandle `args` are typed delayed ValueHandles; i.e. they are
/// not yet bound to mlir::Value*.
BlockBuilder(BlockHandle *bh, ArrayRef<ValueHandle *> args);
/// The only purpose of this operator is to serve as a sequence point so that
/// the evaluation of `stmts` (which build IR snippets in a scoped fashion) is
/// sequenced strictly after the constructor of BlockBuilder.
void operator()(ArrayRef<CapturableHandle> stmts);
private:
BlockBuilder(BlockBuilder &) = delete;
BlockBuilder &operator=(BlockBuilder &other) = delete;
};
/// Base class for ValueHandle, OperationHandle and BlockHandle.
/// Not meant to be used outside of these classes.
class CapturableHandle {
protected:
CapturableHandle() = default;
};
/// ValueHandle implements a (potentially "delayed") typed Value abstraction.
/// ValueHandle should be captured by pointer but otherwise passed by Value
/// everywhere.
/// A ValueHandle can have 3 states:
/// 1. null state (empty type and empty value), in which case it does not hold
/// a value and may never hold a Value (not now of in the future). This is
/// used for MLIR operations with zero returns as well as the result of
/// calling a NestedBuilder::operator(). In both cases the objective is to
/// have an object that can be inserted in an ArrayRef<ValueHandle> to
/// implement nesting;
/// 2. delayed state (empty value), in which case it represents an eagerly
/// typed "delayed" value that can be hold a Value in the future;
/// 3. constructed state,in which case it holds a Value.
///
/// A ValueHandle is meant to capture a single Value* and should be used for
/// operations that have a single result. For convenience of use, we also
/// include AffineForOp in this category although it does not return a value.
/// In the case of AffineForOp, the captured Value* is the loop induction
/// variable.
class ValueHandle : public CapturableHandle {
public:
/// A ValueHandle in a null state can never be captured;
static ValueHandle null() { return ValueHandle(); }
/// A ValueHandle that is constructed from a Type represents a typed "delayed"
/// Value. A delayed Value can only capture Values of the specified type.
/// Such a delayed value represents the declaration (in the PL sense) of a
/// placeholder for an mlir::Value* that will be constructed and captured at
/// some later point in the program.
explicit ValueHandle(Type t) : t(t), v(nullptr) {}
/// A ValueHandle that is constructed from an mlir::Value* is an "eager"
/// Value. An eager Value represents both the declaration and the definition
/// (in the PL sense) of a placeholder for an mlir::Value* that has already
/// been constructed in the past and that is captured "now" in the program.
explicit ValueHandle(Value *v) : t(v->getType()), v(v) {}
/// Builds a ConstantIndexOp of value `cst`. The constant is created at the
/// current insertion point.
/// This implicit constructor is provided to each build an eager Value for a
/// constant at the current insertion point in the IR. An implicit constructor
/// allows idiomatic expressions mixing ValueHandle and literals.
ValueHandle(index_t cst);
/// ValueHandle is a value type, use the default copy constructor.
ValueHandle(const ValueHandle &other) = default;
/// ValueHandle is a value type, the assignment operator typechecks before
/// assigning.
ValueHandle &operator=(const ValueHandle &other);
/// Provide a swap operator.
void swap(ValueHandle &other) {
if (this == &other)
return;
std::swap(t, other.t);
std::swap(v, other.v);
}
/// Implicit conversion useful for automatic conversion to Container<Value*>.
operator Value *() const { return getValue(); }
/// Generic mlir::Op create. This is the key to being extensible to the whole
/// of MLIR without duplicating the type system or the op definitions.
template <typename Op, typename... Args>
static ValueHandle create(Args... args);
/// Special case to build composed AffineApply operations.
// TODO: createOrFold when available and move inside of the `create` method.
static ValueHandle createComposedAffineApply(AffineMap map,
ArrayRef<Value *> operands);
/// Generic create for a named operation producing a single value.
static ValueHandle create(StringRef name, ArrayRef<ValueHandle> operands,
ArrayRef<Type> resultTypes,
ArrayRef<NamedAttribute> attributes = {});
bool hasValue() const { return v != nullptr; }
Value *getValue() const {
assert(hasValue() && "Unexpected null value;");
return v;
}
bool hasType() const { return t != Type(); }
Type getType() const { return t; }
Operation *getOperation() const {
if (!v)
return nullptr;
return v->getDefiningOp();
}
protected:
ValueHandle() : t(), v(nullptr) {}
Type t;
Value *v;
};
/// An OperationHandle can be used in lieu of ValueHandle to capture the
/// operation in cases when one does not care about, or cannot extract, a
/// unique Value* from the operation.
/// This can be used for capturing zero result operations as well as
/// multi-result operations that are not supported by ValueHandle.
/// We do not distinguish further between zero and multi-result operations at
/// this time.
struct OperationHandle : public CapturableHandle {
OperationHandle() : op(nullptr) {}
OperationHandle(Operation *op) : op(op) {}
OperationHandle(const OperationHandle &) = default;
OperationHandle &operator=(const OperationHandle &) = default;
/// Generic mlir::Op create. This is the key to being extensible to the whole
/// of MLIR without duplicating the type system or the op definitions.
template <typename Op, typename... Args>
static OperationHandle create(Args... args);
/// Generic create for a named operation.
static OperationHandle create(StringRef name, ArrayRef<ValueHandle> operands,
ArrayRef<Type> resultTypes,
ArrayRef<NamedAttribute> attributes = {});
operator Operation *() { return op; }
Operation *getOperation() const { return op; }
private:
Operation *op;
};
/// Simple wrapper to build a generic operation without successor blocks.
template <typename HandleType> struct CustomOperation {
CustomOperation(StringRef name) : name(name) {
static_assert(std::is_same<HandleType, ValueHandle>() ||
std::is_same<HandleType, OperationHandle>(),
"Only CustomOperation<ValueHandle> or "
"CustomOperation<OperationHandle> can be constructed.");
}
HandleType operator()(ArrayRef<ValueHandle> operands = {},
ArrayRef<Type> resultTypes = {},
ArrayRef<NamedAttribute> attributes = {}) {
return HandleType::create(name, operands, resultTypes, attributes);
}
std::string name;
};
/// A BlockHandle represents a (potentially "delayed") Block abstraction.
/// This extra abstraction is necessary because an mlir::Block is not an
/// mlir::Value.
/// A BlockHandle should be captured by pointer but otherwise passed by Value
/// everywhere.
class BlockHandle : public CapturableHandle {
public:
/// A BlockHandle constructed without an mlir::Block* represents a "delayed"
/// Block. A delayed Block represents the declaration (in the PL sense) of a
/// placeholder for an mlir::Block* that will be constructed and captured at
/// some later point in the program.
BlockHandle() : block(nullptr) {}
/// A BlockHandle constructed with an mlir::Block* represents an "eager"
/// Block. An eager Block represents both the declaration and the definition
/// (in the PL sense) of a placeholder for an mlir::Block* that has already
/// been constructed in the past and that is captured "now" in the program.
BlockHandle(mlir::Block *block) : block(block) {}
/// BlockHandle is a value type, use the default copy constructor and
/// assignment operator.
BlockHandle(const BlockHandle &) = default;
BlockHandle &operator=(const BlockHandle &) = default;
/// Delegates block creation to MLIR and wrap the resulting mlir::Block.
static BlockHandle create(ArrayRef<Type> argTypes);
operator bool() { return block != nullptr; }
operator mlir::Block *() { return block; }
mlir::Block *getBlock() { return block; }
private:
mlir::Block *block;
};
template <typename Op, typename... Args>
OperationHandle OperationHandle::create(Args... args) {
return OperationHandle(ScopedContext::getBuilder()
->create<Op>(ScopedContext::getLocation(), args...)
.getOperation());
}
template <typename Op, typename... Args>
ValueHandle ValueHandle::create(Args... args) {
Operation *op = ScopedContext::getBuilder()
->create<Op>(ScopedContext::getLocation(), args...)
.getOperation();
if (op->getNumResults() == 1) {
return ValueHandle(op->getResult(0));
} else if (op->getNumResults() == 0) {
if (auto f = dyn_cast<AffineForOp>(op)) {
return ValueHandle(f.getInductionVar());
}
}
llvm_unreachable("unsupported operation, use an OperationHandle instead");
}
namespace op {
ValueHandle operator+(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator-(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator*(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator/(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator%(ValueHandle lhs, ValueHandle rhs);
ValueHandle floorDiv(ValueHandle lhs, ValueHandle rhs);
ValueHandle ceilDiv(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator!(ValueHandle value);
ValueHandle operator&&(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator||(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator^(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator==(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator!=(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator<(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator<=(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator>(ValueHandle lhs, ValueHandle rhs);
ValueHandle operator>=(ValueHandle lhs, ValueHandle rhs);
} // namespace op
} // namespace edsc
} // namespace mlir
#endif // MLIR_EDSC_BUILDERS_H_