Beaver (beaver v0.4.0)

This module contains top level functions and macros for Beaver DSL for MLIR.

use Beaver will import and alias the essential modules and functions for Beaver'DSL for MLIR. It also imports the sigils used in the DSL.

Basic usage

To create an MLIR operation and insert it to a block, you can use the mlir macro. Here is an example of creating a constant operation:

iex> use Beaver
iex> {ctx, blk} = {MLIR.Context.create(), MLIR.Block.create()}
iex> alias Beaver.MLIR.Dialect.Arith
iex> const = mlir ctx: ctx, blk: blk do
iex>   Arith.constant(value: ~a{64: i32}) >>> ~t{i32}
iex> end
iex> const |> MLIR.Value.owner!() |> MLIR.verify!()
iex> MLIR.Block.destroy(blk)
iex> MLIR.Context.destroy(ctx)

Naming conventions

There are some concepts in Elixir and MLIR shared the same name, so it is encouraged to use the following naming conventions for variables:

  • use ctx for MLIR context
  • use blk for MLIR block

Lazy creation of MLIR entities

In Beaver, various functions and sigils might return a function with a signature like MLIR.Context.t() -> MLIR.Type.t(), if the context is not provided. When a creator gets passed to a SSA expression, it will be called with the context to create the entity or later the operation. Deferring the creation of the entity until context available is intended to keep the DSL code clean and succinct. For more information, see the docs of module Beaver.Deferred.

Summary

Functions

Create an MLIR SSA expression with the given arguments and results.

Create an anonymous block.

Create a named block.

Transform the given DSL block but without specifying the context and block.

Macro where Beaver's MLIR DSL expressions get transformed to MLIR API calls.

Create MLIR region. Calling the macro within a do block will create an operation with the region.

Functions

call >>> results

Create an MLIR SSA expression with the given arguments and results.

The right hand of operator >>> is used to typing the result of the SSA or an argument of a block. It kind of works like the :: in specs and types of Elixir.

The return of SSA expression

By default SSA expression will return the MLIR values or the operation created.

  • For op with multiple result: the results of this op in a list.
  • For op with one single result: the result
  • For op with no result: the op itself (for instance, module, func, and terminators)

Different result declarations

There are several ways to declare the result of an SSA expression. The most common cases are single result and multi-results.

  • Single result

    res = Foo.bar(a, b) >>> res_t
  • Multiple results

    [res1, res2] = Foo.bar(a, b) >>> [res_t, res_t]
    [res1, res2] = Foo.bar(a, b) >>> res_t_list
    res_list = Foo.bar(a, b) >>> res_t_list
  • Zero result

    Foo.bar(a, b) >>> []
  • Enable type inference,

    Foo.bar(a, b) >>> :infer
  • To return the op together with the result

    {op, res} = Foo.bar(a, b) >>> {:op, types}

block(list)

(macro)

Create an anonymous block.

The block can be used to call CAPI.

block(call, list)

(macro)

Create a named block.

The block macro works like the function definition in Elixir, and in the do block of block macro you can reference an argument by name. It should follow Elixir's lexical scoping rules and can be referenced by Beaver.Env.block/1

MLIR doesn't have named block

Note that the idea of named block is Beaver's concept. MLIR doesn't have it.

mlir(list)

(macro)

Transform the given DSL block but without specifying the context and block.

This is useful when you want to generate partials of quoted code and doesn't want to pass around the context and block.

mlir(opts, list)

(macro)

Macro where Beaver's MLIR DSL expressions get transformed to MLIR API calls.

This transformation will works on any expression of the >>> form, so it is also possible to call any other vanilla Elixir function/macro.

How it works under the hood

[res0, res1] = TestDialect.some_op(operand0, operand1, attr0: ~a{11 : i32}) >>> ~t{f32}

will be transform to:

[res0, res1] =
  %SSA{}
  |> SSA.arguments([operand0, operand1, attr0: ~a{11 : i32}, attr1: ~a{11 : i32}])
  |> SSA.results([~t{f32}])
  |> TestDialect.some_op()

and TestDialect.some_op() is an Elixir function to actually create the MLIR operation. So it is possible to replace the left hand of the >>> operator with any Elixir function.

region(list)

(macro)

Create MLIR region. Calling the macro within a do block will create an operation with the region.

One caveat is that if it is a Op with region, it requires all arguments to be passed in one list to make it to call the macro version of the Op creation function.

TestDialect.op_with_region [operand0, attr0: ~a{1}i32] do
  region do
    block(arg >>> ~t{f32}) do
      TestDialect.some_op(arg) >>> ~t{f32}
    end
  end
end >>> ~t{f32}