Standard load and store operations are evolving to be separated from the Affine
constructs. Special affine.load/store have been introduced to uphold the
restrictions of the Affine control flow constructs on their operands.
EDSC-produced loads and stores were originally intended to uphold those
restrictions as well so they should use affine.load/store instead of
std.load/store.
PiperOrigin-RevId: 257443307
This is an important step in allowing for the top-level of the IR to be extensible. FuncOp and ModuleOp contain all of the necessary functionality, while using the existing operation infrastructure. As an interim step, many of the usages of Function and Module, including the name, will remain the same. In the future, many of these will be relaxed to allow for many different types of top-level operations to co-exist.
PiperOrigin-RevId: 256427100
The current syntax separates the name and value with ':', but ':' is already overloaded by several other things(e.g. trailing types). This makes the syntax difficult to parse in some situtations:
Old:
"foo: 10 : i32"
New:
"foo = 10 : i32"
PiperOrigin-RevId: 255097928
This CL performs post-commit cleanups.
It adds the ability to specify which shared libraries to load dynamically in ExecutionEngine. The linalg integration test is updated to use a shared library.
Additional minor cleanups related to LLVM lowering of Linalg are also included.
--
PiperOrigin-RevId: 248346589
This CL orders the python tests to:
1. allow introspecting on the EdscTest class and avoid the error-prone process of having to add the test call by hand;
2. account for differences in the order of `dir(edscTest)` between python2, <= python3.5 and >= python 3.6
--
PiperOrigin-RevId: 247609687
The generic form of operations currently supports optional regions to be
located after the operation type. As we are going to add a type to each
region in a leading position in the region syntax, similarly to functions, it
becomes ambiguous to have regions immediately after the operation type. Put
regions between operands the optional list of successors in the generic
operation syntax and wrap them in parentheses. The effect on the exisitng IR
syntax is minimal since only three operations (`affine.for`, `affine.if` and
`gpu.kernel`) currently use regions.
--
PiperOrigin-RevId: 246787087
This completes the transition of Python bindings to use the declarative
builders infrastructure instead of the now-deprecated EDSC emitter
infrastructure. The relevant unit tests have been replicated using the new
functionality and the remaining end-to-end compilation tests have been updated
accordingly. The latter show an improvement in brevity and readability.
--
PiperOrigin-RevId: 241713489
The original reimplementation of EDSC as declarative builders and the
subsequent rework of Python bindings forbade to use the (true) division
operator for values of the index types without providing an alternative. Index
types only support floor and ceil division through affine maps. Expose this to
Python bindings through a `__floordiv__` function on `ValueHandle`s.
--
PiperOrigin-RevId: 241713093
In particular, expose comparison operators as Python operator overloads on
ValueHandles. The comparison currently emits signed integer comparisons only,
which is compatible with the behavior of emitter-based EDSC interface. This is
sub-optimal and must be reconsidered in the future. Note that comparison
operators are not overloaded in the C++ declarative builder API precisely
because this avoids the premature decision on the signedness of comparisons.
Implement the declarative construction of boolean expressions using
ValueHandles by overloading the boolean operators in the `op` namespace to
differentiate between `operator!` for nullity check and for boolean negation.
The operands must be of i1 type. Also expose boolean operations as Python
operator overloads on ValueHandles.
PiperOrigin-RevId: 238421615
In particular, expose `cond_br`, `select` and `call` operations with syntax
similar to that of the previous emitter-based EDSC interface. These are
provided for backwards-compatibility. Ideally, we want them to be
Table-generated from the Op definitions when those definitions are declarative.
Additionally, expose the ability to construct any op given its canonical name,
which also exercises the construction of unregistered ops.
PiperOrigin-RevId: 238421583
Expose edsc::IndexedValue using a syntax smilar to that of edsc::Indexed to
ensure backwards-compatibility. It remains possible to write array-indexed
loads and stores as
C.store([i, j], A.load([i, k]) * B.load([k, j]))
after taking a "view" of some value handle using IndexedValue as
A = IndexedValue(originalValueHandle)
provided that all indices are also value handles.
PiperOrigin-RevId: 238421544
In the original implementation, constants could be bound to EDSC expressions in
the binder, independently from other MLIR Values. A rework of EDSC including
early typing provided the functionality to use MLIR's `constant` operation to
define typed constants instead of binding them separately, but only used it for
index types. The new declarative builder implementation followed by providing
a call for building `constant` operations of index types but nothing more.
Expose similar builders for integers, floats and functions to match the what
binders allow one to use.
PiperOrigin-RevId: 238421508
Provide a function `arg` that returns the function argument as a value handle,
similar to block arguments. This makes function context managers in Python
similar to block context managers, which is more consistent given that the
function context manager sets the insertion point to the first block of the
function and that arguments of that block are those of the function. This
prepares the removal of PythonMLIREmitter class and its bind_function_arguments
helper.
Additionally, provide a helper method in PythonMLIRModule to define a function
and immediately create a context for it. Update the tests that are already
using context managers to use the function context manager instead of creating
the function manually.
PiperOrigin-RevId: 238421087
Expose EDSC block builders as Python context managers, similarly to loop
builders. Note that blocks, unlike loops, are addressable and may need to be
"declared" without necessarily filling their bodies with instructions. This is
the case, for example, when branching to a new block from the existing block.
Therefore, creating the block context manager immediately creates the block
(unless the manager captures an existing block) by creating and destroying the
block builder. With this approach, one can either fill in the block and refer
to it later leveraging Python's dynamic variable lookup
with BlockContext([indexType]) as b:
op(...) # operation inside the block
ret()
op(...) # operation outside the block (in the function entry block)
br(b, [...]) # branching to the block created above
or declare the block contexts upfront and enter them on demand
bb1 = BlockContext() # empty block created in the surrounding function
bb2 = BlockContext() # context
cond_br(bb1.handle, [], bb2.handle, []) # branch to blocks from here
with bb1:
op(...) # operation inside the first block
with bb2:
op(...) # operation inside the second block
with bb1:
op(...) # append operation to the first block
Additionally, one can create multiple throw-away contexts that append to the
same block
with BlockContext() as b:
op(...) # operation inside the block
with BlockContext(appendTo(b)):
op(...) # new context appends to the block
which has a potential of being extended to control the insertion point of the
block at a finer level of granularity.
PiperOrigin-RevId: 238005298
Recently, EDSC introduced an eager mode for building IR in different contexts.
Introduce Python bindings support for loop and loop nest contexts of EDSC
builders. The eager mode is built around the notion of ValueHandle, which is
convenience class for delayed initialization and operator overloads. Expose
this class and overloads directly. The model of insertion contexts maps
naturally to Python context manager mechanism, therefore new bindings are
defined bypassing the C APIs. The bindings now provide three new context
manager classes: FunctionContext, LoopContext and LoopNestContext. The last
two can be used with the `with`-construct in Python to create loop (nests) and
obtain handles to the loop induction variables seamlessly:
with LoopContext(lhs, rhs, 1) as i:
lhs + rhs + i
with LoopContext(rhs, rhs + rhs, 2) as j:
x = i + j
Any statement within the Python context will trigger immediate emission of the
corresponding IR constructs into the context owned by the nearest context
manager.
PiperOrigin-RevId: 237447732
Currently, Python bindings provide support for declarting and defining MLIR
functions given a list of argument and result types. Extend the support for
both function declaration and function definition to handle optional function
attributes and function argument attributes. Function attributes are exposed
as keyword arguments on function declaration and definition calls. Function
argument attributes are exposed through a special object that combines the
argument type and its list of attributes. Such objects can be passed instead
of bare types into the type declaration and definition calls. They can be
constructed from bare types and reused in different declarations.
Note that, from the beginning, Python bindings did not pass through C bindings
to declare and define functions. This commit keeps the direct interaction
between Python and C++.
PiperOrigin-RevId: 237047561
EDSC Expressions can now be used to build arbitrary MLIR operations identified
by their canonical name, i.e. the name obtained from
`OpClass::getOperationName()` for registered operations. Expose this
functionality to the C API and Python bindings. This exposes builder-level
interface to Python and avoids the need for experimental Python code to
implement EDSC free function calls for constructing each op type.
This modification required exposing mlir::Attribute to the C API and Python
bindings, which only supports integer attributes for now.
This is step 4/n to making EDSCs more generalizable.
PiperOrigin-RevId: 236306776
Leverage the recently introduced support for multiple argument groups and
multiple destination blocks in EDSC Expressions to implement conditional
branches in EDSC. Conditional branches have two successors and three argument
groups. The first group contains a single expression of i1 type that
corresponds to the condition of the branch. The two following groups contain
arguments of the two successors of the conditional branch instruction, in the
same order as the successors. Expose this instruction to the C API and Python
bindings.
PiperOrigin-RevId: 235542768
The new implementation of blocks was designed to support blocks with arguments.
More specifically, StmtBlock can be constructed with a list of Bindables that
will be bound to block aguments upon construction. Leverage this functionality
to implement branch instructions with arguments.
This additionally requires the statement storage to have a list of successors,
similarly to core IR operations.
Becauase successor chains can form loops, we need a possibility to decouple
block declaration, after which it becomes usable by branch instructions, from
block body definition. This is achieved by creating an empty block and by
resetting its body with a new list of instructions. Note that assigning a
block from another block will not affect any instructions that may have
designated this block as their successor (this behavior is necessary to make
value-type semantics of EDSC types consistent). Combined, one can now write
generators like
EDSCContext context;
Type indexType = ...;
Bindable i(indexType), ii(indexType), zero(indexType), one(indexType);
StmtBlock loopBlock({i}, {});
loopBlock.set({ii = i + one,
Branch(loopBlock, {ii})});
MLIREmitter(&builder)
.bindConstant<ConstantIndexOp>(zero, 0)
.bindConstant<ConstantIndexOp>(one, 1)
.emitStmt(Branch(loopBlock, {zero}));
where the emitter will emit the statement and its successors, if present.
PiperOrigin-RevId: 235541892
This change introduces three new operators in EDSC: Div (also exposed
via Expr.__div__ aka /) -- floating-point division, FloorDiv and CeilDiv
for flooring/ceiling index division.
The lowering to LLVM will be implemented in b/124872679.
PiperOrigin-RevId: 234963217
Introduce support for binding MLIR functions as constant expressions. Standard
constant operation supports functions as possible constant values.
Provide C APIs to look up existing named functions in an MLIR module and expose
them to the Python bindings. Provide Python bindings to declare a function in
an MLIR module without defining it and to add a definition given a function
declaration. These declarations are useful when attempting to link MLIR
modules with, e.g., the standard library.
Introduce EDSC support for direct and indirect calls to other MLIR functions.
Internally, an indirect call is always emitted to leverage existing support for
delayed construction of MLIR Values using EDSC Exprs. If the expression is
bound to a constant function (looked up or declared beforehand), MLIR constant
folding will be able to replace an indirect call by a direct call. Currently,
only zero- and one-result functions are supported since we don't have support
for multi-valued expressions in EDSC.
Expose function calling interface to Python bindings on expressions by defining
a `__call__` function accepting a variable number of arguments.
PiperOrigin-RevId: 234959444
Introduce a type-safe way of building a 'for' loop with max/min bounds in EDSC.
Define new types MaxExpr and MinExpr in C++ EDSC API and expose them to Python
bindings. Use values of these type to construct 'for' loops with max/min in
newly introduced overloads of the `edsc::For` factory function. Note that in C
APIs, we still must expose MaxMinFor as a different function because C has no
overloads. Also note that MaxExpr and MinExpr do _not_ derive from Expr
because they are not allowed to be used in a regular Expr context (which may
produce `affine.apply` instructions not expecting `min` or `max`).
Factory functions `Min` and `Max` in Python can be further overloaded to
produce chains of comparisons and selects on non-index types. This is not
trivial in C++ since overloaded functions cannot differ by the return type only
(`MaxExpr` or `Expr`) and making `MaxExpr` derive from `Expr` defies the
purpose of type-safe construction.
PiperOrigin-RevId: 234786131
MLIR supports 'for' loops with lower(upper) bound defined by taking a
maximum(minimum) of a list of expressions, but does not have first-class affine
constructs for the maximum(minimum). All these expressions must have affine
provenance, similarly to a single-expression bound. Add support for
constructing such loops using EDSC. The expression factory function is called
`edsc::MaxMinFor` to (1) highlight that the maximum(minimum) operation is
applied to the lower(upper) bound expressions and (2) differentiate it from a
`edsc::For` that creates multiple perfectly nested loops (and should arguably
be called `edsc::ForNest`).
PiperOrigin-RevId: 234785996
Introduce a functionality to create EDSC expressions from typed constants.
This complements the current functionality that uses "unbound" expressions and
binds them to a specific constant before emission. It comes in handy in cases
where we want to check if something is a constant early during construciton
rather than late during emission, for example multiplications and divisions in
affine expressions. This is also consistent with MLIR vision of constants
being defined by an operation (rather than being special kinds of values in the
IR) by exposing this operation as EDSC expression.
PiperOrigin-RevId: 234758020
Expose the result types of edsc::Expr, which are now stored for all types of
Exprs and not only for the variadic ones. Require return types when an Expr is
constructed, if it will ever have some. An empty return type list is
interpreted as an Expr that does not create a value (e.g. `return` or `store`).
Conceptually, all edss::Exprs are now typed, with the type being a (potentially
empty) tuple of return types. Unbound expressions and Bindables must now be
constructed with a specific type they will take. This makes EDSC less
evidently type-polymorphic, but we can still write generic code such as
Expr sumOfSquares(Expr lhs, Expr rhs) { return lhs * lhs + rhs * rhs; }
and use it to construct different typed expressions as
sumOfSquares(Bindable(IndexType::get(ctx)), Bindable(IndexType::get(ctx)));
sumOfSquares(Bindable(FloatType::getF32(ctx)),
Bindable(FloatType::getF32(ctx)));
On the positive side, we get the following.
1. We can now perform type checking when constructing Exprs rather than during
MLIR emission. Nevertheless, this is still duplicates the Op::verify()
until we can factor out type checking from that.
2. MLIREmitter is significantly simplified.
3. ExprKind enum is only used for actual kinds of expressions. Data structures
are converging with AbstractOperation, and the users can now create a
VariadicExpr("canonical_op_name", {types}, {exprs}) for any operation, even
an unregistered one without having to extend the enum and make pervasive
changes to EDSCs.
On the negative side, we get the following.
1. Typed bindables are more verbose, even in Python.
2. We lose the ability to do print debugging for higher-level EDSC abstractions
that are implemented as multiple MLIR Ops, for example logical disjunction.
This is the step 2/n towards making EDSC extensible.
***
Move MLIR Op construction from MLIREmitter::emitExpr to Expr::build since Expr
now has sufficient information to build itself.
This is the step 3/n towards making EDSC extensible.
Both of these strive to minimize the amount of irrelevant changes. In
particular, this introduces more complex pretty-printing for affine and binary
expression to make sure tests continue to pass. It also relies on string
comparison to identify specific operations that an Expr produces.
PiperOrigin-RevId: 234609882
EDSC currently implement a block as a statement that is itself a list of
statements. This suffers from two modeling problems: (1) these blocks are not
addressable, i.e. one cannot create an instruction where thus constructed block
is a successor; (2) they support block nesting, which is not supported by MLIR
blocks. Furthermore, emitting such "compound statement" (misleadingly named
`Block` in Python bindings) does not actually produce a new Block in the IR.
Implement support for creating actual IR Blocks in EDSC. In particular, define
a new StmtBlock EDSC class that is neither an Expr nor a Stmt but contains a
list of Stmts. Additionally, StmtBlock may have (early-) typed arguments.
These arguments are Bindable expressions that can be used inside the block.
Provide two calls in the MLIREmitter, `emitBlock` that actually emits a new
block and `emitBlockBody` that only emits the instructions contained in the
block without creating a new block. In the latter case, the instructions must
not use block arguments.
Update Python bindings to make it clear when instruction emission happens
without creating a new block.
PiperOrigin-RevId: 234556474
The existing implementation of makeFunctionType in EDSC contains a bug: the
array of input types is overwritten using output types passed as arguments and
the array of output types is never filled in. This leads to all sorts of
incorrect memory behavior. Fill in the array of output types using the proper
argument.
PiperOrigin-RevId: 234177221
This CL applies the following simplifications to EDSCs:
1. Rename Block to StmtList because an MLIR Block is a different, not yet
supported, notion;
2. Rework Bindable to drop specific storage and just use it as a simple wrapper
around Expr. The only value of Bindable is to force a static cast when used by
the user to bind into the emitter. For all intended purposes, Bindable is just
a lightweight check that an Expr is Unbound. This simplifies usage and reduces
the API footprint. After playing with it for some time, it wasn't worth the API
cognition overhead;
3. Replace makeExprs and makeBindables by makeNewExprs and copyExprs which is
more explicit and less easy to misuse;
4. Add generally useful functionality to MLIREmitter:
a. expose zero and one for the ubiquitous common lower bounds and step;
b. add support to create already bound Exprs for all function arguments as
well as shapes and views for Exprs bound to memrefs.
5. Delete Stmt::operator= and replace by a `Stmt::set` method which is more
explicit.
6. Make Stmt::operator Expr() explicit.
7. Indexed.indices assertions are removed to pave the way for expressing slices
and views as well as to work with 0-D memrefs.
The CL plugs those simplifications with TableGen and allows emitting a full MLIR function for
pointwise add.
This "x.add" op is both type and rank-agnostic (by allowing ArrayRef of Expr
passed to For loops) and opens the door to spinning up a composable library of
existing and custom ops that should automate a lot of the tedious work in
TF/XLA -> MLIR.
Testing needs to be significantly improved but can be done in a separate CL.
PiperOrigin-RevId: 231982325
This CL also introduces a set of python bindings using pybind11. The bindings
are exercised using a `test_py2andpy3.py` test suite that works for both
python 2 and 3.
`test_py3.py` on the other hand uses the more idiomatic,
python 3 only "PEP 3132 -- Extended Iterable Unpacking" to implement a rank
and type-agnostic copy with transposition.
Because python assignment is by reference, we cannot easily make the
assignment operator use the same type of sugaring as in C++; i.e. the
following:
```cpp
Stmt block = edsc::Block({
For(ivs, zeros, shapeA, ones, {
C[ivs] = IA[ivs] + IB[ivs]
})});
```
has no equivalent in the native Python EDSCs at this time.
However, the sugaring can be built as a simple DSL in python and is left as
future work.
PiperOrigin-RevId: 231337667