From 8421d79259f201d0f0435eebf182caddab2ce65c Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 00:29:05 +0800 Subject: [PATCH 01/19] overhaul of namespaces --- CONTRIBUTING.md | 159 ++++++++++++++ README.md | 147 +------------ bench/enif_add.ex | 4 +- bench/enif_support.ex | 14 +- guides/your-first-beaver-compiler.livemd | 4 +- lib/beaver.ex | 198 +++++++++++------- .../{diagnostics_capturer.ex => capturer.ex} | 2 +- lib/beaver/{mlir/operation => }/changeset.ex | 2 +- lib/beaver/{mlir/pass => }/composer.ex | 4 +- lib/beaver/deferred.ex | 8 +- lib/beaver/{dsl => }/env.ex | 18 +- lib/beaver/{exterior => }/exterior.ex | 7 - lib/beaver/exterior/README.md | 1 - lib/beaver/exterior/dialects.ex | 5 - lib/beaver/exterior/elixir.ex | 12 ++ .../{mlir/pass/composer => }/generator.ex | 12 +- lib/beaver/mlir.ex | 166 +++++++++++++-- lib/beaver/mlir/attribute.ex | 15 +- lib/beaver/mlir/block.ex | 2 + lib/beaver/mlir/capi.ex | 36 ++-- lib/beaver/mlir/capi_codegen.ex | 2 +- lib/beaver/mlir/capi_kinds.ex | 4 +- lib/beaver/mlir/context.ex | 21 +- lib/beaver/mlir/conversion.ex | 2 +- lib/beaver/mlir/diagnostic.ex | 5 +- lib/beaver/mlir/dialect/arith.ex | 16 +- lib/beaver/mlir/dialect/func.ex | 2 +- lib/beaver/mlir/execution_engine.ex | 16 +- lib/beaver/mlir/external_pass.ex | 5 +- lib/beaver/mlir/location.ex | 2 +- lib/beaver/mlir/module.ex | 31 ++- lib/beaver/mlir/named_attribute.ex | 6 + lib/beaver/mlir/operation.ex | 58 +---- lib/beaver/mlir/operation/state.ex | 22 +- lib/beaver/mlir/pass_manager.ex | 3 + lib/beaver/mlir/pattern.ex | 56 ----- lib/beaver/mlir/string_ref.ex | 22 +- lib/beaver/mlir/transform.ex | 8 + lib/beaver/mlir/transforms.ex | 8 - lib/beaver/mlir/type.ex | 74 ++++--- lib/beaver/mlir/value.ex | 5 +- lib/beaver/pass_runner.ex | 2 - lib/beaver/{dsl => }/pattern.ex | 2 +- lib/beaver/{string_printer.ex => printer.ex} | 2 +- lib/beaver/{mlir => }/sigils.ex | 18 +- lib/beaver/slang.ex | 77 +++---- lib/beaver/{dsl => }/ssa.ex | 0 lib/beaver/walker.ex | 45 +++- mix.exs | 27 +-- mix.lock | 4 + native/src/string_ref.zig | 7 +- profile/cuda_runtime_overhead.exs | 6 +- test/arith_test.exs | 53 +++-- test/block_test.exs | 35 +++- test/bytecode_test.exs | 6 +- test/capi_test.exs | 72 ++++--- test/cf_test.exs | 21 +- test/cuda_runtime_test.exs | 6 +- test/debug_output_test.exs | 16 +- test/diagnostic_test.exs | 8 +- test/dialect_registry_test.exs | 3 +- test/e2e_test.exs | 6 +- test/elixir_ast_dialect_test.exs | 6 +- test/enif_test.exs | 2 +- test/entity_test.exs | 11 +- test/exterior_test.exs | 5 +- test/gen_ir_test.exs | 2 +- test/gpu_test.exs | 8 +- test/irdl_test.exs | 12 +- test/load_ir_test.exs | 4 +- test/memref_test.exs | 16 +- test/op_test.exs | 4 +- test/pass_test.exs | 24 +-- test/pdl_test.exs | 34 +-- test/redundant_transpose_test.exs | 8 +- test/region_test.exs | 4 +- test/string_ref_test.exs | 18 +- test/support/add_two_integers.ex | 1 + test/support/beaver_case.ex | 4 +- test/support/cmath_ir.ex | 5 +- test/support/dummy.ex | 3 +- test/support/elixir_ast_dialect.ex | 30 +-- test/support/test_tosa_patterns.ex | 1 + test/support/toy_pass.ex | 2 +- test/support/translate.ex | 9 +- test/tosa_test.exs | 25 ++- test/type_infer_test.exs | 6 +- 87 files changed, 1050 insertions(+), 794 deletions(-) create mode 100644 CONTRIBUTING.md rename lib/beaver/{diagnostics_capturer.ex => capturer.ex} (97%) rename lib/beaver/{mlir/operation => }/changeset.ex (98%) rename lib/beaver/{mlir/pass => }/composer.ex (98%) rename lib/beaver/{dsl => }/env.ex (68%) rename lib/beaver/{exterior => }/exterior.ex (68%) delete mode 100644 lib/beaver/exterior/README.md delete mode 100644 lib/beaver/exterior/dialects.ex rename lib/beaver/{mlir/pass/composer => }/generator.ex (83%) create mode 100644 lib/beaver/mlir/named_attribute.ex delete mode 100644 lib/beaver/mlir/pattern.ex create mode 100644 lib/beaver/mlir/transform.ex delete mode 100644 lib/beaver/mlir/transforms.ex rename lib/beaver/{dsl => }/pattern.ex (99%) rename lib/beaver/{string_printer.ex => printer.ex} (96%) rename lib/beaver/{mlir => }/sigils.ex (80%) rename lib/beaver/{dsl => }/ssa.ex (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..33c5192fc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,159 @@ +# Contributing to Beaver +This document describes how to contribute to Beaver by introducing the idea behind how Beaver group its functionalities and how to set up the development environment. + +## Beaver's functionalities + +There are three main parts in Beaver. Here is a brief introduction to each part: +- DSL: The syntax of Beaver SSA expression. +- Utilities: Work with MLIR in Elixir way. +- Bindings: Thin wrapper of MLIR CAPI. + +### DSL +Modules including `Beaver`, `Beaver.Env`. + +DSL is the core part of Beaver. It uses Elixir's syntax to express MLIR semantics. + +### Utilities +Modules including `Beaver.Walker`, `Beaver.Composer` + +Utilities are the helper functions that help to generate or manipulate MLIR IR. They are implemented in Elixir and is designed to be used in the DSL part to further enhance it and improve ergonomics. + +### Bindings +Modules including `Beaver.MLIR`, `Beaver.MLIR.Dialect`, `Beaver.MLIR.Pass`, `Beaver.MLIR.Transforms`, `Beaver.MLIR.ExecutionEngine` + +Bindings are the part that provides the interface to the MLIR CAPIs. It is implemented in Zig and is responsible for calling MLIR functions. Note that Beaver's bindings will try not to use `TableGen` and instead try to make use Elixir and Zig's meta-programming features to generate the bindings. + +## Development + +1. Install Elixir, https://elixir-lang.org/install.html +2. Install Zig, https://ziglang.org/learn/getting-started/#installing-zig +3. Install LLVM/MLIR + +- Option 1: Install with pip + + ```bash + python3 -m pip install -r dev-requirements.txt + export LLVM_CONFIG_PATH=$(python3 -c 'import mlir;print(mlir.__path__[0])')/bin/llvm-config + ``` + +- Option 2: Build from source https://mlir.llvm.org/getting_started/ + Recommended install commands: + + ```bash + cmake -B build -S llvm -G Ninja -DLLVM_ENABLE_PROJECTS=mlir \ + -DLLVM_TARGETS_TO_BUILD="host" \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DLLVM_ENABLE_OCAMLDOC=OFF \ + -DLLVM_ENABLE_BINDINGS=OFF \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=${HOME}/llvm-install + cmake --build build -t install + export LLVM_CONFIG_PATH=$HOME/llvm-install/bin/llvm-config + ``` + + (Optional) To use Vulkan: + + - Install Vulkan SDK (global installation is required), reference: https://vulkan.lunarg.com/sdk/home + - Setting environment variable by adding commands these to your bash/zsh profile: + + ``` + # you might need to change the version here + cd $HOME/VulkanSDK/1.3.216.0/ + source setup-env.sh + cd - + ``` + + - Use `vulkaninfo` and `vkvia` to verify Vulkan is working + - Add `-DMLIR_ENABLE_VULKAN_RUNNER=ON` in LLVM CMake config command + +4. Develop and run tests +- Clone this repo and `kinda` in the same directory + ```bash + git clone https://github.com/beaver-lodge/beaver.git + git clone https://github.com/beaver-lodge/kinda.git + ``` +- Make sure LLVM environment variable is set properly, otherwise it might fail to build + + ```bash + echo $LLVM_CONFIG_PATH + ``` + +- Build and run Elixir tests + ```bash + mix deps.get + BEAVER_BUILD_CMAKE=1 mix test + # run tests with filters + mix test --exclude vulkan # use this to skip vulkan tests + mix test --only smoke + mix test --only nx + ``` + +5. debug + +- setting environment variable to control Erlang scheduler number, `ERL_AFLAGS="+S 10:5"` +- run mix test under LLDB, `scripts/lldb-mix-test` + +## Release a new version + +### Update Elixir source + +- Bump versions in [`README.md`](README.md) and [`mix.exs`](/mix.exs) + +### Linux + +- Run CI, which generates the new GitHub release uploaded to https://github.com/beaver-lodge/beaver-prebuilt/releases. +- Update release url in [`mix.exs`](/mix.exs) + +### Mac + +- Run macOS build with: + + ```bash + rm -rf _build/prod + bash scripts/build-for-publish.sh + ``` + +- Upload the `beaver-nif-[xxx].tar.gz` file to release + +### Generate `checksum.exs` + +``` +rm checksum.exs +mix clean +mix +mix elixir_make.checksum --all --ignore-unavailable --print +``` + +Check the version in the output is correct. + +### Publish to Hex + +``` +BEAVER_BUILD_CMAKE=1 mix hex.publish +``` + +## Format CMake files + +```bash +python3 -m pip install cmake-format +cmake-format -i native/**/CMakeLists.txt native/**/*.cmake +``` + +## Erlang apps in Beaver + +LLVM/MLIR is a giant project, and built around that Beaver have thousands of functions. To properly ship LLVM/MLIR and streamline the development process, we need to carefully break the functionalities at different level into different Erlang apps under the same umbrella. + +- `:beaver`: Elixir and C/C++ hybrid. + - Top level app ships the high level functionalities including IR generation and pattern definition. + - MLIR CAPI wrappers built by parsing LLVM/MLIR CAPI C headers and some middle level helper functions to hide the C pointer related operations. This app will add the loaded MLIR C library and managed MLIR context to Erlang supervisor tree. Rust is also used in this app, but mainly for LLVM/MLIR CMake integration. + - All the Ops defined in stock MLIR dialects, built by querying the registry. This app will ship MLIR Ops with Erlang idiomatic practices like behavior compliance. +- `:kinda`: Elixir and Zig hybrid, generating NIFs from MLIR C headers. Repo: https://github.com/beaver-lodge/kinda + +## Miscellaneous + +Some other notes on consuming and development + +- Only `:beaver` and `:kinda` are designed to be used as stand-alone app being directly consumed by other apps. +- `:manx` could only work with Nx. +- Although `:kinda` is built for Beaver, any Erlang/Elixir app with interest bundling some C API could take advantage of it as well. diff --git a/README.md b/README.md index 0276a8441..d842c997a 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,16 @@ defmodule ToyPass do with 1 <- Beaver.Walker.regions(operation) |> Enum.count(), {:ok, _} <- MLIR.Pattern.apply_(MLIR.Module.from_operation(operation), [replace_add_op(benefit: 2)]) do - MLIR.dump!(operation) + :ok + else + _ -> raise "unreachable" end end end +use Beaver +import MLIR.Transforms +ctx = MLIR.Context.create() ~m""" module { func.func @tosa_add(%arg0: tensor<1x3xf32>, %arg1: tensor<2x1xf32>) -> tensor<2x3xf32> { @@ -79,9 +84,9 @@ module { } } """.(ctx) -|> MLIR.Pass.Composer.append(ToyPass) +|> Beaver.Composer.append(ToyPass) |> canonicalize -|> MLIR.Pass.Composer.run!() +|> Beaver.Composer.run!() ``` ## Goals @@ -151,26 +156,7 @@ import_deps: [:beaver], ### Projects built on top of Beaver - [Charm](https://github.com/beaver-lodge/charms): Compile a subset of Elixir to native targets. -- [MLIR Accelerated Nx](https://github.com/beaver-lodge/manx): A backend for [Nx](https://github.com/elixir-nx/nx/tree/main/nx#readme). - -## Erlang apps in Beaver - -LLVM/MLIR is a giant project, and built around that Beaver have thousands of functions. To properly ship LLVM/MLIR and streamline the development process, we need to carefully break the functionalities at different level into different Erlang apps under the same umbrella. - -- `:beaver`: Elixir and C/C++ hybrid. - - Top level app ships the high level functionalities including IR generation and pattern definition. - - MLIR CAPI wrappers built by parsing LLVM/MLIR CAPI C headers and some middle level helper functions to hide the C pointer related operations. This app will add the loaded MLIR C library and managed MLIR context to Erlang supervisor tree. Rust is also used in this app, but mainly for LLVM/MLIR CMake integration. - - All the Ops defined in stock MLIR dialects, built by querying the registry. This app will ship MLIR Ops with Erlang idiomatic practices like behavior compliance. -- `:kinda`: Elixir and Zig hybrid, generating NIFs from MLIR C headers. Repo: https://github.com/beaver-lodge/kinda - -### Notes on consuming and development - -- Only `:beaver` and `:kinda` are designed to be used as stand-alone app being directly consumed by other apps. -- `:manx` could only work with Nx. -- Although `:kinda` is built for Beaver, any Erlang/Elixir app with interest bundling some C API could take advantage of it as well. -- The namespace `Beaver.MLIR` is for standard features are generally expected in any MLIR tools. -- The namespace `Beaver` is for concepts and practice only exists in Beaver, which are mostly in a DSL provided as a set of macros (including `mlir/0`, `block/1`, `defpat/2`, etc). The implementations are usually under `Beaver.DSL` namespace. -- In Beaver, there is no strict requirements on the consistency between the Erlang app name and Elixir module name. Two modules with same namespace prefix could locate in different Erlang apps (this happens a lot to the `Beaver.MLIR` namespace). Of course redefinition of Elixir modules with an identical name should be avoided. +- [MLIR Accelerated Nx](https://github.com/beaver-lodge/manx): A backend for [Nx](https://github.com/elixir-nx/nx/). ## How it works? @@ -280,117 +266,4 @@ Usually a function accepting a MLIR context to create an operation or type is ca ## Development -1. Install Elixir, https://elixir-lang.org/install.html -2. Install Zig, https://ziglang.org/learn/getting-started/#installing-zig -3. Install LLVM/MLIR - -- Option 1: Install with pip - - ```bash - python3 -m pip install -r dev-requirements.txt - export LLVM_CONFIG_PATH=$(python3 -c 'import mlir;print(mlir.__path__[0])')/bin/llvm-config - ``` - -- Option 2: Build from source https://mlir.llvm.org/getting_started/ - Recommended install commands: - - ```bash - cmake -B build -S llvm -G Ninja -DLLVM_ENABLE_PROJECTS=mlir \ - -DLLVM_TARGETS_TO_BUILD="host" \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DLLVM_ENABLE_ASSERTIONS=ON \ - -DLLVM_ENABLE_OCAMLDOC=OFF \ - -DLLVM_ENABLE_BINDINGS=OFF \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_INSTALL_PREFIX=${HOME}/llvm-install - cmake --build build -t install - export LLVM_CONFIG_PATH=$HOME/llvm-install/bin/llvm-config - ``` - - (Optional) To use Vulkan: - - - Install Vulkan SDK (global installation is required), reference: https://vulkan.lunarg.com/sdk/home - - Setting environment variable by adding commands these to your bash/zsh profile: - - ``` - # you might need to change the version here - cd $HOME/VulkanSDK/1.3.216.0/ - source setup-env.sh - cd - - ``` - - - Use `vulkaninfo` and `vkvia` to verify Vulkan is working - - Add `-DMLIR_ENABLE_VULKAN_RUNNER=ON` in LLVM CMake config command - -4. Develop and run tests -- Clone this repo and `kinda` in the same directory - ```bash - git clone https://github.com/beaver-lodge/beaver.git - git clone https://github.com/beaver-lodge/kinda.git - ``` -- Make sure LLVM environment variable is set properly, otherwise it might fail to build - - ```bash - echo $LLVM_CONFIG_PATH - ``` - -- Build and run Elixir tests - ```bash - mix deps.get - BEAVER_BUILD_CMAKE=1 mix test - # run tests with filters - mix test --exclude vulkan # use this to skip vulkan tests - mix test --only smoke - mix test --only nx - ``` - -5. debug - -- setting environment variable to control Erlang scheduler number, `ERL_AFLAGS="+S 10:5"` -- run mix test under LLDB, `scripts/lldb-mix-test` - -## Release a new version - -### Update Elixir source - -- Bump versions in [`README.md`](README.md) and [`mix.exs`](/mix.exs) - -### Linux - -- Run CI, which generates the new GitHub release uploaded to https://github.com/beaver-lodge/beaver-prebuilt/releases. -- Update release url in [`mix.exs`](/mix.exs) - -### Mac - -- Run macOS build with: - - ```bash - rm -rf _build/prod - bash scripts/build-for-publish.sh - ``` - -- Upload the `beaver-nif-[xxx].tar.gz` file to release - -### Generate `checksum.exs` - -``` -rm checksum.exs -mix clean -mix -mix elixir_make.checksum --all --ignore-unavailable --print -``` - -Check the version in the output is correct. - -### Publish to Hex - -``` -BEAVER_BUILD_CMAKE=1 mix hex.publish -``` - -## (Optional) Format CMake files - -```bash -python3 -m pip install cmake-format -cmake-format -i native/**/CMakeLists.txt native/**/*.cmake -``` +Please refer to [Beaver's contributing guide](/CONTRIBUTING.md) diff --git a/bench/enif_add.ex b/bench/enif_add.ex index 3478587cb..0938f56aa 100644 --- a/bench/enif_add.ex +++ b/bench/enif_add.ex @@ -13,11 +13,11 @@ defmodule AddENIF do s_table = op |> MLIR.Operation.from_module() |> MLIR.CAPI.mlirSymbolTableCreate() found = MLIR.CAPI.mlirSymbolTableLookup(s_table, MLIR.StringRef.create("enif_make_int64")) - if MLIR.is_null(found) do + if MLIR.null?(found) do raise "Function not found" end - if not MLIR.Dialect.Func.is_external(found) do + if not MLIR.Dialect.Func.external?(found) do raise "Function is not external" end diff --git a/bench/enif_support.ex b/bench/enif_support.ex index c300391e2..466ff2f03 100644 --- a/bench/enif_support.ex +++ b/bench/enif_support.ex @@ -15,7 +15,7 @@ defmodule ENIFSupport do @behaviour ENIFSupport def init(ctx) do create(ctx) - |> MLIR.Operation.verify!() + |> MLIR.verify!() |> after_verification() |> ENIFSupport.lower() end @@ -25,17 +25,17 @@ defmodule ENIFSupport do @print_flag "ENIF_SUPPORT_PRINT_IR" def lower(op) do op - |> then(&if(System.get_env(@print_flag), do: MLIR.Transforms.print_ir(&1), else: &1)) - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") + |> then(&if(System.get_env(@print_flag), do: MLIR.Transform.print_ir(&1), else: &1)) + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") |> convert_scf_to_cf |> convert_arith_to_llvm() |> convert_index_to_llvm() |> convert_func_to_llvm() - |> MLIR.Pass.Composer.append("convert-vector-to-llvm{reassociate-fp-reductions}") - |> MLIR.Pass.Composer.append("finalize-memref-to-llvm") - |> then(&if(System.get_env(@print_flag), do: MLIR.Transforms.print_ir(&1), else: &1)) + |> Beaver.Composer.append("convert-vector-to-llvm{reassociate-fp-reductions}") + |> Beaver.Composer.append("finalize-memref-to-llvm") + |> then(&if(System.get_env(@print_flag), do: MLIR.Transform.print_ir(&1), else: &1)) |> reconcile_unrealized_casts - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() |> then(fn m -> e = MLIR.ExecutionEngine.create!(m, opt_level: 3) |> Beaver.ENIF.register_symbols() %ENIFSupport{mod: m, engine: e} diff --git a/guides/your-first-beaver-compiler.livemd b/guides/your-first-beaver-compiler.livemd index 50661173f..dd3e7554f 100644 --- a/guides/your-first-beaver-compiler.livemd +++ b/guides/your-first-beaver-compiler.livemd @@ -68,9 +68,9 @@ import MLIR.Conversion llvm_ir = ir |> convert_arith_to_llvm - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") |> convert_func_to_llvm - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() llvm_ir |> Beaver.MLIR.to_string() |> IO.puts() ``` diff --git a/lib/beaver.ex b/lib/beaver.ex index be8a272b3..c8d849882 100644 --- a/lib/beaver.ex +++ b/lib/beaver.ex @@ -1,81 +1,45 @@ defmodule Beaver do alias Beaver.MLIR + require Beaver.Env @moduledoc """ This module contains top level functions and macros for Beaver DSL for MLIR. - Here are some of the examples of most common forms of Beaver DSL: - - single result - ``` - res = TOSA.add(a, b) >>> res_t - ``` - - multiple results - ``` - [res1, res2] = TOSA.add(a, b) >>> [res_t, res_t] - [res1, res2] = TOSA.add(a, b) >>> res_t_list - res_list = TOSA.add(a, b) >>> res_t_list - ``` - - infer results - ``` - TOSA.add(a, b) >>> :infer - ``` - - with op - ``` - {op, res} = TOSA.add(a, b) >>> {:op, res_t} - ``` - - with no result - ``` - TOSA.add(a, b) >>> [] - ``` + `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`. """ defmacro __using__(_) do quote do import Beaver alias Beaver.MLIR - import MLIR.Sigils + import Beaver.Sigils end end @doc """ - This is a macro where Beaver's MLIR DSL expressions get transformed to MLIR API calls. - This transformation will works on any expression of this form, so it is also possible to call any other function/macro rather than an Op creation function. There is one operator `>>>` for typing the result of the SSA or an argument of a block. It kind of works like the `::` in specs and types of Elixir. + Transform the given DSL block but without specifying the context and block. - ## How it works under the hood - ``` - mlir do - [res0, res1] = TestDialect.some_op(operand0, operand1, attr0: ~a{11 : i32}) >>> ~t{f32} - end - # will be transform to: - [res0, res1] = - %DSL.SSA{} - |> DSL.SSA.arguments([operand0, operand1, attr0: ~a{11 : i32}, attr1: ~a{11 : i32}]) - |> DSL.SSA.results([~t{f32}]) - |> TestDialect.some_op() - ``` - The SSA form will return: - - 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) - - If there is no returns, add a `[]` to make the transformation effective: - ``` - TestDialect.some_op(operand0) >>> [] - ``` - To defer the creation of a terminator in case its successor block has not been created. You can pass an atom of the name in the block's call form. - ``` - CF.cond_br(cond0, :bb1, {:bb2, [v0]}) >>> [] - ``` - To create region, call the op with a do 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. 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} - ``` + This is useful when you want to generate partials of quoted code and doesn't want to pass around the context and block. """ defmacro mlir(do: dsl_block) do quote do @@ -85,6 +49,26 @@ defmodule Beaver do end end + @doc """ + 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. + """ defmacro mlir(opts, do: dsl_block) do dsl_block_ast = dsl_block |> Beaver.SSA.prewalk(&MLIR.Operation.eval_ssa/1) @@ -94,7 +78,8 @@ defmodule Beaver do Kernel.var!(beaver_internal_env_ctx) = unquote(ctx) match?(%MLIR.Context{}, Kernel.var!(beaver_internal_env_ctx)) || - raise Beaver.EnvNotFoundError, MLIR.Context + raise CompileError, + Beaver.Env.compile_err_msg(MLIR.Context, unquote(Macro.escape(__CALLER__))) end end @@ -104,7 +89,8 @@ defmodule Beaver do Kernel.var!(beaver_internal_env_block) = unquote(block) match?(%MLIR.Block{}, Kernel.var!(beaver_internal_env_block)) || - raise Beaver.EnvNotFoundError, MLIR.Block + raise CompileError, + Beaver.Env.compile_err_msg(MLIR.Block, unquote(Macro.escape(__CALLER__))) end end @@ -112,7 +98,7 @@ defmodule Beaver do require Beaver.Env alias Beaver.MLIR alias Beaver.MLIR.{Type, Attribute, ODS} - import Beaver.MLIR.Sigils + import Beaver.Sigils import Beaver.MLIR.Dialect.Builtin unquote(ctx_ast) @@ -148,16 +134,6 @@ defmodule Beaver do end end - defmodule EnvNotFoundError do - defexception [:message] - - @impl true - def exception(type) when type in [MLIR.Context, MLIR.Region, MLIR.Block] do - msg = "no valid #{inspect(type)} in the environment" - %EnvNotFoundError{message: msg} - end - end - @doc false def not_found(%Macro.Env{} = env) do {:not_found, [file: env.file, line: env.line]} @@ -185,6 +161,11 @@ defmodule Beaver do end end + @doc """ + Create an anonymous block. + + The block can be used to call CAPI. + """ defmacro block(do: body) do quote do block _() do @@ -193,6 +174,16 @@ defmodule Beaver do end end + @doc """ + 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 {: .info} + > + > Note that the idea of named block is Beaver's concept. MLIR doesn't have it. + """ defmacro block(call, do: body) do {b_name, args} = Macro.decompose_call(call) if not is_atom(b_name), do: raise("block name must be an atom or underscore") @@ -217,6 +208,20 @@ defmodule Beaver do end end + @doc """ + 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} + ``` + """ defmacro region(do: body) do regions = if Macro.Env.has_var?(__CALLER__, {:beaver_internal_env_regions, nil}) do @@ -247,6 +252,45 @@ defmodule Beaver do end end + @doc """ + 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} + ``` + + """ def _call >>> _results do raise( "`>>>` operator is expected to be transformed away. Maybe you forget to put the expression inside the Beaver.mlir/1 macro's do block?" @@ -274,14 +318,14 @@ defmodule Beaver do when is_function(fun, 0) and is_function(init, 0) and is_function(handler, 2) do {:ok, pid} = GenServer.start( - Beaver.DiagnosticsCapturer, + Beaver.Capturer, handler ) - handler_id = Beaver.DiagnosticsCapturer.attach(ctx, pid) + handler_id = Beaver.Capturer.attach(ctx, pid) try do - {fun.(), Beaver.DiagnosticsCapturer.collect(pid)} + {fun.(), Beaver.Capturer.collect(pid)} after Beaver.MLIR.Diagnostic.detach(ctx, handler_id) :ok = GenServer.stop(pid) diff --git a/lib/beaver/diagnostics_capturer.ex b/lib/beaver/capturer.ex similarity index 97% rename from lib/beaver/diagnostics_capturer.ex rename to lib/beaver/capturer.ex index 4400101f5..9e893fc89 100644 --- a/lib/beaver/diagnostics_capturer.ex +++ b/lib/beaver/capturer.ex @@ -1,4 +1,4 @@ -defmodule Beaver.DiagnosticsCapturer do +defmodule Beaver.Capturer do alias Beaver.MLIR use GenServer diff --git a/lib/beaver/mlir/operation/changeset.ex b/lib/beaver/changeset.ex similarity index 98% rename from lib/beaver/mlir/operation/changeset.ex rename to lib/beaver/changeset.ex index f2f9a5e51..9688ac691 100644 --- a/lib/beaver/mlir/operation/changeset.ex +++ b/lib/beaver/changeset.ex @@ -1,4 +1,4 @@ -defmodule Beaver.MLIR.Operation.Changeset do +defmodule Beaver.Changeset do @moduledoc false alias Beaver.MLIR diff --git a/lib/beaver/mlir/pass/composer.ex b/lib/beaver/composer.ex similarity index 98% rename from lib/beaver/mlir/pass/composer.ex rename to lib/beaver/composer.ex index 617d8ca7a..92e09fb93 100644 --- a/lib/beaver/mlir/pass/composer.ex +++ b/lib/beaver/composer.ex @@ -1,4 +1,4 @@ -defmodule Beaver.MLIR.Pass.Composer do +defmodule Beaver.Composer do import Beaver.MLIR.CAPI alias Beaver.MLIR require Logger @@ -46,7 +46,7 @@ defmodule Beaver.MLIR.Pass.Composer do defp add_pipeline(%MLIR.OpPassManager{} = pm, pipeline_str) when is_binary(pipeline_str) do {res, err} = - Beaver.StringPrinter.run( + Beaver.Printer.run( &mlirOpPassManagerAddPipeline( pm, MLIR.StringRef.create(pipeline_str), diff --git a/lib/beaver/deferred.ex b/lib/beaver/deferred.ex index 610210ccf..d6e014786 100644 --- a/lib/beaver/deferred.ex +++ b/lib/beaver/deferred.ex @@ -18,12 +18,16 @@ defmodule Beaver.Deferred do @spec fetch_context(opts :: opts) :: MLIR.Context.t() | Macro.t() | nil def fetch_context(opts) do - opts[:ctx] || opts[:context] + opts[:ctx] end @spec fetch_block(opts :: opts) :: MLIR.Block.t() | Macro.t() | nil def fetch_block(opts) do - opts[:blk] || opts[:block] + if opts[:block] do + raise ArgumentError, "use :blk instead of :block as key" + end + + opts[:blk] end def create({:parametric, _, _, f}, ctx) when is_function(f) and not is_nil(ctx) do diff --git a/lib/beaver/dsl/env.ex b/lib/beaver/env.ex similarity index 68% rename from lib/beaver/dsl/env.ex rename to lib/beaver/env.ex index 87348a963..d01d7acac 100644 --- a/lib/beaver/dsl/env.ex +++ b/lib/beaver/env.ex @@ -2,6 +2,7 @@ defmodule Beaver.Env do @moduledoc """ This module defines macros to getting MLIR context, region, block within the do block of mlir/1. It works like __MODULE__/0, __CALLER__/0 of Elixir special forms. """ + alias Beaver.MLIR @doc """ Return context in the DSL environment @@ -10,12 +11,13 @@ defmodule Beaver.Env do if Macro.Env.has_var?(__CALLER__, {:beaver_internal_env_ctx, nil}) do quote do match?(%Beaver.MLIR.Context{}, Kernel.var!(beaver_internal_env_ctx)) || - raise Beaver.EnvNotFoundError, Beaver.MLIR.Context + raise CompileError, + Beaver.Env.compile_err_msg(Beaver.MLIR.Context, unquote(Macro.escape(__CALLER__))) Kernel.var!(beaver_internal_env_ctx) end else - raise Beaver.EnvNotFoundError, Beaver.MLIR.Context + raise CompileError, Beaver.Env.compile_err_msg(Beaver.MLIR.Context, __CALLER__) end end @@ -26,7 +28,8 @@ defmodule Beaver.Env do if Macro.Env.has_var?(__CALLER__, {:beaver_env_region, nil}) do quote do match?(%Beaver.MLIR.Region{}, Kernel.var!(beaver_env_region)) || - raise Beaver.EnvNotFoundError, Beaver.MLIR.Region + raise CompileError, + Beaver.Env.compile_err_msg(Beaver.MLIR.Region, unquote(Macro.escape(__CALLER__))) Kernel.var!(beaver_env_region) end @@ -47,7 +50,7 @@ defmodule Beaver.Env do Kernel.var!(beaver_internal_env_block) end else - raise Beaver.EnvNotFoundError, Beaver.MLIR.Block + raise CompileError, Beaver.Env.compile_err_msg(MLIR.Block, __CALLER__) end end @@ -63,4 +66,11 @@ defmodule Beaver.Env do end end end + + @doc false + def compile_err_msg(entity, %Macro.Env{} = env) + when entity in [MLIR.Context, MLIR.Region, MLIR.Block] do + m = Module.split(entity) |> List.last() |> String.downcase() + [file: env.file, line: env.line, description: "no valid #{m} in the environment"] + end end diff --git a/lib/beaver/exterior/exterior.ex b/lib/beaver/exterior.ex similarity index 68% rename from lib/beaver/exterior/exterior.ex rename to lib/beaver/exterior.ex index 9dc76cc04..75cf27044 100644 --- a/lib/beaver/exterior/exterior.ex +++ b/lib/beaver/exterior.ex @@ -8,11 +8,4 @@ defmodule Beaver.Exterior do Register a register to a MLIR context """ @callback register_dialect(MLIR.Context.t()) :: :ok | {:error, String.t()} - - def register_all(ctx) do - # TODO: get it from app config or other mechanism - for dialect <- [Beaver.Exterior.Elixir] do - :ok = dialect.register_dialect(ctx) - end - end end diff --git a/lib/beaver/exterior/README.md b/lib/beaver/exterior/README.md deleted file mode 100644 index 881066e56..000000000 --- a/lib/beaver/exterior/README.md +++ /dev/null @@ -1 +0,0 @@ -# Extending Beaver with additional out-of-tree MLIR dialect diff --git a/lib/beaver/exterior/dialects.ex b/lib/beaver/exterior/dialects.ex deleted file mode 100644 index e8814fffc..000000000 --- a/lib/beaver/exterior/dialects.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Beaver.MLIR.Dialect.Elixir do - use Beaver.MLIR.Dialect, - dialect: "elixir", - ops: ~w{add} -end diff --git a/lib/beaver/exterior/elixir.ex b/lib/beaver/exterior/elixir.ex index 5c57b6491..dc43f3f7d 100644 --- a/lib/beaver/exterior/elixir.ex +++ b/lib/beaver/exterior/elixir.ex @@ -1,4 +1,16 @@ +defmodule Beaver.MLIR.Dialect.Elixir do + @moduledoc """ + This module defines Elixir dialect to represent Elixir AST in MLIR. This is at a moment a placeholder for future development. + """ + use Beaver.MLIR.Dialect, + dialect: "elixir", + ops: ~w{add} +end + defmodule Beaver.Exterior.Elixir do + @moduledoc """ + This module defines the Exterior for Elixir dialect. This is at a moment a placeholder for future development. + """ alias Beaver.MLIR @behaviour Beaver.Exterior def register_dialect(ctx) do diff --git a/lib/beaver/mlir/pass/composer/generator.ex b/lib/beaver/generator.ex similarity index 83% rename from lib/beaver/mlir/pass/composer/generator.ex rename to lib/beaver/generator.ex index 990eadbae..2f8e0558e 100644 --- a/lib/beaver/mlir/pass/composer/generator.ex +++ b/lib/beaver/generator.ex @@ -1,4 +1,4 @@ -defmodule Beaver.MLIR.Pass.Composer.Generator do +defmodule Beaver.ComposerGenerator do @moduledoc false alias Beaver.MLIR alias Beaver.MLIR.CAPI @@ -14,7 +14,7 @@ defmodule Beaver.MLIR.Pass.Composer.Generator do quote bind_quoted: [prefix: prefix] do alias Beaver.MLIR alias Beaver.MLIR.CAPI - alias Beaver.MLIR.Pass.Composer + alias Beaver.Composer # We are calling C functions dynamically at compile time, so we need to make sure managed libraries get loaded. @@ -28,16 +28,16 @@ defmodule Beaver.MLIR.Pass.Composer.Generator do arg_name = pass |> CAPI.beaverPassGetArgument() - |> MLIR.StringRef.to_string() + |> MLIR.to_string() pass_name = pass |> CAPI.beaverPassGetName() - |> MLIR.StringRef.to_string() + |> MLIR.to_string() - normalized_name = MLIR.Pass.Composer.Generator.normalized_name(arg_name) + normalized_name = Beaver.ComposerGenerator.normalized_name(arg_name) - doc = pass |> CAPI.beaverPassGetDescription() |> MLIR.StringRef.to_string() + doc = pass |> CAPI.beaverPassGetDescription() |> MLIR.to_string() @doc """ #{doc} diff --git a/lib/beaver/mlir.ex b/lib/beaver/mlir.ex index fb16ca976..9399c5270 100644 --- a/lib/beaver/mlir.ex +++ b/lib/beaver/mlir.ex @@ -1,10 +1,40 @@ defmodule Beaver.MLIR do + alias Beaver.MLIR.{Attribute, Type, Operation} + alias Beaver.MLIR + import Beaver.MLIR.CAPI + alias Beaver.MLIR.CAPI + @moduledoc """ - This module provides common functions to work with MLIR entities. + Core functionality for working with MLIR. + + This module serves as the primary interface for MLIR operations and entities in Beaver. It provides: + + - String conversion utilities for debugging and serialization + - Null checking for safe operation handling + - Context and location retrieval for MLIR entities + + ## Printing Operations + + When converting operations to strings, you can specify the output format: + - `generic: true` - Uses MLIR's generic format + - `generic: false` - Uses MLIR's specialized format + - `bytecode: true` - Uses MLIR's bytecode format - The functions in this module will leverage pattern matching to extract the entity type and call the corresponding CAPI function. + The default format may vary between MLIR versions, so explicitly specify the format + when consistent output is required. + + ## Inspecting MLIR Entities + Beaver doesn't implement `Inspect` protocol for MLIR entities because the output might be too verbose and can crash the BEAM if invalid entities are passed. Use `Beaver.MLIR.to_string/2` to convert entities to inspect it. + + ## Null Safety + + Many MLIR operations can return null values. Use `null?/1` to safely check entities + before performing operations that require non-null values. + + ## Name spaces to include difference kinds of CAPI delegates + - `Beaver.MLIR.***`: APIs to related to lifecycle, including creating and destroying MLIR entities. + - `Beaver.MLIR`: APIs like `Beaver.MLIR.dump!/1` or `Beaver.MLIR.null?/1`. These are standard features are generally expected in any MLIR tools """ - alias Beaver.MLIR.CAPI defp extract_entity_name(m) do ["Beaver", "MLIR", entity_name] = m |> Module.split() @@ -24,7 +54,7 @@ defmodule Beaver.MLIR do OpPassManager, PassManager, Identifier, - Diagnostic + ExecutionEngine } @doc """ @@ -46,7 +76,7 @@ defmodule Beaver.MLIR do @doc """ Compare two MLIR entities. """ - def equal?(a = %m{}, b = %m{}) do + def equal?(%m{} = a, %m{} = b) do entity_name = extract_entity_name(m) apply(CAPI, :"mlir#{entity_name}Equal", [a, b]) |> Beaver.Native.to_term() end @@ -69,9 +99,15 @@ defmodule Beaver.MLIR do | Module.t() | Block.t() | Dialect.t() + | ExecutionEngine.t() - @spec is_null(nullable()) :: boolean() - def is_null(%m{} = entity) do + @doc """ + Check if an MLIR entity is null. + + To prevent crashing the BEAM, it is encouraged to use this function to check if an entity is null before calling functions that require a non-null entity. + """ + @spec null?(nullable()) :: boolean() + def null?(%m{} = entity) do entity_name = extract_entity_name(m) f = :"beaverIsNull#{entity_name}" function_exported?(CAPI, f, 1) && apply(CAPI, f, [entity]) |> Beaver.Native.to_term() @@ -80,7 +116,7 @@ defmodule Beaver.MLIR do defp not_null_run(%m{} = entity, dumper) do entity_name = extract_entity_name(m) - if is_null(entity) do + if null?(entity) do {:error, "#{entity_name} is null"} else dumper.(entity) @@ -98,23 +134,26 @@ defmodule Beaver.MLIR do | PassManager.t() | Module.t() | Identifier.t() - | Diagnostic.t() @type dump_opts :: [generic: boolean()] @spec dump(printable(), dump_opts()) :: :ok @spec dump!(printable(), dump_opts()) :: any() + @doc """ + Dump MLIR element to stdio. + + This will call the printer registered in C/C++. Note that the outputs wouldn't go through Erlang's IO system, so it's not possible to capture the output in Elixir. If you need to capture the output, use `to_string/1` instead. + """ def dump(mlir, opts \\ []) def dump(%Beaver.MLIR.Module{} = mlir, opts) do - CAPI.mlirModuleGetOperation(mlir) - |> dump(opts) + mlirModuleGetOperation(mlir) |> dump(opts) end def dump(%Operation{} = mlir, opts) do if opts[:generic] do - not_null_run(mlir, &CAPI.beaverOperationDumpGeneric/1) + not_null_run(mlir, &beaverOperationDumpGeneric/1) else - not_null_run(mlir, &CAPI.mlirOperationDump/1) + not_null_run(mlir, &mlirOperationDump/1) end end @@ -123,6 +162,9 @@ defmodule Beaver.MLIR do not_null_run(entity, &apply(CAPI, :"mlir#{entity_name}Dump", [&1])) end + @doc """ + Dump MLIR element to stdio and raise an error if it fails. + """ def dump!(mlir, opts \\ []) def dump!(mlir, opts) do @@ -138,7 +180,9 @@ defmodule Beaver.MLIR do @spec to_string(printable(), dump_opts()) :: :ok @doc """ - Print MLIR element as Elixir binary string. When printing an operation, it is recommended to use `generic: false` or `generic: true` to explicitly specify the format if your usage requires consistent output. If not specified, the default behavior is subject to change according to the MLIR version. + Print MLIR element or StringRef as Elixir binary string. + + When printing an operation, it is recommended to use `generic: false` or `generic: true` to explicitly specify the format if your usage requires consistent output. If not specified, the default behavior is subject to change according to the MLIR version. """ def to_string(mlir, opts \\ []) @@ -165,7 +209,7 @@ defmodule Beaver.MLIR do end def to_string(%PassManager{} = pm, _opts) do - pm |> CAPI.mlirPassManagerGetAsOpPassManager() |> __MODULE__.to_string() + pm |> mlirPassManagerGetAsOpPassManager() |> __MODULE__.to_string() end def to_string(f, opts) when is_function(f) do @@ -176,6 +220,95 @@ defmodule Beaver.MLIR do entity_name = extract_entity_name(m) not_null_run(entity, &apply(CAPI, :"beaver_raw_to_string_#{entity_name}", [&1.ref])) end + + @type verifiable() :: Operation.t() | Module.t() + @spec verify!(verifiable()) :: :ok + def verify!(op) do + case verify(op) do + {:ok, op} -> + op + + :null -> + raise "MLIR operation verification failed because the operation is null. Maybe it is parsed from an ill-formed text format? Please have a look at the diagnostic output above by MLIR C++" + + :fail -> + raise "MLIR operation verification failed" + end + end + + @spec verify(verifiable()) :: {:ok, verifiable()} | :null | :fail + def verify(op) do + if null?(op) do + :null + else + is_success = + MLIR.Operation.from_module(op) + |> mlirOperationVerify() + |> Beaver.Native.to_term() + + if is_success do + {:ok, op} + else + :fail + end + end + end + + @type applicable() :: Operation.t() | Module.t() + @type apply_opt :: {:debug, boolean()} + @apply_default_opts [debug: false] + @spec apply!(applicable(), apply_opt) :: applicable() + @doc """ + Apply patterns on a container (region, operation, module). + It returns the container if it succeeds otherwise it raises. + """ + def apply!(op, patterns, opts \\ @apply_default_opts) do + case apply_(op, patterns, opts) do + {:ok, module} -> + module + + _ -> + raise "failed to apply pattern" + end + end + + @doc """ + Apply patterns on a container (operation, module). + It is named `apply_` with a underscore to avoid name collision with `Kernel.apply/2` + """ + @spec apply_(applicable(), apply_opt) :: {:ok, applicable()} | {:error, String.t()} + def apply_(op, patterns, opts \\ @apply_default_opts) when is_list(patterns) do + if MLIR.null?(op), do: raise("op is null") + ctx = MLIR.Operation.from_module(op) |> MLIR.context() + pattern_module = MLIR.Location.from_env(__ENV__, ctx: ctx) |> MLIR.Module.empty() + block = Beaver.MLIR.Module.body(pattern_module) + + for p <- patterns do + p = p.(ctx, block) + + if opts[:debug] do + p |> MLIR.dump!() + end + end + + MLIR.verify!(pattern_module) + MLIR.verify!(op) + pdl_pat_mod = mlirPDLPatternModuleFromModule(pattern_module) + + frozen_pat_set = + pdl_pat_mod |> mlirRewritePatternSetFromPDLPatternModule() |> mlirFreezeRewritePattern() + + result = beaverModuleApplyPatternsAndFoldGreedily(op, frozen_pat_set) + mlirPDLPatternModuleDestroy(pdl_pat_mod) + mlirFrozenRewritePatternSetDestroy(frozen_pat_set) + MLIR.Module.destroy(pattern_module) + + if MLIR.LogicalResult.success?(result) do + {:ok, op} + else + {:error, "failed to apply pattern set"} + end + end end for m <- [ @@ -189,7 +322,8 @@ for m <- [ Beaver.MLIR.OpPassManager, Beaver.MLIR.PassManager, Beaver.MLIR.Identifier, - Beaver.MLIR.Diagnostic + Beaver.MLIR.Diagnostic, + Beaver.MLIR.StringRef ] do defimpl String.Chars, for: m do defdelegate to_string(mlir), to: Beaver.MLIR diff --git a/lib/beaver/mlir/attribute.ex b/lib/beaver/mlir/attribute.ex index 94f14e6d7..f86975d48 100644 --- a/lib/beaver/mlir/attribute.ex +++ b/lib/beaver/mlir/attribute.ex @@ -1,10 +1,3 @@ -defmodule Beaver.MLIR.NamedAttribute do - @moduledoc """ - This module defines a wrapper struct of NamedAttribute in MLIR - """ - use Kinda.ResourceKind, forward_module: Beaver.Native -end - defmodule Beaver.MLIR.Attribute do @moduledoc """ This module defines functions parsing and creating attributes in MLIR. @@ -13,21 +6,17 @@ defmodule Beaver.MLIR.Attribute do alias Beaver.MLIR alias Beaver.MLIR.Type - import MLIR.Sigils + import Beaver.Sigils use Kinda.ResourceKind, forward_module: Beaver.Native - def is_null(a) do - beaverIsNullAttribute(a) |> Beaver.Native.to_term() - end - def get(attr_str, opts \\ []) when is_binary(attr_str) do attr = MLIR.StringRef.create(attr_str) Beaver.Deferred.from_opts(opts, fn ctx -> attr = mlirAttributeParseGet(ctx, attr) - if is_null(attr) do + if MLIR.null?(attr) do raise "fail to parse attribute: #{attr_str}" end diff --git a/lib/beaver/mlir/block.ex b/lib/beaver/mlir/block.ex index 80416b3ac..2df61983e 100644 --- a/lib/beaver/mlir/block.ex +++ b/lib/beaver/mlir/block.ex @@ -87,4 +87,6 @@ defmodule Beaver.MLIR.Block do locs ) end + + defdelegate destroy(blk), to: MLIR.CAPI, as: :mlirBlockDestroy end diff --git a/lib/beaver/mlir/capi.ex b/lib/beaver/mlir/capi.ex index 7e117b7c6..52ca69aba 100644 --- a/lib/beaver/mlir/capi.ex +++ b/lib/beaver/mlir/capi.ex @@ -16,8 +16,9 @@ defmodule Beaver.MLIR.CAPI do def load_nif do nif_file = ~c"#{:code.priv_dir(:beaver)}/lib/libBeaverNIF" + dylib = "#{nif_file}.dylib" - if File.exists?(dylib = "#{nif_file}.dylib") do + if File.exists?(dylib) do dylib |> Path.basename() |> File.ln_s("#{nif_file}.so") @@ -51,19 +52,26 @@ defmodule Beaver.MLIR.CAPI do def beaver_raw_logical_mutex_token_signal_failure(_), do: raise("NIF not loaded") def beaver_raw_registered_ops(_ctx), do: raise("NIF not loaded") def beaver_raw_registered_dialects(_ctx), do: raise("NIF not loaded") - def beaver_raw_string_ref_to_binary(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Attribute(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Type(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Operation(_), do: raise("NIF not loaded") - def beaver_raw_to_string_OperationSpecialized(_), do: raise("NIF not loaded") - def beaver_raw_to_string_OperationGeneric(_), do: raise("NIF not loaded") - def beaver_raw_to_string_OperationBytecode(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Value(_), do: raise("NIF not loaded") - def beaver_raw_to_string_AffineMap(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Location(_), do: raise("NIF not loaded") - def beaver_raw_to_string_OpPassManager(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Identifier(_), do: raise("NIF not loaded") - def beaver_raw_to_string_Diagnostic(_), do: raise("NIF not loaded") + + for f <- ~w{ + StringRef + Attribute + Type + Operation + OperationSpecialized + OperationGeneric + OperationBytecode + Value + AffineMap + Location + OpPassManager + Identifier + Diagnostic + } do + f = :"beaver_raw_to_string_#{f}" + def unquote(f)(_), do: raise("NIF not loaded") + end + def beaver_raw_get_string_ref(_), do: raise("NIF not loaded") def beaver_raw_read_opaque_ptr(_, _), do: raise("NIF not loaded") def beaver_raw_deallocate_opaque_ptr(_), do: raise("NIF not loaded") diff --git a/lib/beaver/mlir/capi_codegen.ex b/lib/beaver/mlir/capi_codegen.ex index cd9e42353..6314e8015 100644 --- a/lib/beaver/mlir/capi_codegen.ex +++ b/lib/beaver/mlir/capi_codegen.ex @@ -130,7 +130,7 @@ defmodule Beaver.MLIR.CAPI.CodeGen do :StringArray ], &%KindDecl{module_name: Module.concat(Beaver.Native, &1)} - ) ++ [%KindDecl{module_name: Beaver.StringPrinter, kind_functions: [make: 0]}] + ) ++ [%KindDecl{module_name: Beaver.Printer, kind_functions: [make: 0]}] end @impl Kinda.CodeGen diff --git a/lib/beaver/mlir/capi_kinds.ex b/lib/beaver/mlir/capi_kinds.ex index 22178aa85..8d9313ed9 100644 --- a/lib/beaver/mlir/capi_kinds.ex +++ b/lib/beaver/mlir/capi_kinds.ex @@ -28,8 +28,6 @@ for m <- mlir_mods do m = Module.concat(Beaver.MLIR, m) defmodule m do - use Kinda.ResourceKind, - root_module: Beaver.MLIR.CAPI, - forward_module: Beaver.Native + use Kinda.ResourceKind, forward_module: Beaver.Native end end diff --git a/lib/beaver/mlir/context.ex b/lib/beaver/mlir/context.ex index ea7c5e9f8..9dbfe9649 100644 --- a/lib/beaver/mlir/context.ex +++ b/lib/beaver/mlir/context.ex @@ -6,14 +6,27 @@ defmodule Beaver.MLIR.Context do import MLIR.CAPI use Kinda.ResourceKind, forward_module: Beaver.Native + @doc """ + Run a function with a registry appended to the context. + """ + def with_registry(ctx, fun) when is_function(fun, 1) do + registry = mlirDialectRegistryCreate() + mlirContextAppendDialectRegistry(ctx, registry) + fun.(registry) + mlirDialectRegistryDestroy(registry) + end + # create an interim registry and append all dialects to the context defp load_all_dialects(ctx) do registry = mlirDialectRegistryCreate() - mlirRegisterAllDialects(registry) - mlirContextAppendDialectRegistry(ctx, registry) - Beaver.Exterior.register_all(ctx) + + with_registry(ctx, fn registry -> + mlirRegisterAllDialects(registry) + mlirContextAppendDialectRegistry(ctx, registry) + mlirContextLoadAllAvailableDialects(ctx) + end) + mlirDialectRegistryDestroy(registry) - mlirContextLoadAllAvailableDialects(ctx) end @type context_option :: {:allow_unregistered, boolean()} | {:all_dialects, boolean()} diff --git a/lib/beaver/mlir/conversion.ex b/lib/beaver/mlir/conversion.ex index a6afb3f4f..f8aebaa42 100644 --- a/lib/beaver/mlir/conversion.ex +++ b/lib/beaver/mlir/conversion.ex @@ -2,5 +2,5 @@ defmodule Beaver.MLIR.Conversion do @moduledoc """ Conversions MLIR provides by default. """ - use Beaver.MLIR.Pass.Composer.Generator, prefix: "mlirCreateConversion" + use Beaver.ComposerGenerator, prefix: "mlirCreateConversion" end diff --git a/lib/beaver/mlir/diagnostic.ex b/lib/beaver/mlir/diagnostic.ex index 66303e958..2d91dba82 100644 --- a/lib/beaver/mlir/diagnostic.ex +++ b/lib/beaver/mlir/diagnostic.ex @@ -1,6 +1,9 @@ defmodule Beaver.MLIR.Diagnostic do - alias Beaver.MLIR + @moduledoc """ + This module provides functions to work with MLIR diagnostics. + """ use Kinda.ResourceKind, forward_module: Beaver.Native + alias Beaver.MLIR @mlir_severity_levels [:error, :warning, :note, :remark] @doc """ diff --git a/lib/beaver/mlir/dialect/arith.ex b/lib/beaver/mlir/dialect/arith.ex index e01649072..fbeffec25 100644 --- a/lib/beaver/mlir/dialect/arith.ex +++ b/lib/beaver/mlir/dialect/arith.ex @@ -4,7 +4,7 @@ defmodule Beaver.MLIR.Dialect.Arith do """ alias Beaver.MLIR alias Beaver.MLIR.Dialect - import MLIR.Sigils + import Beaver.Sigils use Dialect, dialect: "arith", ops: Dialect.Registry.ops("arith") @@ -58,4 +58,18 @@ defmodule Beaver.MLIR.Dialect.Arith do i = i_type_to_magic_num(type) MLIR.Attribute.integer(MLIR.Type.i64(), i) end + + @compare_operators [:!=, :==, :>, :>=, :<, :<=] + defp operator_to_type(:!=, _), do: :ne + defp operator_to_type(:==, _), do: :eq + defp operator_to_type(:>, signed), do: :"#{if signed, do: :s, else: :u}gt" + defp operator_to_type(:>=, signed), do: :"#{if signed, do: :s, else: :u}ge" + defp operator_to_type(:<, signed), do: :"#{if signed, do: :s, else: :u}lt" + defp operator_to_type(:<=, signed), do: :"#{if signed, do: :s, else: :u}le" + + def operator_to_predicate(operator, signed \\ true) + + def operator_to_predicate(operator, signed) when operator in @compare_operators do + operator_to_type(operator, signed) |> cmp_i_predicate() + end end diff --git a/lib/beaver/mlir/dialect/func.ex b/lib/beaver/mlir/dialect/func.ex index 57224811a..447f42ec9 100644 --- a/lib/beaver/mlir/dialect/func.ex +++ b/lib/beaver/mlir/dialect/func.ex @@ -27,7 +27,7 @@ defmodule Beaver.MLIR.Dialect.Func do end end - def is_external(op) do + def external?(op) do with [r] <- Beaver.Walker.regions(op) |> Enum.to_list(), [] <- Beaver.Walker.blocks(r) |> Enum.to_list() do true diff --git a/lib/beaver/mlir/execution_engine.ex b/lib/beaver/mlir/execution_engine.ex index 195691dce..5ab6b2cd6 100644 --- a/lib/beaver/mlir/execution_engine.ex +++ b/lib/beaver/mlir/execution_engine.ex @@ -3,18 +3,10 @@ defmodule Beaver.MLIR.ExecutionEngine do This module defines functions working with MLIR #{__MODULE__ |> Module.split() |> List.last()}. """ alias Beaver.MLIR - alias Beaver.MLIR.Pass.Composer + alias Beaver.Composer import Beaver.MLIR.CAPI - use Kinda.ResourceKind, - root_module: Beaver.MLIR.CAPI, - forward_module: Beaver.Native - - def is_null(jit) do - jit - |> beaverIsNullExecutionEngine() - |> Beaver.Native.to_term() - end + use Kinda.ResourceKind, forward_module: Beaver.Native @doc """ Create a MLIR JIT engine for a module and check if successful. Usually this module should be of LLVM dialect. @@ -53,9 +45,7 @@ defmodule Beaver.MLIR.ExecutionEngine do object_dump ) - is_null = is_null(jit) - - if is_null do + if MLIR.null?(jit) do raise "Execution engine creation failed" end diff --git a/lib/beaver/mlir/external_pass.ex b/lib/beaver/mlir/external_pass.ex index 32f82cbaa..4f8852282 100644 --- a/lib/beaver/mlir/external_pass.ex +++ b/lib/beaver/mlir/external_pass.ex @@ -3,10 +3,7 @@ defmodule Beaver.MLIR.ExternalPass do # Lower level API to work with MLIR's external pass (pass defined in C). Use Beaver.MLIR.Pass for idiomatic Erlang behavior. alias Beaver.MLIR alias Beaver.MLIR.CAPI - - use Kinda.ResourceKind, - root_module: Beaver.MLIR.CAPI, - forward_module: Beaver.Native + use Kinda.ResourceKind, forward_module: Beaver.Native defp op_name_from_persistent_attributes(pass_module) do op_name = pass_module.__info__(:attributes)[:root_op] || [] diff --git a/lib/beaver/mlir/location.ex b/lib/beaver/mlir/location.ex index ab25d5e44..886754149 100644 --- a/lib/beaver/mlir/location.ex +++ b/lib/beaver/mlir/location.ex @@ -14,7 +14,7 @@ defmodule Beaver.MLIR.Location do iex> ctx = MLIR.Context.create() iex> MLIR.Location.file(name: "filename", line: 1, column: 1, ctx: ctx) |> MLIR.to_string() ~s{filename:1:1} - iex> ctx |> MLIR.Context.destroy + iex> MLIR.Context.destroy(ctx) """ @type file_opts() :: [name: String.t(), line: integer(), column: integer() | nil] diff --git a/lib/beaver/mlir/module.ex b/lib/beaver/mlir/module.ex index 9597d7bdb..1270dbaca 100644 --- a/lib/beaver/mlir/module.ex +++ b/lib/beaver/mlir/module.ex @@ -5,31 +5,30 @@ defmodule Beaver.MLIR.Module do alias Beaver.MLIR alias Beaver.MLIR.CAPI - def create(context, str) when is_binary(str) do - CAPI.mlirModuleCreateParse(context, MLIR.StringRef.create(str)) + @doc """ + Create a MLIR module by parsing string. + """ + def create(str, opts \\ []) when is_binary(str) do + Beaver.Deferred.from_opts( + opts, + fn ctx -> + CAPI.mlirModuleCreateParse(ctx, MLIR.StringRef.create(str)) + end + ) end - def create!(context, str) when is_binary(str) do - module = create(context, str) - verify!(module) - module + def create!(str, opts \\ []) when is_binary(str) do + create(str, opts) |> verify!() end use Kinda.ResourceKind, forward_module: Beaver.Native - def is_null(module) do - CAPI.beaverIsNullModule(module) |> Beaver.Native.to_term() - end - - defp not_null!(module) do - if is_null(module) do + def verify!(module) do + if MLIR.null?(module) do raise "module is null" end - end - def verify!(module) do - not_null!(module) - MLIR.Operation.verify!(module) + MLIR.verify!(module) end defdelegate destroy(module), to: CAPI, as: :mlirModuleDestroy diff --git a/lib/beaver/mlir/named_attribute.ex b/lib/beaver/mlir/named_attribute.ex new file mode 100644 index 000000000..1c2b186f4 --- /dev/null +++ b/lib/beaver/mlir/named_attribute.ex @@ -0,0 +1,6 @@ +defmodule Beaver.MLIR.NamedAttribute do + @moduledoc """ + This module defines a wrapper struct of NamedAttribute in MLIR + """ + use Kinda.ResourceKind, forward_module: Beaver.Native +end diff --git a/lib/beaver/mlir/operation.ex b/lib/beaver/mlir/operation.ex index 793b09dd2..73714ce2f 100644 --- a/lib/beaver/mlir/operation.ex +++ b/lib/beaver/mlir/operation.ex @@ -3,7 +3,8 @@ defmodule Beaver.MLIR.Operation do This module defines functions working with MLIR #{__MODULE__ |> Module.split() |> List.last()}. """ alias Beaver.MLIR - alias __MODULE__.{State, Changeset} + alias __MODULE__.State + alias Beaver.Changeset import Beaver.MLIR.CAPI require Logger @behaviour Access @@ -79,61 +80,10 @@ defmodule Beaver.MLIR.Operation do |> create() end - @default_verify_opts [debug: false] - def verify!(op, opts \\ @default_verify_opts) do - case verify(op, opts ++ [should_raise: true]) do - {:ok, op} -> - op - - :null -> - raise "MLIR operation verification failed because the operation is null. Maybe it is parsed from an ill-formed text format? Please have a look at the diagnostic output above by MLIR C++" - - :fail -> - raise "MLIR operation verification failed" - end - end - - def verify(op, opts \\ @default_verify_opts) do - debug = opts |> Keyword.get(:debug, false) - - is_null = MLIR.is_null(op) - - if is_null do - :null - else - is_success = from_module(op) |> mlirOperationVerify() |> Beaver.Native.to_term() - - if not is_success and debug do - Logger.info("Start printing op failed to pass the verification. This might crash.") - Logger.info(MLIR.to_string(op)) - end - - if is_success do - {:ok, op} - else - :fail - end - end - end - - def dump(op) do - op |> from_module |> mlirOperationDump() - op - end - - @doc """ - Verify the op and dump it. It raises if the verification fails. - """ - def dump!(%__MODULE__{} = op) do - verify!(op) - mlirOperationDump(op) - op - end - def name(%__MODULE__{} = operation) do mlirOperationGetName(operation) |> mlirIdentifierStr() - |> MLIR.StringRef.to_string() + |> MLIR.to_string() end defdelegate location(op), to: MLIR.CAPI, as: :mlirOperationGetLocation @@ -174,7 +124,7 @@ defmodule Beaver.MLIR.Operation do def fetch(operation, attribute) do attr = mlirOperationGetAttributeByName(operation, MLIR.StringRef.create(attribute)) - if MLIR.is_null(attr) do + if MLIR.null?(attr) do :error else {:ok, attr} diff --git a/lib/beaver/mlir/operation/state.ex b/lib/beaver/mlir/operation/state.ex index f83cf468b..5e80d6eee 100644 --- a/lib/beaver/mlir/operation/state.ex +++ b/lib/beaver/mlir/operation/state.ex @@ -7,41 +7,41 @@ defmodule Beaver.MLIR.Operation.State do alias Beaver.MLIR.CAPI defp prepare( - %MLIR.Operation.Changeset{ + %Beaver.Changeset{ location: location, context: nil } = changeset ) when not is_nil(location) and not is_function(location) do - %MLIR.Operation.Changeset{changeset | context: MLIR.context(location)} + %Beaver.Changeset{changeset | context: MLIR.context(location)} end defp prepare( - %MLIR.Operation.Changeset{ + %Beaver.Changeset{ location: nil, context: context } = changeset ) when not is_nil(context) do - %MLIR.Operation.Changeset{changeset | location: MLIR.Location.unknown(ctx: context)} + %Beaver.Changeset{changeset | location: MLIR.Location.unknown(ctx: context)} end defp prepare( - %MLIR.Operation.Changeset{ + %Beaver.Changeset{ location: location, context: context } = changeset ) when not is_nil(context) and is_function(location, 1) do - %MLIR.Operation.Changeset{changeset | location: location.(context)} + %Beaver.Changeset{changeset | location: location.(context)} end defp prepare( - %MLIR.Operation.Changeset{ + %Beaver.Changeset{ location: %Beaver.MLIR.Location{} = location } = changeset ) do - %MLIR.Operation.Changeset{changeset | location: location} + %Beaver.Changeset{changeset | location: location} end defp add_attributes(state, []) do @@ -69,7 +69,7 @@ defmodule Beaver.MLIR.Operation.State do raise "attribute not supported: #{inspect({k, v})}" end - if MLIR.is_null(attr) do + if MLIR.null?(attr) do raise "attribute can't be null, #{inspect({k, v})}" end @@ -174,8 +174,8 @@ defmodule Beaver.MLIR.Operation.State do @doc """ Create a new operation state in MLIR CAPI. """ - def create(%MLIR.Operation.Changeset{} = changeset) do - %MLIR.Operation.Changeset{ + def create(%Beaver.Changeset{} = changeset) do + %Beaver.Changeset{ name: name, attributes: attributes, operands: operands, diff --git a/lib/beaver/mlir/pass_manager.ex b/lib/beaver/mlir/pass_manager.ex index 43b4cb5db..13b0223fe 100644 --- a/lib/beaver/mlir/pass_manager.ex +++ b/lib/beaver/mlir/pass_manager.ex @@ -1,4 +1,7 @@ defmodule Beaver.MLIR.PassManager do + @moduledoc """ + This module defines functions working with MLIR PassManager. + """ use Kinda.ResourceKind, forward_module: Beaver.Native alias Beaver.MLIR diff --git a/lib/beaver/mlir/pattern.ex b/lib/beaver/mlir/pattern.ex deleted file mode 100644 index b6805c56a..000000000 --- a/lib/beaver/mlir/pattern.ex +++ /dev/null @@ -1,56 +0,0 @@ -defmodule Beaver.MLIR.Pattern do - alias Beaver.MLIR - import MLIR.CAPI - - @apply_default_opts [debug: false] - @doc """ - Apply patterns on a container (region, operation, module). - It returns the container if it succeeds otherwise it raises. - """ - def apply!(op, patterns, opts \\ @apply_default_opts) do - case apply_(op, patterns, opts) do - {:ok, module} -> - module - - _ -> - raise "failed to apply pattern" - end - end - - @doc """ - Apply patterns on a container (region, operation, module). - It is named `apply_` with a underscore to avoid name collision with `Kernel.apply/2` - """ - def apply_(op, patterns, opts \\ @apply_default_opts) when is_list(patterns) do - if MLIR.is_null(op), do: raise("op is null") - ctx = MLIR.Operation.from_module(op) |> MLIR.context() - pattern_module = MLIR.Location.from_env(__ENV__, ctx: ctx) |> MLIR.Module.empty() - block = Beaver.MLIR.Module.body(pattern_module) - - for p <- patterns do - p = p.(ctx, block) - - if opts[:debug] do - p |> MLIR.dump!() - end - end - - MLIR.Operation.verify!(pattern_module) - MLIR.Operation.verify!(op) - pdl_pat_mod = mlirPDLPatternModuleFromModule(pattern_module) - - frozen_pat_set = - pdl_pat_mod |> mlirRewritePatternSetFromPDLPatternModule() |> mlirFreezeRewritePattern() - - result = beaverModuleApplyPatternsAndFoldGreedily(op, frozen_pat_set) - mlirPDLPatternModuleDestroy(pdl_pat_mod) - mlirFrozenRewritePatternSetDestroy(frozen_pat_set) - MLIR.Module.destroy(pattern_module) - - if MLIR.LogicalResult.success?(result) do - {:ok, op} - else - {:error, "failed to apply pattern set"} - end - end -end diff --git a/lib/beaver/mlir/string_ref.ex b/lib/beaver/mlir/string_ref.ex index adb48aabb..dceaf6ac2 100644 --- a/lib/beaver/mlir/string_ref.ex +++ b/lib/beaver/mlir/string_ref.ex @@ -2,19 +2,16 @@ defmodule Beaver.MLIR.StringRef do @moduledoc """ This module defines functions working with MLIR #{__MODULE__ |> Module.split() |> List.last()}. """ - alias Beaver.MLIR.CAPI - use Kinda.ResourceKind, forward_module: Beaver.Native @doc """ Create a Elixir owned null-terminated C string from a Elixir bitstring and create a `StringRef` from it. - > Note: A `StringRef` will not reference the original BEAM binary. - > Instead, it will reference a copy of the binary and owns it. - > In other words, excessively creating `StringRef` using this function can lead to memory leak. + > #### Not really a reference {: .info} + > + > A `StringRef` created in Elixir is not a reference to the BEAM binary as argument. Instead, a copy is made. In other words, excessively creating `StringRef` using this function can lead to memory leak. """ - def create(value) when is_binary(value) do %__MODULE__{ref: CAPI.beaver_raw_get_string_ref(value)} end @@ -28,22 +25,17 @@ defmodule Beaver.MLIR.StringRef do end @doc """ - Converts an `StringRef` to a string. + Get the length of the StringRef as Erlang integer. """ - def to_string(%__MODULE__{ref: ref}) do - CAPI.beaver_raw_string_ref_to_binary(ref) - end - def length(%__MODULE__{} = str) do CAPI.beaverStringRefGetLength(str) |> Beaver.Native.to_term() end + @doc """ + Get the data of the StringRef as an array pointer of unsigned 8-bit integers. + """ def data(%__MODULE__{} = str) do CAPI.beaverStringRefGetData(str) |> then(&%Beaver.Native.Array{ref: &1, element_kind: Beaver.Native.U8}) end - - defimpl String.Chars do - defdelegate to_string(mlir), to: Beaver.MLIR.StringRef - end end diff --git a/lib/beaver/mlir/transform.ex b/lib/beaver/mlir/transform.ex new file mode 100644 index 000000000..31f9d3695 --- /dev/null +++ b/lib/beaver/mlir/transform.ex @@ -0,0 +1,8 @@ +defmodule Beaver.MLIR.Transform do + @moduledoc """ + Transformations MLIR provides by default. + """ + use Beaver.ComposerGenerator, prefix: "mlirCreateTransforms" + use Beaver.ComposerGenerator, prefix: "mlirCreateLinalg" + use Beaver.ComposerGenerator, prefix: "mlirCreateGPU" +end diff --git a/lib/beaver/mlir/transforms.ex b/lib/beaver/mlir/transforms.ex deleted file mode 100644 index 5fc696d5f..000000000 --- a/lib/beaver/mlir/transforms.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Beaver.MLIR.Transforms do - @moduledoc """ - Transformations MLIR provides by default. - """ - use Beaver.MLIR.Pass.Composer.Generator, prefix: "mlirCreateTransforms" - use Beaver.MLIR.Pass.Composer.Generator, prefix: "mlirCreateLinalg" - use Beaver.MLIR.Pass.Composer.Generator, prefix: "mlirCreateGPU" -end diff --git a/lib/beaver/mlir/type.ex b/lib/beaver/mlir/type.ex index 71d9fd3f3..3bb765176 100644 --- a/lib/beaver/mlir/type.ex +++ b/lib/beaver/mlir/type.ex @@ -1,16 +1,38 @@ defmodule Beaver.MLIR.Type do @moduledoc """ - This module defines functions working with MLIR #{__MODULE__ |> Module.split() |> List.last()}. + This module provides functions to work with MLIR's type system, allowing creation of MLIR type. + + ## Type Categories + + ### Basic Types + - `integer/1` (`i32/1`, `i64/1`, etc.) + - `float/1` (`f32/1`, `f64/1`, etc.) + - `index/1` + - `none/1` + + ### Composite Types + - Tensors (`ranked_tensor/2` and `unranked_tensor/1`) + - `vector/2` + - `memref/3` + - `function/3` """ alias Beaver.MLIR - alias Beaver.MLIR.CAPI + import Beaver.MLIR.CAPI use Kinda.ResourceKind, forward_module: Beaver.Native def get(string, opts \\ []) def get(string, opts) when is_binary(string) do - Beaver.Deferred.from_opts(opts, &CAPI.mlirTypeParseGet(&1, MLIR.StringRef.create(string))) + Beaver.Deferred.from_opts(opts, fn ctx -> + t = mlirTypeParseGet(ctx, MLIR.StringRef.create(string)) + + if MLIR.null?(t) do + raise "fail to parse type: #{string}" + end + + t + end) end def function(inputs, results, opts \\ []) do @@ -28,7 +50,7 @@ defmodule Beaver.MLIR.Type do |> Enum.map(&Beaver.Deferred.create(&1, ctx)) |> Beaver.Native.array(__MODULE__) - CAPI.mlirFunctionTypeGet(ctx, num_inputs, inputs, num_results, results) + mlirFunctionTypeGet(ctx, num_inputs, inputs, num_results, results) end) end @@ -53,7 +75,7 @@ defmodule Beaver.MLIR.Type do nil ) when is_list(shape) do - ranked_tensor(shape, element_type, CAPI.mlirAttributeGetNull()) + ranked_tensor(shape, element_type, mlirAttributeGetNull()) end def ranked_tensor( @@ -69,7 +91,7 @@ defmodule Beaver.MLIR.Type do |> Enum.map(&escape_dynamic/1) |> Beaver.Native.array(Beaver.Native.I64) - CAPI.mlirRankedTensorTypeGet(rank, shape, element_type, encoding) + mlirRankedTensorTypeGet(rank, shape, element_type, encoding) end def unranked_tensor(element_type) @@ -78,7 +100,7 @@ defmodule Beaver.MLIR.Type do end def unranked_tensor(%__MODULE__{} = element_type) do - CAPI.mlirUnrankedTensorTypeGet(element_type) + mlirUnrankedTensorTypeGet(element_type) end def complex(element_type) when is_function(element_type, 1) do @@ -86,7 +108,7 @@ defmodule Beaver.MLIR.Type do end def complex(%__MODULE__{} = element_type) do - CAPI.mlirComplexTypeGet(element_type) + mlirComplexTypeGet(element_type) end def memref( @@ -109,11 +131,11 @@ defmodule Beaver.MLIR.Type do shape = shape |> Enum.map(&escape_dynamic/1) |> Beaver.Native.array(Beaver.Native.I64) - default_null = CAPI.mlirAttributeGetNull() + default_null = mlirAttributeGetNull() layout = Keyword.get(opts, :layout) || default_null memory_space = Keyword.get(opts, :memory_space) || default_null - CAPI.mlirMemRefTypeGet(element_type, rank, shape, layout, memory_space) + mlirMemRefTypeGet(element_type, rank, shape, layout, memory_space) end @doc """ @@ -123,7 +145,7 @@ defmodule Beaver.MLIR.Type do iex> ctx = MLIR.Context.create() iex> MLIR.Type.vector([1, 2, 3], MLIR.Type.i32).(ctx) |> MLIR.to_string() "vector<1x2x3xi32>" - iex> ctx |> MLIR.Context.destroy + iex> MLIR.Context.destroy(ctx) """ def vector(shape, element_type) when is_function(element_type, 1) do @@ -133,7 +155,7 @@ defmodule Beaver.MLIR.Type do def vector(shape, %__MODULE__{} = element_type) when is_list(shape) do rank = length(shape) shape = shape |> Beaver.Native.array(Beaver.Native.I64) - CAPI.mlirVectorTypeGet(rank, shape, element_type) + mlirVectorTypeGet(rank, shape, element_type) end @doc """ @@ -143,7 +165,7 @@ defmodule Beaver.MLIR.Type do iex> ctx = MLIR.Context.create() iex> MLIR.Type.tuple([MLIR.Type.i32, MLIR.Type.i32], ctx: ctx) |> MLIR.to_string() "tuple" - iex> ctx |> MLIR.Context.destroy + iex> MLIR.Context.destroy(ctx) """ def tuple(elements, opts \\ []) when is_list(elements) do Beaver.Deferred.from_opts(opts, fn ctx -> @@ -154,26 +176,28 @@ defmodule Beaver.MLIR.Type do |> Enum.map(&Beaver.Deferred.create(&1, ctx)) |> Beaver.Native.array(__MODULE__) - CAPI.mlirTupleTypeGet(ctx, num_elements, elements) + mlirTupleTypeGet(ctx, num_elements, elements) end) end def f16(opts \\ []) do - Beaver.Deferred.from_opts(opts, &CAPI.mlirF16TypeGet/1) + Beaver.Deferred.from_opts(opts, &mlirF16TypeGet/1) end def f32(opts \\ []) do - Beaver.Deferred.from_opts(opts, &CAPI.mlirF32TypeGet/1) + Beaver.Deferred.from_opts(opts, &mlirF32TypeGet/1) end def f64(opts \\ []) do - Beaver.Deferred.from_opts(opts, &CAPI.mlirF64TypeGet/1) + Beaver.Deferred.from_opts(opts, &mlirF64TypeGet/1) end - def f(bitwidth, opts \\ []) when is_integer(bitwidth) do + def float(bitwidth, opts \\ []) when is_integer(bitwidth) do apply(__MODULE__, String.to_atom("f#{bitwidth}"), [opts]) end + defdelegate f(bitwidth, opts \\ []), to: __MODULE__, as: :float + def integer(bitwidth, opts \\ [signed: false]) do signed = Keyword.get(opts, :signed) @@ -181,9 +205,9 @@ defmodule Beaver.MLIR.Type do opts, fn ctx -> if signed do - CAPI.mlirIntegerTypeSignedGet(ctx, bitwidth) + mlirIntegerTypeSignedGet(ctx, bitwidth) else - CAPI.mlirIntegerTypeGet(ctx, bitwidth) + mlirIntegerTypeGet(ctx, bitwidth) end end ) @@ -192,14 +216,14 @@ defmodule Beaver.MLIR.Type do def index(opts \\ []) do Beaver.Deferred.from_opts( opts, - &CAPI.mlirIndexTypeGet(&1) + &mlirIndexTypeGet(&1) ) end def none(opts \\ []) do Beaver.Deferred.from_opts( opts, - &CAPI.mlirNoneTypeGet(&1) + &mlirNoneTypeGet(&1) ) end @@ -214,14 +238,14 @@ defmodule Beaver.MLIR.Type do end def integer?(%MLIR.Type{} = t) do - MLIR.CAPI.mlirTypeIsAInteger(t) |> Beaver.Native.to_term() + mlirTypeIsAInteger(t) |> Beaver.Native.to_term() end def index?(%MLIR.Type{} = t) do - MLIR.CAPI.mlirTypeIsAIndex(t) |> Beaver.Native.to_term() + mlirTypeIsAIndex(t) |> Beaver.Native.to_term() end def float?(%MLIR.Type{} = t) do - MLIR.CAPI.mlirTypeIsAFloat(t) |> Beaver.Native.to_term() + mlirTypeIsAFloat(t) |> Beaver.Native.to_term() end end diff --git a/lib/beaver/mlir/value.ex b/lib/beaver/mlir/value.ex index b082b7b2b..96dab82cb 100644 --- a/lib/beaver/mlir/value.ex +++ b/lib/beaver/mlir/value.ex @@ -1,6 +1,9 @@ defmodule Beaver.MLIR.Value do @moduledoc """ - This module defines functions working with MLIR #{__MODULE__ |> Module.split() |> List.last()}. + This module handles MLIR values, which represent SSA (Static Single Assignment) values in the IR. + + Values can be either block arguments or operation results. That's why this module provides + functions to check if a value is an argument or a result (`argument?/1`, `result?/1`), or to get the owner of a result (`owner/1`). """ alias Beaver.MLIR.CAPI diff --git a/lib/beaver/pass_runner.ex b/lib/beaver/pass_runner.ex index c91c4fb08..b4d38e171 100644 --- a/lib/beaver/pass_runner.ex +++ b/lib/beaver/pass_runner.ex @@ -1,8 +1,6 @@ defmodule Beaver.PassRunner do alias Beaver.MLIR - require Logger - @moduledoc """ `GenServer` to run an MLIR pass implemented in Elixir """ diff --git a/lib/beaver/dsl/pattern.ex b/lib/beaver/pattern.ex similarity index 99% rename from lib/beaver/dsl/pattern.ex rename to lib/beaver/pattern.ex index f7870be29..aa75ebfef 100644 --- a/lib/beaver/dsl/pattern.ex +++ b/lib/beaver/pattern.ex @@ -26,7 +26,7 @@ defmodule Beaver.Pattern do end end >>> [] end - |> MLIR.Operation.verify!(debug: true) + |> MLIR.verify!() end defmacro defpat(call, do: body) do diff --git a/lib/beaver/string_printer.ex b/lib/beaver/printer.ex similarity index 96% rename from lib/beaver/string_printer.ex rename to lib/beaver/printer.ex index 22306b3bd..5720ed84b 100644 --- a/lib/beaver/string_printer.ex +++ b/lib/beaver/printer.ex @@ -1,4 +1,4 @@ -defmodule Beaver.StringPrinter do +defmodule Beaver.Printer do @moduledoc """ This module provides a way to run MLIR CAPI with a string callback and user data. """ diff --git a/lib/beaver/mlir/sigils.ex b/lib/beaver/sigils.ex similarity index 80% rename from lib/beaver/mlir/sigils.ex rename to lib/beaver/sigils.ex index 9d6bfb4a6..50ec5ad53 100644 --- a/lib/beaver/mlir/sigils.ex +++ b/lib/beaver/sigils.ex @@ -1,4 +1,4 @@ -defmodule Beaver.MLIR.Sigils do +defmodule Beaver.Sigils do @moduledoc """ Sigils return a function to create MLIR elements by parsing the content. """ @@ -16,16 +16,17 @@ defmodule Beaver.MLIR.Sigils do ...> return %res : i32 ...> } ...> } - ...> \""".(ctx) |> MLIR.Operation.verify!() - iex> ctx |> MLIR.Context.destroy + ...> \""".(ctx) |> MLIR.verify!() + iex> MLIR.Context.destroy(ctx) """ def sigil_m(string, []) do - &MLIR.Module.create(&1, string) + &MLIR.Module.create(string, ctx: &1) end @doc """ Create an attribute creator. - You might add a modifier to it as a shortcut to annotate the type + + Add a modifier to it as a shortcut to annotate the type ## Examples iex> ctx = MLIR.Context.create() @@ -33,7 +34,7 @@ defmodule Beaver.MLIR.Sigils do true iex> ~a{1 : i32}.(ctx) |> MLIR.to_string() "1 : i32" - iex> ctx |> MLIR.Context.destroy + iex> MLIR.Context.destroy(ctx) """ def sigil_a(string, []), do: MLIR.Attribute.get(string) @@ -44,7 +45,8 @@ defmodule Beaver.MLIR.Sigils do @doc """ Create a type creator. - You might add a modifier to it as a shortcut to make it a higher order type. + + Add a modifier to it as a shortcut to make it a higher order type. ## Examples iex> ctx = MLIR.Context.create() @@ -56,7 +58,7 @@ defmodule Beaver.MLIR.Sigils do true iex> MLIR.equal?(Type.complex(Type.f32()), ~tcomplex.(ctx)) true - iex> ctx |> MLIR.Context.destroy + iex> MLIR.Context.destroy(ctx) """ def sigil_t(string, []), do: MLIR.Type.get(string) diff --git a/lib/beaver/slang.ex b/lib/beaver/slang.ex index 363c9e82f..001e75408 100644 --- a/lib/beaver/slang.ex +++ b/lib/beaver/slang.ex @@ -4,7 +4,15 @@ defmodule Beaver.Slang do @variadic_tags [:variadic, :optional, :single] @callback __slang_dialect__(ctx :: Beaver.MLIR.Context.t()) :: :ok | {:error, String.t()} @moduledoc """ - Defining a MLIR dialect with macros in Elixir. Internally expressions are compiled to [IRDL](https://mlir.llvm.org/docs/Dialects/IRDL/) + Provides macros and utilities for defining MLIR dialects in Elixir. + + This module allows you to define MLIR dialects using Elixir macros, which are internally compiled to + [IRDL](https://mlir.llvm.org/docs/Dialects/IRDL/). It provides: + + - Macro-based dialect definition + - Operation creation and manipulation + - Type and attribute handling + - Constraint definition support """ @doc """ @@ -105,14 +113,13 @@ defmodule Beaver.Slang do tags = values |> List.wrap() - |> Enum.map(fn + |> Enum.map_join(",", fn {variadic_tag, _} when variadic_tag in @variadic_tags -> variadic_tag |> Atom.to_string() _ -> "single" end) - |> Enum.join(",") [ variadicity: ~a{#irdl} @@ -188,51 +195,47 @@ defmodule Beaver.Slang do end # This function transforms the given argument AST based on the provided usage (if it is using as variable or constraint declaration). It handles different cases based on the structure of the argument and returns the transformed AST. - defp transform_arg(ast, i, usage) when usage in [:constraint, :variable] do + defp transform_constraint(ast, i) do case ast do {_name, _line0, nil} -> - case usage do - # erase hanging vars to suppress warning - :constraint -> - nil + nil - :variable -> - ast - end + {:=, _line0, [_var, _right]} -> + ast - {:=, _line0, [var, _right]} -> - case usage do - :constraint -> - ast + {variadic_tag, {_name, _line0, nil}} when variadic_tag in @variadic_tags -> + ast - :variable -> - var + _ -> + quote do + unquote(get_slang_arg_ast(i)) = + Beaver.Slang.create_constraint(unquote(ast), + blk: Beaver.Env.block(), + ctx: Beaver.Env.context() + ) end + end + end - {variadic_tag, {_name, _line0, nil}} - when variadic_tag in @variadic_tags -> + defp transform_variable(ast, i) do + case ast do + {_name, _line0, nil} -> ast - _ -> - case usage do - :constraint -> - quote do - unquote(get_slang_arg_ast(i)) = - Beaver.Slang.create_constraint(unquote(ast), - blk: Beaver.Env.block(), - ctx: Beaver.Env.context() - ) - end + {:=, _line0, [var, _right]} -> + var - :variable -> - get_slang_arg_ast(i) - end + {variadic_tag, {_name, _line0, nil}} when variadic_tag in @variadic_tags -> + ast + + _ -> + get_slang_arg_ast(i) end end # This function generates the AST for the arguments of a creator function as variables. defp get_args_as_vars(args) do - for {v, i} <- Enum.with_index(args), do: transform_arg(v, i, :variable) + for {v, i} <- Enum.with_index(args), do: transform_variable(v, i) end # This function generates the AST for a creator function for an IRDL operation (like `irdl.operation`, `irdl.type`). It uses the transform_defop_pins/1 function to transform the pins, generates the MLIR code for the operation and its arguments, and applies the operation using op_applier/1. @@ -248,7 +251,7 @@ defmodule Beaver.Slang do |> Macro.postwalk(&transform_defop_pins/1) |> Enum.with_index() |> Enum.map(fn {ast, i} -> - transform_arg(ast, i, :constraint) + transform_constraint(ast, i) end) quote do @@ -467,9 +470,9 @@ defmodule Beaver.Slang do """ def load(ctx, mod) when is_atom(mod) do apply(mod, :__slang_dialect__, [ctx]) - |> Beaver.MLIR.Transforms.canonicalize() - |> MLIR.Pass.Composer.run!() - |> MLIR.Operation.verify!() + |> Beaver.MLIR.Transform.canonicalize() + |> Beaver.Composer.run!() + |> MLIR.verify!() |> Beaver.MLIR.CAPI.mlirLoadIRDLDialects() end end diff --git a/lib/beaver/dsl/ssa.ex b/lib/beaver/ssa.ex similarity index 100% rename from lib/beaver/dsl/ssa.ex rename to lib/beaver/ssa.ex diff --git a/lib/beaver/walker.ex b/lib/beaver/walker.ex index 18964930b..7621b6c48 100644 --- a/lib/beaver/walker.ex +++ b/lib/beaver/walker.ex @@ -13,14 +13,45 @@ alias Beaver.MLIR.{ } defmodule Beaver.Walker do + require Beaver.Pattern alias Beaver.MLIR.CAPI alias __MODULE__.OpReplacement @moduledoc """ - Walker to traverse MLIR structures including operands, results, successors, attributes, regions. - It implements the `Enumerable` protocol and the `Access` behavior. + Provides traversal capabilities for MLIR structures. + + This module implements traversal functionality for MLIR structures including: + - `operations/1` + - `results/1` + - `successors/1` + - `attributes/1` + - `regions/1` + + It implements both the `Enumerable` protocol and the `Access` behavior to provide + a familiar interface for working with MLIR structures. + + ### Depth-first, pre-order and post-order walking + Allows traversing MLIR structures in depth-first order, visiting each node and its + children before moving to siblings. Supports both pre-order (visit node before children) and post-order (visit children + before node). + + ### Mutation Support + It is possible to modifying the MLIR structure during traversal with CAPIs. It is recommended to use `replace/2` to replace an operation to keep the traversal going. + + ### Pattern-based Transformations + You can apply transformation patterns defined using `Beaver.Pattern.defpat/2` to MLIR structures during traversal. + + ### Access Syntax + - Access behavior to provide convenient attribute access: + ```elixir + op[:attr_name] + op["attr_name"] + ``` + - convenient access to get operands, results, regions + ``` + operands(op)[0] + ``` """ - @type operation() :: Module.t() | Operation.t() | OpReplacement.t() @type container() :: operation() | Region.t() | Block.t() | NamedAttribute.t() @type element() :: operation() | Region.t() | Block.t() | Value.t() | NamedAttribute.t() @@ -256,7 +287,7 @@ defmodule Beaver.Walker do get_first: &CAPI.mlirBlockGetFirstOperation/1, get_next: &CAPI.mlirOperationGetNextInBlock/1, get_parent: &CAPI.mlirOperationGetBlock/1, - is_null: &MLIR.is_null/1 + is_null: &MLIR.null?/1 ) end @@ -271,7 +302,7 @@ defmodule Beaver.Walker do get_first: &CAPI.mlirRegionGetFirstBlock/1, get_next: &CAPI.mlirBlockGetNextInRegion/1, get_parent: &CAPI.mlirBlockGetParentRegion/1, - is_null: &MLIR.is_null/1 + is_null: &MLIR.null?/1 ) end @@ -299,7 +330,7 @@ defmodule Beaver.Walker do named_attribute |> MLIR.CAPI.beaverNamedAttributeGetName() |> MLIR.CAPI.mlirIdentifierStr() - |> MLIR.StringRef.to_string() do + |> MLIR.to_string() do name == to_string(key) end end) @@ -317,7 +348,7 @@ defmodule Beaver.Walker do with name_str <- name |> MLIR.CAPI.mlirIdentifierStr() - |> MLIR.StringRef.to_string() do + |> MLIR.to_string() do name_str == to_string(key) end end) diff --git a/mix.exs b/mix.exs index 8cafcaed4..df86d145e 100644 --- a/mix.exs +++ b/mix.exs @@ -44,7 +44,9 @@ defmodule Beaver.MixProject do [ main: "Beaver", extras: [ - "guides/your-first-beaver-compiler.livemd" + "guides/your-first-beaver-compiler.livemd", + "CONTRIBUTING.md", + "README.md" ], filter_modules: fn m, _meta -> name = Atom.to_string(m) @@ -60,24 +62,24 @@ defmodule Beaver.MixProject do ~r"Beaver.Exterior.*", Beaver.SSA ], - Walker: [ - ~r"Beaver.Walker.*" + Utils: [ + ~r"Beaver.Walker.*", + ~r"Beaver.Deferred.*", + ~r"Beaver.Capturer.*", + ~r"Beaver.Pass.*", + ~r"Beaver.Printer.*", + ~r"Beaver.Composer.*", + ~r"Beaver.Sigils.*" ], ENIF: [ ~r"Beaver.ENIF.*" ], - Utils: [ - ~r"Beaver.Deferred.*", - ~r"Beaver.Diagnostic.*", - ~r"Beaver.Pass.*", - ~r"Beaver.String.*" + MLIR: [ + ~r"Beaver.MLIR(?!\.Dialect).*" ], Dialect: [ ~r"Beaver.MLIR.Dialect.*" ], - MLIR: [ - ~r"Beaver.MLIR.*" - ], Native: [ ~r"Beaver.Native.*" ] @@ -115,7 +117,8 @@ defmodule Beaver.MixProject do {:elixir_make, "~> 0.4", runtime: false}, {:kinda, "~> 0.9.2"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, - {:benchee, "~> 1.0", only: :dev} + {:benchee, "~> 1.0", only: :dev}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false} ] end end diff --git a/mix.lock b/mix.lock index f929aa485..753fabc30 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,13 @@ %{ "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "kinda": {:hex, :kinda, "0.9.2", "d75aaac76810f50b9db91347034f73e4a305a9a6ba405862f4db7342793e265b", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "fd4a8484cd95d6e17de841079466e70ff8d96fe996fbd44bb3d7a6be95ce30ac"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, diff --git a/native/src/string_ref.zig b/native/src/string_ref.zig index 78c6d91fb..18ea063e0 100644 --- a/native/src/string_ref.zig +++ b/native/src/string_ref.zig @@ -7,6 +7,7 @@ const kinda = @import("kinda"); pub const c = @import("prelude.zig"); const mem = @import("std").mem; +const print_nif_prefix = "beaver_raw_to_string_"; pub fn PrinterNIF(comptime name: []const u8, comptime ResourceKind: type, comptime print_fn: anytype) type { return struct { const Error = error{ @@ -28,13 +29,13 @@ pub fn PrinterNIF(comptime name: []const u8, comptime ResourceKind: type, compti print_fn(entity, collect_string_ref, &printer); return beam.make_slice(env, printer.buffer.items); } - const entry = result.nif("beaver_raw_to_string_" ++ name, 1, to_string).entry; + const entry = result.nif(print_nif_prefix ++ name, 1, to_string).entry; }; } // collect multiple MLIR StringRef and join them as a single erlang binary pub const Printer = struct { - pub const ResourceKind = kinda.ResourceKind(@This(), "Elixir.Beaver.StringPrinter"); + pub const ResourceKind = kinda.ResourceKind(@This(), "Elixir.Beaver.Printer"); pub const PtrType = *@This(); pub const ArrayType = [*]@This(); const Error = error{ NullPointerFound, InvalidPrinter, @"Already flushed" }; @@ -128,7 +129,7 @@ fn beaver_raw_get_string_ref(env: beam.env, _: c_int, args: [*c]const beam.term) pub const nifs = .{ result.nif("beaver_raw_get_string_ref", 1, beaver_raw_get_string_ref).entry, - result.nif("beaver_raw_string_ref_to_binary", 1, beaver_raw_string_ref_to_binary).entry, + result.nif(print_nif_prefix ++ "StringRef", 1, beaver_raw_string_ref_to_binary).entry, PrinterNIF("Attribute", mlir_capi.Attribute, c.mlirAttributePrint).entry, PrinterNIF("Type", mlir_capi.Type, c.mlirTypePrint).entry, PrinterNIF("Operation", mlir_capi.Operation, c.mlirOperationPrint).entry, diff --git a/profile/cuda_runtime_overhead.exs b/profile/cuda_runtime_overhead.exs index 40fc833f0..4672b0754 100644 --- a/profile/cuda_runtime_overhead.exs +++ b/profile/cuda_runtime_overhead.exs @@ -9,9 +9,9 @@ libs = ~w{libmlir_cuda_runtime.so libmlir_runner_utils.so libmlir_c_runner_utils jit = MLIR.Module.create(ctx, File.read!("test/gpu-to-cubin.mlir")) - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") - |> MLIR.Pass.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") + |> Beaver.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") + |> Beaver.Composer.run!() |> MLIR.ExecutionEngine.create!( shared_lib_paths: Enum.map(libs, &Path.join([:code.priv_dir(:beaver), "lib", &1])) ) diff --git a/test/arith_test.exs b/test/arith_test.exs index bb8c0e25a..4a254af7c 100644 --- a/test/arith_test.exs +++ b/test/arith_test.exs @@ -11,23 +11,48 @@ defmodule ArithTest do mlir ctx: ctx do module do for p <- [:eq, :ne, :slt, :sle, :sgt, :sge, :ult, :ule, :ugt, :uge] do - f = - Func.func _( - function_type: Type.function([Type.i32(), Type.i32()], []), - sym_name: "\"#{p}\"" - ) do - region do - block _(a >>> Type.i32(), b >>> Type.i32()) do - Arith.cmpi(a, b, predicate: Arith.cmp_i_predicate(p)) >>> Type.i1() - Func.return() >>> [] - end + Func.func _( + function_type: Type.function([Type.i32(), Type.i32()], []), + sym_name: + Beaver.MLIR.Attribute.string("f#{System.unique_integer([:positive])}") + ) do + region do + block _(a >>> Type.i32(), b >>> Type.i32()) do + Arith.cmpi(a, b, predicate: Arith.cmp_i_predicate(p)) >>> Type.i1() + Func.return() >>> [] end end + end + |> tap(&assert(MLIR.to_string(&1, generic: false) =~ "#{p}")) + end + end + |> MLIR.verify!() + end + end - assert f |> MLIR.to_string(generic: false) =~ "#{p}" + for p <- [:!=, :==, :>, :>=, :<, :<=], signed <- [true, false] do + test "int operator #{p}, signed: #{signed}", %{ctx: ctx} do + mlir ctx: ctx do + module do + Func.func _( + function_type: Type.function([Type.i32(), Type.i32()], []), + sym_name: + Beaver.MLIR.Attribute.string("f#{System.unique_integer([:positive])}") + ) do + region do + block _(a >>> Type.i32(), b >>> Type.i32()) do + Arith.cmpi(a, b, + predicate: Arith.operator_to_predicate(unquote(p), unquote(signed)) + ) >>> + Type.i1() + + Func.return() >>> [] + end + end + end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end end @@ -55,7 +80,7 @@ defmodule ArithTest do f = Func.func _( function_type: Type.function([Type.f32(), Type.f32()], []), - sym_name: MLIR.Attribute.string("f#{System.unique_integer()}") + sym_name: MLIR.Attribute.string("f#{System.unique_integer([:positive])}") ) do region do block _(a >>> Type.f32(), b >>> Type.f32()) do @@ -70,6 +95,6 @@ defmodule ArithTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end end diff --git a/test/block_test.exs b/test/block_test.exs index dc61bd9bb..0469125de 100644 --- a/test/block_test.exs +++ b/test/block_test.exs @@ -34,7 +34,7 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end test "dangling block", %{ctx: ctx, diagnostic_server: diagnostic_server} do @@ -50,9 +50,9 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify() + |> MLIR.verify() - assert Beaver.DiagnosticsCapturer.collect(diagnostic_server) =~ + assert Beaver.Capturer.collect(diagnostic_server) =~ "reference to block defined in another region" end @@ -72,9 +72,9 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify() + |> MLIR.verify() - assert Beaver.DiagnosticsCapturer.collect(diagnostic_server) =~ + assert Beaver.Capturer.collect(diagnostic_server) =~ "branch has 1 operands for successor" end @@ -109,9 +109,9 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify() + |> MLIR.verify() - assert Beaver.DiagnosticsCapturer.collect(diagnostic_server) =~ + assert Beaver.Capturer.collect(diagnostic_server) =~ "branch has 1 operands for successor" end @@ -142,9 +142,9 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify() + |> MLIR.verify() - assert Beaver.DiagnosticsCapturer.collect(diagnostic_server) =~ + assert Beaver.Capturer.collect(diagnostic_server) =~ "expect at least a terminator" end end @@ -165,7 +165,7 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() assert {:not_found, [file: ^file, line: ^line]} = Beaver.Env.block() end @@ -189,7 +189,20 @@ defmodule BlockTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() + end + end + + test "no block to insert to", %{ctx: ctx} do + assert_raise CompileError, "nofile:1: no valid block in the environment", fn -> + Code.eval_quoted( + quote do + mlir ctx: var!(ctx) do + Arith.constant(value: Attribute.integer(Type.i(32), 0)) >>> {:op, Type.i(32)} + end + end, + ctx: ctx + ) end end end diff --git a/test/bytecode_test.exs b/test/bytecode_test.exs index 5f5f44b87..14a42006d 100644 --- a/test/bytecode_test.exs +++ b/test/bytecode_test.exs @@ -5,11 +5,11 @@ defmodule BytecodeTest do defp roundtrip_bytecode(m) do m - |> MLIR.Operation.verify!() + |> MLIR.verify!() |> MLIR.to_string(bytecode: true) |> tap(fn s -> assert String.starts_with?(s, "ML\xefR") end) - |> then(&MLIR.Module.create!(MLIR.context(m), &1)) - |> MLIR.Operation.verify!() + |> then(&MLIR.Module.create!(&1, ctx: MLIR.context(m))) + |> MLIR.verify!() end test "bytecode writing and parsing", %{ctx: ctx} do diff --git a/test/capi_test.exs b/test/capi_test.exs index c06a84965..56326ab95 100644 --- a/test/capi_test.exs +++ b/test/capi_test.exs @@ -19,17 +19,20 @@ defmodule MlirTest do _module = mlirModuleCreateEmpty(location) module = - MLIR.Module.create(ctx, """ - func.func private @printNewline() - func.func private @printI64(i64) - """) + MLIR.Module.create( + """ + func.func private @printNewline() + func.func private @printI64(i64) + """, + ctx: ctx + ) - MLIR.Operation.verify!(module) + MLIR.verify!(module) _operation = mlirModuleGetOperation(module) _ret_str = MLIR.StringRef.create("func.return") - changeset = %MLIR.Operation.Changeset{name: "func.return", location: location} + changeset = %Beaver.Changeset{name: "func.return", location: location} for _i <- 0..200 do changeset |> MLIR.Operation.State.create() |> Beaver.Native.ptr() |> mlirOperationCreate() @@ -46,20 +49,20 @@ defmodule MlirTest do mlirRegionAppendOwnedBlock(func_body_region, func_body) # create func changeset = - %MLIR.Operation.Changeset{name: "func.func", context: ctx} - |> MLIR.Operation.Changeset.add_argument( + %Beaver.Changeset{name: "func.func", context: ctx} + |> Beaver.Changeset.add_argument( sym_name: "\"add\"", function_type: "(i64, i64) -> (i64)" ) - |> MLIR.Operation.Changeset.add_argument(func_body_region) + |> Beaver.Changeset.add_argument(func_body_region) func_op = changeset |> MLIR.Operation.create() add_op_state = - %MLIR.Operation.Changeset{name: "arith.addi", location: location} - |> MLIR.Operation.Changeset.add_argument(MLIR.Block.get_arg!(func_body, 0)) - |> MLIR.Operation.Changeset.add_argument(arg1) - |> MLIR.Operation.Changeset.add_argument({:result_types, ["i64"]}) + %Beaver.Changeset{name: "arith.addi", location: location} + |> Beaver.Changeset.add_argument(MLIR.Block.get_arg!(func_body, 0)) + |> Beaver.Changeset.add_argument(arg1) + |> Beaver.Changeset.add_argument({:result_types, ["i64"]}) |> MLIR.Operation.State.create() name = beaverOperationStateGetName(add_op_state) @@ -84,17 +87,15 @@ defmodule MlirTest do r = mlirOperationGetResult(add_op, 0) return_op = - %MLIR.Operation.Changeset{name: "func.return", context: ctx} - |> MLIR.Operation.Changeset.add_argument(r) + %Beaver.Changeset{name: "func.return", context: ctx} + |> Beaver.Changeset.add_argument(r) |> MLIR.Operation.create() mlirBlockInsertOwnedOperation(func_body, 0, add_op) mlirBlockInsertOwnedOperationAfter(func_body, add_op, return_op) module_body = mlirModuleGetBody(module) mlirBlockInsertOwnedOperation(module_body, 0, func_op) - - MLIR.Operation.verify!(module, debug: true) - + MLIR.verify!(module) mlirContextDestroy(ctx) end @@ -118,21 +119,24 @@ defmodule MlirTest do mlirContextLoadAllAvailableDialects(ctx) _add_op = - %MLIR.Operation.Changeset{name: "elixir.add", context: ctx} + %Beaver.Changeset{name: "elixir.add", context: ctx} |> MLIR.Operation.create() end def create_adder_module(ctx) do - MLIR.Module.create(ctx, """ - func.func @foo(%arg0 : i32) -> i32 { - %res = arith.addi %arg0, %arg0 : i32 - return %res : i32 - } - """) + MLIR.Module.create( + """ + func.func @foo(%arg0 : i32) -> i32 { + %res = arith.addi %arg0, %arg0 : i32 + return %res : i32 + } + """, + ctx: ctx + ) end def create_redundant_transpose_module(ctx) do - MLIR.Module.create(ctx, """ + MLIR.Module.create(""" func.func @test_transpose(%arg0: tensor<1x2x3xi32>) -> () { %0 = arith.constant dense<[1, 2, 0]> : tensor<3xi32> %1 = "tosa.transpose"(%arg0, %0) : (tensor<1x2x3xi32>, tensor<3xi32>) -> (tensor<2x3x1xi32>) @@ -140,7 +144,7 @@ defmodule MlirTest do %3 = "tosa.transpose"(%1, %2) : (tensor<2x3x1xi32>, tensor<3xi32>) -> (tensor<1x2x3xi32>) return } - """) + """).(ctx) end test "run a simple pass" do @@ -167,7 +171,7 @@ defmodule MlirTest do @moduledoc false def run(%Beaver.MLIR.Operation{} = op) do - MLIR.Operation.verify!(op) + MLIR.verify!(op) end end @@ -176,14 +180,14 @@ defmodule MlirTest do use MLIR.Pass, on: "func.func" def run(%Beaver.MLIR.Operation{} = op) do - MLIR.Operation.verify!(op) + MLIR.verify!(op) end end test "Run a generic pass" do ctx = MLIR.Context.create() module = create_adder_module(ctx) - assert not MLIR.Module.is_null(module) + assert not MLIR.null?(module) type_id_allocator = mlirTypeIDAllocatorCreate() external = %MLIR.Pass{} = MLIR.ExternalPass.create(TestPass) pm = mlirPassManagerCreate(ctx) @@ -199,7 +203,7 @@ defmodule MlirTest do test "Run a func operation pass", %{ctx: ctx} do module = create_adder_module(ctx) - assert not MLIR.Module.is_null(module) + assert not MLIR.null?(module) external = %MLIR.Pass{} = MLIR.ExternalPass.create(TestFuncPass) pm = mlirPassManagerCreate(ctx) npm = mlirPassManagerGetNestedUnder(pm, MLIR.StringRef.create("func.func")) @@ -212,7 +216,7 @@ defmodule MlirTest do test "Run pass with patterns", %{ctx: ctx} do module = create_redundant_transpose_module(ctx) - assert not MLIR.Module.is_null(module) + assert not MLIR.null?(module) external = %MLIR.Pass{} = MLIR.ExternalPass.create(TestFuncPass) pm = mlirPassManagerCreate(ctx) npm = mlirPassManagerGetNestedUnder(pm, MLIR.StringRef.create("func.func")) @@ -255,8 +259,8 @@ defmodule MlirTest do """ ctx = MLIR.Context.create() - module = MLIR.Module.create(ctx, module_str) - MLIR.Operation.verify!(module) + module = MLIR.Module.create(module_str, ctx: ctx) + MLIR.verify!(module) lower_to_llvm(ctx, MLIR.Operation.from_module(module)) jit = MLIR.ExecutionEngine.create!(module) arg = Beaver.Native.I32.make(42) diff --git a/test/cf_test.exs b/test/cf_test.exs index f62fece90..398c71505 100644 --- a/test/cf_test.exs +++ b/test/cf_test.exs @@ -7,7 +7,6 @@ defmodule CfTest do require Func @moduletag :smoke - # TODO: move MutCompiler to an independent file, so it would be clear for new users defmodule MutCompiler do use Beaver require Beaver.MLIR @@ -45,7 +44,7 @@ defmodule CfTest do {arg = %MLIR.Value{}, acc} = gen_mlir(arg, acc) mlir = - mlir block: block, ctx: ctx do + mlir blk: block, ctx: ctx do Func.return(arg) >>> [] end @@ -97,7 +96,7 @@ defmodule CfTest do end end - mlir block: entry, ctx: ctx do + mlir blk: entry, ctx: ctx do CF.cond_br(condition, true_branch, false_branch) >>> [] end @@ -128,7 +127,7 @@ defmodule CfTest do {right = %MLIR.Value{}, acc} = gen_mlir(right, acc) less = - mlir block: block, ctx: ctx do + mlir blk: block, ctx: ctx do Arith.cmpf(left, right, predicate: Arith.cmp_f_predicate(:ugt)) >>> Type.i1() end @@ -142,7 +141,7 @@ defmodule CfTest do {right = %MLIR.Value{}, acc} = gen_mlir(right, acc) # we only work with float 32 for now add = - mlir block: block, ctx: ctx do + mlir blk: block, ctx: ctx do Arith.mulf(left, right) >>> Type.f32() end @@ -200,7 +199,7 @@ defmodule CfTest do end end # we let MLIR verify the generated IR for us, so it gonna be legit! - |> MLIR.Operation.verify!(debug: true) + |> MLIR.verify!() end # In most of LLVM or other compiler guidance, it starts with ast parsing. @@ -217,16 +216,16 @@ defmodule CfTest do end defp lower(ir) do - import MLIR.{Transforms, Conversion} + import MLIR.{Transform, Conversion} ir |> convert_arith_to_llvm - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") - |> MLIR.Pass.Composer.nested("func.func", {"DoNothing0", "func.func", fn _ -> :ok end}) + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") + |> Beaver.Composer.nested("func.func", {"DoNothing0", "func.func", fn _ -> :ok end}) |> convert_func_to_llvm |> canonicalize - |> MLIR.Pass.Composer.append({"DoNothing1", "builtin.module", fn _ -> :ok end}) - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.append({"DoNothing1", "builtin.module", fn _ -> :ok end}) + |> Beaver.Composer.run!() end def get_func(ir, func) do diff --git a/test/cuda_runtime_test.exs b/test/cuda_runtime_test.exs index dd3e1b2ed..506673d93 100644 --- a/test/cuda_runtime_test.exs +++ b/test/cuda_runtime_test.exs @@ -14,9 +14,9 @@ defmodule CUDARuntimeTest do jit = MLIR.Module.create(ctx, File.read!("test/gpu-to-cubin.mlir")) - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") - |> MLIR.Pass.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") + |> Beaver.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") + |> Beaver.Composer.run!() |> MLIR.ExecutionEngine.create!( shared_lib_paths: Enum.map(libs, &Path.join([:code.priv_dir(:beaver), "lib", &1])) ) diff --git a/test/debug_output_test.exs b/test/debug_output_test.exs index 0260fecdd..b49f6c5d6 100644 --- a/test/debug_output_test.exs +++ b/test/debug_output_test.exs @@ -16,25 +16,25 @@ defmodule DebugOutputTest do test "op stats", %{ctx: ctx} do ir(ctx) - |> MLIR.Transforms.print_op_stats() - |> MLIR.Pass.Composer.run!() + |> MLIR.Transform.print_op_stats() + |> Beaver.Composer.run!() end test "print ir", %{ctx: ctx} do ir(ctx) - |> MLIR.Transforms.print_ir() - |> MLIR.Pass.Composer.run!() + |> MLIR.Transform.print_ir() + |> Beaver.Composer.run!() end test "timing", %{ctx: ctx} do ir(ctx) - |> MLIR.Transforms.canonicalize() - |> MLIR.Pass.Composer.run!(timing: true) + |> MLIR.Transform.canonicalize() + |> Beaver.Composer.run!(timing: true) end test "pass manager enable print ir", %{ctx: ctx} do ir(ctx) - |> MLIR.Transforms.canonicalize() - |> MLIR.Pass.Composer.run!(print: true) + |> MLIR.Transform.canonicalize() + |> Beaver.Composer.run!(print: true) end end diff --git a/test/diagnostic_test.exs b/test/diagnostic_test.exs index 19ae7bd96..bbb11839b 100644 --- a/test/diagnostic_test.exs +++ b/test/diagnostic_test.exs @@ -6,11 +6,11 @@ defmodule DiagnosticTest do def start_and_attach(ctx, cb) do {:ok, server} = GenServer.start( - Beaver.DiagnosticsCapturer, + Beaver.Capturer, cb ) - handler_id = Beaver.DiagnosticsCapturer.attach(ctx, server) + handler_id = Beaver.Capturer.attach(ctx, server) {server, handler_id} end @@ -39,7 +39,7 @@ defmodule DiagnosticTest do ) assert_raise RuntimeError, @err_msg, fn -> DiagnosticTestHelper.get_attr(ctx) end - assert Beaver.DiagnosticsCapturer.collect(server) == @collected + assert Beaver.Capturer.collect(server) == @collected DiagnosticTestHelper.cleanup_handler(ctx, server, handler_id) end @@ -52,7 +52,7 @@ defmodule DiagnosticTest do assert_raise RuntimeError, @err_msg, fn -> DiagnosticTestHelper.get_attr(ctx) end - assert Beaver.DiagnosticsCapturer.collect(server) == + assert Beaver.Capturer.collect(server) == "hello#{@collected}" DiagnosticTestHelper.cleanup_handler(ctx, server, handler_id) diff --git a/test/dialect_registry_test.exs b/test/dialect_registry_test.exs index f066621fa..6600ec6e7 100644 --- a/test/dialect_registry_test.exs +++ b/test/dialect_registry_test.exs @@ -61,8 +61,7 @@ defmodule DialectRegistryTest do {"vector", "Vector"}, {"x86vector", "X86Vector"}, {"xegpu", "XeGPU"}, - {"polynomial", "Polynomial"}, - {"elixir", "Elixir"} + {"polynomial", "Polynomial"} ] |> MapSet.new() diff --git a/test/e2e_test.exs b/test/e2e_test.exs index 314851039..205afcc0d 100644 --- a/test/e2e_test.exs +++ b/test/e2e_test.exs @@ -4,8 +4,8 @@ defmodule E2ETest do @moduletag :smoke describe "e2e compilation and JIT invocation" do - import Beaver.MLIR.Sigils - import MLIR.{Transforms, Conversion} + import Beaver.Sigils + import MLIR.{Transform, Conversion} def make_jit(ctx) do ~m""" @@ -20,7 +20,7 @@ defmodule E2ETest do |> cse |> convert_func_to_llvm |> convert_arith_to_llvm - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() |> MLIR.ExecutionEngine.create!() end diff --git a/test/elixir_ast_dialect_test.exs b/test/elixir_ast_dialect_test.exs index e322709b5..237f2fbdf 100644 --- a/test/elixir_ast_dialect_test.exs +++ b/test/elixir_ast_dialect_test.exs @@ -30,7 +30,7 @@ defmodule ELXDialectTest do mlir_module = ast |> ElixirAST.from_ast(ctx: ctx) - |> MLIR.Pass.Composer.nested( + |> Beaver.Composer.nested( "builtin.module", [ {:nested, "func.func", @@ -39,8 +39,8 @@ defmodule ELXDialectTest do ]} ] ) - |> MLIR.Pass.Composer.run!() - |> MLIR.Operation.verify!() + |> Beaver.Composer.run!() + |> MLIR.verify!() {_, op_names} = mlir_module diff --git a/test/enif_test.exs b/test/enif_test.exs index 5096737a0..95cf97973 100644 --- a/test/enif_test.exs +++ b/test/enif_test.exs @@ -22,7 +22,7 @@ defmodule EnifTest do assert to_string(b) =~ "dense<[104, 101, 108, 108, 111]>" end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end test "enif string inspected as memref", %{ctx: ctx} do diff --git a/test/entity_test.exs b/test/entity_test.exs index fc6b98c01..9f4d3b787 100644 --- a/test/entity_test.exs +++ b/test/entity_test.exs @@ -5,8 +5,9 @@ defmodule EntityTest do use Beaver.Case, async: true, diagnostic: :server alias Beaver.MLIR alias MLIR.{Type, Attribute} - import MLIR.Sigils - doctest Beaver.MLIR.Sigils + import Beaver.Sigils + doctest Beaver + doctest Beaver.Sigils doctest Beaver.MLIR.Type doctest Beaver.MLIR.Location @@ -78,7 +79,7 @@ defmodule EntityTest do Attribute.integer(Type.f32(), 1).(ctx) end - assert not Attribute.is_null( + assert not MLIR.null?( Attribute.type( Type.function( [Type.i(32)], @@ -186,10 +187,10 @@ defmodule EntityTest do describe "null" do test "attr", %{ctx: ctx, diagnostic_server: diagnostic_server} do assert_raise RuntimeError, "fail to parse attribute: ???", fn -> - Attribute.get("???", ctx: ctx) |> MLIR.is_null() + Attribute.get("???", ctx: ctx) |> MLIR.null?() end - assert Beaver.DiagnosticsCapturer.collect(diagnostic_server) =~ + assert Beaver.Capturer.collect(diagnostic_server) =~ "expected attribute value" end end diff --git a/test/exterior_test.exs b/test/exterior_test.exs index a78ae3198..bf23928b0 100644 --- a/test/exterior_test.exs +++ b/test/exterior_test.exs @@ -8,6 +8,9 @@ defmodule ExteriorTest do require Func test "generate ops in elixir dialect", %{ctx: ctx} do + Beaver.Exterior.Elixir.register_dialect(ctx) + MLIR.CAPI.mlirContextLoadAllAvailableDialects(ctx) + ir = mlir ctx: ctx do module do @@ -21,7 +24,7 @@ defmodule ExteriorTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end text = ir |> MLIR.to_string() diff --git a/test/gen_ir_test.exs b/test/gen_ir_test.exs index 47653e183..e67b63043 100644 --- a/test/gen_ir_test.exs +++ b/test/gen_ir_test.exs @@ -32,7 +32,7 @@ defmodule CFTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() text = ir |> MLIR.to_string() diff --git a/test/gpu_test.exs b/test/gpu_test.exs index b20beaf5c..ead16e99c 100644 --- a/test/gpu_test.exs +++ b/test/gpu_test.exs @@ -12,8 +12,8 @@ defmodule GPUTest do System.trap_signal(:sigchld, fn -> :ok end) assert MLIR.Module.create(ctx, File.read!("test/gpu-to-cubin.mlir")) - |> MLIR.Pass.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") + |> Beaver.Composer.run!() |> to_string() =~ "gpu.binary @other_func_kernel" end @@ -21,8 +21,8 @@ defmodule GPUTest do MLIR.Context.register_translations(ctx) assert MLIR.Module.create(ctx, File.read!("test/gpu-to-cubin.mlir")) - |> MLIR.Pass.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=isa}") - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=isa}") + |> Beaver.Composer.run!() |> to_string() =~ "gpu.binary @other_func_kernel" end end diff --git a/test/irdl_test.exs b/test/irdl_test.exs index f48cdacb3..a6776ed44 100644 --- a/test/irdl_test.exs +++ b/test/irdl_test.exs @@ -4,7 +4,7 @@ defmodule IRDLTest do @moduletag :smoke test "gen irdl", %{ctx: ctx} do - import Beaver.MLIR.Sigils + import Beaver.Sigils m = ~m""" @@ -31,7 +31,7 @@ defmodule IRDLTest do } } """.(ctx) - |> MLIR.Operation.verify!() + |> MLIR.verify!() assert m |> Beaver.MLIR.CAPI.mlirLoadIRDLDialects() @@ -48,7 +48,7 @@ defmodule IRDLTest do alias Beaver.MLIR.Type require Func - CMath.__slang_dialect__(ctx) |> MLIR.Operation.verify!() + CMath.__slang_dialect__(ctx) |> MLIR.verify!() Beaver.Slang.load(ctx, CMath) CMath.IRExample.get(ctx) @@ -56,17 +56,17 @@ defmodule IRDLTest do assert not (CMath.some_attr(Type.f32()) |> Beaver.Deferred.create(ctx) - |> MLIR.is_null()) + |> MLIR.null?()) assert not (MLIR.CAPI.mlirContextGetOrLoadDialect( ctx, MLIR.StringRef.create("cmath") ) - |> MLIR.is_null()) + |> MLIR.null?()) end test "var dialect", %{ctx: ctx} do use Beaver - TestVariadic.__slang_dialect__(ctx) |> MLIR.Operation.verify!() + TestVariadic.__slang_dialect__(ctx) |> MLIR.verify!() end end diff --git a/test/load_ir_test.exs b/test/load_ir_test.exs index 5fb609041..7ed0840a9 100644 --- a/test/load_ir_test.exs +++ b/test/load_ir_test.exs @@ -1,7 +1,7 @@ defmodule LoadIRTest do use Beaver.Case, async: true alias Beaver.MLIR - import Beaver.MLIR.Sigils + import Beaver.Sigils test "example from upstream with br", %{ctx: ctx} do content = File.read!("test/br_example.mlir") @@ -9,6 +9,6 @@ defmodule LoadIRTest do ~m""" #{content} """.(ctx) - |> MLIR.Operation.verify!() + |> MLIR.verify!() end end diff --git a/test/memref_test.exs b/test/memref_test.exs index 342afd109..ba40fe0e0 100644 --- a/test/memref_test.exs +++ b/test/memref_test.exs @@ -15,8 +15,8 @@ defmodule MemRefTest do end test "run mlir module defined by sigil", %{ctx: ctx} do - import Beaver.MLIR.Sigils - import MLIR.{Transforms, Conversion} + import Beaver.Sigils + import MLIR.{Transform, Conversion} jit = ~m""" @@ -32,20 +32,20 @@ defmodule MemRefTest do return } """.(ctx) - |> MLIR.Operation.verify!() + |> MLIR.verify!() |> canonicalize |> cse - |> MLIR.Pass.Composer.nested("func.func", convert_linalg_to_loops()) - |> MLIR.Pass.Composer.append("one-shot-bufferize") - |> MLIR.Pass.Composer.nested( + |> Beaver.Composer.nested("func.func", convert_linalg_to_loops()) + |> Beaver.Composer.append("one-shot-bufferize") + |> Beaver.Composer.nested( "func.func", ~w{finalizing-bufferize buffer-deallocation convert-linalg-to-loops} ) |> convert_scf_to_cf - |> MLIR.Pass.Composer.append("finalize-memref-to-llvm") + |> Beaver.Composer.append("finalize-memref-to-llvm") |> convert_func_to_llvm |> reconcile_unrealized_casts - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() |> MLIR.ExecutionEngine.create!() arr = [0.112122112, 0.2123213, 10_020.9, 213_120.0, 0.2, 100.4] diff --git a/test/op_test.exs b/test/op_test.exs index 90c033a1c..b27c0f666 100644 --- a/test/op_test.exs +++ b/test/op_test.exs @@ -1,14 +1,14 @@ defmodule OpTest do use Beaver.Case, async: true alias Beaver.MLIR - import Beaver.MLIR.Sigils + import Beaver.Sigils test "get_and_update", %{ctx: ctx} do const = ~m""" %0 = arith.constant dense<42> : vector<4xi32> """.(ctx) - |> MLIR.Operation.verify!() + |> MLIR.verify!() |> MLIR.Module.body() |> Beaver.Walker.operations() |> Enum.at(0) diff --git a/test/pass_test.exs b/test/pass_test.exs index d78adffbd..6fe5393f4 100644 --- a/test/pass_test.exs +++ b/test/pass_test.exs @@ -4,7 +4,7 @@ defmodule PassTest do use Beaver alias Beaver.MLIR.Dialect.{Func, Arith} require Func - import MLIR.Transforms + import MLIR.Transform defmodule PassRaisingException do @moduledoc false @@ -26,10 +26,10 @@ defmodule PassTest do end end end - |> MLIR.Operation.verify!(debug: true) + |> MLIR.verify!() end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end test "exception in run/1", %{ctx: ctx, diagnostic_server: diagnostic_server} do @@ -38,14 +38,14 @@ defmodule PassTest do assert_raise RuntimeError, ~r"Unexpected failure running passes", fn -> assert capture_log(fn -> ir - |> MLIR.Pass.Composer.nested("func.func", [ + |> Beaver.Composer.nested("func.func", [ PassRaisingException ]) - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() end) =~ ~r"fail to run a pass" end - assert Beaver.DiagnosticsCapturer.collect(diagnostic_server) =~ + assert Beaver.Capturer.collect(diagnostic_server) =~ "Fail to run a pass implemented in Elixir" end @@ -53,13 +53,13 @@ defmodule PassTest do ir = example_ir(ctx) ir - |> MLIR.Pass.Composer.append( + |> Beaver.Composer.append( {"test-pass", "builtin.module", fn op -> assert MLIR.to_string(op) =~ ~r"func.func @some_func" end} ) - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() end test "multi level nested", %{ctx: ctx} do @@ -67,7 +67,7 @@ defmodule PassTest do assert ir |> canonicalize() - |> MLIR.Pass.Composer.nested( + |> Beaver.Composer.nested( "func.func1", [ canonicalize(), @@ -81,7 +81,7 @@ defmodule PassTest do ]} ] ) - |> MLIR.Pass.Composer.to_pipeline() =~ + |> Beaver.Composer.to_pipeline() =~ ~r/func1.+func2.+func.func3\(canonicalize/ end @@ -92,8 +92,8 @@ defmodule PassTest do ~r"Unexpected failure parsing pipeline: something wrong, MLIR Textual PassPipeline Parser:1:1: error: 'something wrong' does not refer to a registered pass or pass pipeline", fn -> ir - |> MLIR.Pass.Composer.append("something wrong") - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.append("something wrong") + |> Beaver.Composer.run!() end end end diff --git a/test/pdl_test.exs b/test/pdl_test.exs index 49dcd0f3b..c83259733 100644 --- a/test/pdl_test.exs +++ b/test/pdl_test.exs @@ -5,7 +5,7 @@ defmodule PDLTest do alias MLIR.Type import MLIR.CAPI alias MLIR.Dialect.{Func, TOSA} - import MLIR.Transforms + import MLIR.Transform require Func require TOSA require Type @@ -43,8 +43,8 @@ defmodule PDLTest do """ def apply_patterns(pattern_module, ir_module, cb) do - MLIR.Operation.verify!(pattern_module) - MLIR.Operation.verify!(ir_module) + MLIR.verify!(pattern_module) + MLIR.verify!(ir_module) pdl_pat_mod = mlirPDLPatternModuleFromModule(pattern_module) frozen_pat_set = @@ -59,7 +59,7 @@ defmodule PDLTest do test "AreEqualOp", %{ctx: ctx} do mlirContextSetAllowUnregisteredDialects(ctx, true) - pattern_module = MLIR.Module.create(ctx, @apply_rewrite_op_patterns) + pattern_module = MLIR.Module.create(@apply_rewrite_op_patterns, ctx: ctx) inspector = fn {:successor, %MLIR.Block{} = successor}, acc -> @@ -137,7 +137,7 @@ defmodule PDLTest do assert MLIR.equal?(mlir, MLIR.Operation.from_module(pattern_module)) - ir_module = MLIR.Module.create(ctx, @apply_rewrite_op_ir) + ir_module = MLIR.Module.create(@apply_rewrite_op_ir, ctx: ctx) apply_patterns(pattern_module, ir_module, fn ir_module -> ir_string = MLIR.to_string(ir_module) @@ -153,11 +153,11 @@ defmodule PDLTest do test "AreEqualOp pdl version", %{ctx: ctx} do mlirContextSetAllowUnregisteredDialects(ctx, true) - pattern_module = MLIR.Module.create(ctx, @are_equal_op_pdl) - assert not MLIR.Module.is_null(pattern_module), "fail to parse module" - ir_module = MLIR.Module.create(ctx, @apply_rewrite_op_ir) - MLIR.Operation.verify!(pattern_module) - MLIR.Operation.verify!(ir_module) + pattern_module = MLIR.Module.create(@are_equal_op_pdl, ctx: ctx) + assert not MLIR.null?(pattern_module), "fail to parse module" + ir_module = MLIR.Module.create(@apply_rewrite_op_ir, ctx: ctx) + MLIR.verify!(pattern_module) + MLIR.verify!(ir_module) pattern_string = MLIR.to_string(pattern_module) assert String.contains?(pattern_string, "test.op") assert String.contains?(pattern_string, "test.success2") @@ -181,14 +181,14 @@ defmodule PDLTest do TestTOSAPatterns.replace_multi_add_op3() ] do ir_module = TestTOSAPatterns.gen_ir_module(ctx) - MLIR.Operation.verify!(ir_module) + MLIR.verify!(ir_module) ir_string = MLIR.to_string(ir_module) assert not String.contains?(ir_string, "tosa.sub"), ir_string - MLIR.Pattern.apply!(ir_module, [pattern]) - |> MLIR.Operation.verify!(debug: true) - |> MLIR.Transforms.canonicalize() - |> MLIR.Pass.Composer.run!() + MLIR.apply!(ir_module, [pattern]) + |> MLIR.verify!() + |> MLIR.Transform.canonicalize() + |> Beaver.Composer.run!() ir_string = MLIR.to_string(ir_module) assert not String.contains?(ir_string, "tosa.add"), ir_string @@ -206,9 +206,9 @@ defmodule PDLTest do } } """.(ctx) - |> MLIR.Pass.Composer.append(ToyPass) + |> Beaver.Composer.append(ToyPass) |> canonicalize - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() ir_string = MLIR.to_string(ir) assert not (ir_string =~ "tosa.add") diff --git a/test/redundant_transpose_test.exs b/test/redundant_transpose_test.exs index 20b25d171..97b7fa2fc 100644 --- a/test/redundant_transpose_test.exs +++ b/test/redundant_transpose_test.exs @@ -7,7 +7,7 @@ defmodule RedundantTransposeTest do test "pass to optimize redundant transpose", %{ctx: ctx} do use Beaver - import Beaver.MLIR.Transforms + import Beaver.MLIR.Transform defmodule Helper do @moduledoc false @@ -43,7 +43,7 @@ defmodule RedundantTransposeTest do end end end - |> MLIR.Operation.verify!(debug: true) + |> MLIR.verify!() end defmodule DeduplicateTransposePass do @@ -85,11 +85,11 @@ defmodule RedundantTransposeTest do ir_string = ir - |> MLIR.Pass.Composer.nested("func.func", [ + |> Beaver.Composer.nested("func.func", [ DeduplicateTransposePass ]) |> canonicalize - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() |> MLIR.to_string() assert ir_string =~ "return %arg0 : tensor<*xf32>", ir_string diff --git a/test/region_test.exs b/test/region_test.exs index 52bf1c3e2..0fae11357 100644 --- a/test/region_test.exs +++ b/test/region_test.exs @@ -73,7 +73,7 @@ defmodule RegionTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end test "nested regions", %{ctx: ctx} do @@ -126,6 +126,6 @@ defmodule RegionTest do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end end diff --git a/test/string_ref_test.exs b/test/string_ref_test.exs index 1fc16d1b2..98d323601 100644 --- a/test/string_ref_test.exs +++ b/test/string_ref_test.exs @@ -7,7 +7,7 @@ defmodule StringRefTest do for _ <- 1..1_000 do s = "hello world" r = MLIR.StringRef.create(s) - assert s == MLIR.StringRef.to_string(r) + assert s == MLIR.to_string(r) assert s == to_string(r) end end @@ -17,7 +17,7 @@ defmodule StringRefTest do :rand.seed(:exsss, {100, 101, 102}) s = 0..size |> Enum.shuffle() |> List.to_string() r = MLIR.StringRef.create(s) - assert s == MLIR.StringRef.to_string(r) + assert s == MLIR.to_string(r) assert byte_size(s) == MLIR.StringRef.length(r) end end @@ -25,7 +25,7 @@ defmodule StringRefTest do describe "printer" do test "collect string ref", %{ctx: ctx} do {:ok, txt} = - Beaver.StringPrinter.run(fn cb, ud -> + Beaver.Printer.run(fn cb, ud -> MLIR.Location.file(name: "1", line: 2, column: 3, ctx: ctx) |> MLIR.CAPI.mlirLocationPrint(cb, ud) end) @@ -33,22 +33,22 @@ defmodule StringRefTest do assert ~s{loc("1":2:3)} == txt {attr, "1 : i64"} = - Beaver.StringPrinter.run(fn cb, ud -> + Beaver.Printer.run(fn cb, ud -> MLIR.Attribute.get("1", ctx: ctx) |> tap(&MLIR.CAPI.mlirAttributePrint(&1, cb, ud)) end) - refute MLIR.is_null(attr) + refute MLIR.null?(attr) end test "flush only once", %{ctx: ctx} do - {sp, user_data} = Beaver.StringPrinter.create() + {sp, user_data} = Beaver.Printer.create() MLIR.Location.file(name: "1", line: 2, column: 3, ctx: ctx) - |> MLIR.CAPI.mlirLocationPrint(Beaver.StringPrinter.callback(), user_data) + |> MLIR.CAPI.mlirLocationPrint(Beaver.Printer.callback(), user_data) - assert ~s{loc("1":2:3)} == Beaver.StringPrinter.flush(sp) - assert_raise Kinda.CallError, ~r"Already flushed", fn -> Beaver.StringPrinter.flush(sp) end + assert ~s{loc("1":2:3)} == Beaver.Printer.flush(sp) + assert_raise Kinda.CallError, ~r"Already flushed", fn -> Beaver.Printer.flush(sp) end end end end diff --git a/test/support/add_two_integers.ex b/test/support/add_two_integers.ex index 750e139a5..7755f3cb3 100644 --- a/test/support/add_two_integers.ex +++ b/test/support/add_two_integers.ex @@ -1,4 +1,5 @@ defmodule AddTwoIntegers do + @moduledoc false use TranslateMLIR mlir_func llvm_add(a :: i64, b :: i64) do diff --git a/test/support/beaver_case.ex b/test/support/beaver_case.ex index d839ca75c..8cdad8ebe 100644 --- a/test/support/beaver_case.ex +++ b/test/support/beaver_case.ex @@ -17,11 +17,11 @@ defmodule Beaver.Case do if unquote(options)[:diagnostic] == :server do {:ok, pid} = GenServer.start( - Beaver.DiagnosticsCapturer, + Beaver.Capturer, &"#{&2}[Beaver] [Diagnostic] [#{to_string(MLIR.location(&1))}] #{to_string(&1)}\n" ) - {pid, Beaver.DiagnosticsCapturer.attach(ctx, pid)} + {pid, Beaver.Capturer.attach(ctx, pid)} else {nil, nil} end diff --git a/test/support/cmath_ir.ex b/test/support/cmath_ir.ex index 406f9da31..dc1da79f7 100644 --- a/test/support/cmath_ir.ex +++ b/test/support/cmath_ir.ex @@ -1,4 +1,5 @@ defmodule CMath.IRExample do + @moduledoc false use Beaver alias Beaver.MLIR.Dialect.{Func, Arith} require Func @@ -31,7 +32,7 @@ defmodule CMath.IRExample do end end end - |> MLIR.Operation.verify!() + |> MLIR.verify!() end def get(ctx) do @@ -51,6 +52,6 @@ defmodule CMath.IRExample do return %conorm : f32 } """.(ctx) - |> MLIR.Operation.verify!() + |> MLIR.verify!() end end diff --git a/test/support/dummy.ex b/test/support/dummy.ex index 407d8de33..e82312c7b 100644 --- a/test/support/dummy.ex +++ b/test/support/dummy.ex @@ -1,4 +1,5 @@ defmodule Beaver.Dummy do + @moduledoc false use Beaver alias Beaver.MLIR.{Attribute, Type} alias Beaver.MLIR.Dialect.{Func, Arith, CF} @@ -8,7 +9,7 @@ defmodule Beaver.Dummy do mlir ctx: ctx, blk: block do Func.func some_func( function_type: Type.function([], [Type.i(32)]), - sym_name: MLIR.Attribute.string("f#{System.unique_integer()}") + sym_name: MLIR.Attribute.string("f#{System.unique_integer([:positive])}") ) do region do block do diff --git a/test/support/elixir_ast_dialect.ex b/test/support/elixir_ast_dialect.ex index 82fd89202..236e69c5b 100644 --- a/test/support/elixir_ast_dialect.ex +++ b/test/support/elixir_ast_dialect.ex @@ -152,7 +152,22 @@ defmodule ElixirAST do defp extract_var_name(var_op) do Beaver.Walker.attributes(var_op)["name"] |> MLIR.CAPI.mlirStringAttrGetValue() - |> MLIR.StringRef.to_string() + |> MLIR.to_string() + end + + defp lookup_var(op, acc) do + [val] = + Beaver.Walker.results(op)[0] + |> Beaver.Walker.uses() + |> Enum.to_list() + + case val |> MLIR.CAPI.mlirOpOperandGetOwner() |> MLIR.Operation.name() do + "ex.bind" -> + {op, acc} + + _ -> + {Beaver.Walker.replace(op, acc.variables[extract_var_name(op)]), acc} + end end def run(func) do @@ -174,18 +189,7 @@ defmodule ElixirAST do {r, acc} "ex.var" -> - [val] = - Beaver.Walker.results(op)[0] - |> Beaver.Walker.uses() - |> Enum.to_list() - - case val |> MLIR.CAPI.mlirOpOperandGetOwner() |> MLIR.Operation.name() do - "ex.bind" -> - {op, acc} - - _ -> - {Beaver.Walker.replace(op, acc.variables[extract_var_name(op)]), acc} - end + lookup_var(op, acc) _ -> {op, acc} diff --git a/test/support/test_tosa_patterns.ex b/test/support/test_tosa_patterns.ex index 31d02d46f..e2b08a277 100644 --- a/test/support/test_tosa_patterns.ex +++ b/test/support/test_tosa_patterns.ex @@ -1,4 +1,5 @@ defmodule TestTOSAPatterns do + @moduledoc false use Beaver alias Beaver.MLIR alias MLIR.Type diff --git a/test/support/toy_pass.ex b/test/support/toy_pass.ex index 4f9852b37..669b409da 100644 --- a/test/support/toy_pass.ex +++ b/test/support/toy_pass.ex @@ -21,7 +21,7 @@ defmodule ToyPass do def run(%MLIR.Operation{} = operation) do with 1 <- Beaver.Walker.regions(operation) |> Enum.count(), {:ok, _} <- - MLIR.Pattern.apply_(MLIR.Module.from_operation(operation), [replace_add_op(benefit: 2)]) do + MLIR.apply_(MLIR.Module.from_operation(operation), [replace_add_op(benefit: 2)]) do :ok else _ -> raise "unreachable" diff --git a/test/support/translate.ex b/test/support/translate.ex index d0f08ed30..c198f84f1 100644 --- a/test/support/translate.ex +++ b/test/support/translate.ex @@ -1,4 +1,5 @@ defmodule TranslateMLIR do + @moduledoc false defmacro __using__(_) do quote do import TranslateMLIR @@ -300,14 +301,14 @@ defmodule TranslateMLIR do jit = ~m{#{ir}}.(ctx) - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") |> convert_scf_to_cf |> convert_arith_to_llvm() |> convert_index_to_llvm() |> convert_func_to_llvm() - |> MLIR.Pass.Composer.append("finalize-memref-to-llvm") + |> Beaver.Composer.append("finalize-memref-to-llvm") |> reconcile_unrealized_casts - |> MLIR.Pass.Composer.run!(print: System.get_env("DEFM_PRINT_IR") == "1") + |> Beaver.Composer.run!(print: System.get_env("DEFM_PRINT_IR") == "1") |> MLIR.ExecutionEngine.create!() %{mode: return_mode, maker: {mod, func, args}} = @@ -352,7 +353,7 @@ defmodule TranslateMLIR do {ir, return_convention} = compile_defm(call, expr, ctx) - ir = ir |> MLIR.Operation.verify!() |> MLIR.to_string() + ir = ir |> MLIR.verify!() |> MLIR.to_string() MLIR.Context.destroy(ctx) diff --git a/test/tosa_test.exs b/test/tosa_test.exs index c23a734a2..a197b4779 100644 --- a/test/tosa_test.exs +++ b/test/tosa_test.exs @@ -4,20 +4,23 @@ defmodule TosaTest do alias Beaver.MLIR.Dialect.{Func, TOSA} require Func alias Beaver.Native - import MLIR.{Transforms, Conversion} + import MLIR.{Transform, Conversion} def test_lower_to_llvm(op) do op - |> MLIR.Pass.Composer.nested("func.func", [convert_vector_to_scf(), convert_linalg_to_loops()]) + |> Beaver.Composer.nested("func.func", [ + convert_vector_to_scf(), + convert_linalg_to_loops() + ]) |> lower_affine() |> convert_scf_to_cf() |> canonicalize() |> cse() - |> MLIR.Pass.Composer.append("convert-vector-to-llvm{reassociate-fp-reductions}") - |> MLIR.Pass.Composer.nested("func.func", convert_math_to_llvm()) - |> MLIR.Pass.Composer.append("expand-strided-metadata") + |> Beaver.Composer.append("convert-vector-to-llvm{reassociate-fp-reductions}") + |> Beaver.Composer.nested("func.func", convert_math_to_llvm()) + |> Beaver.Composer.append("expand-strided-metadata") |> lower_affine() - |> MLIR.Pass.Composer.append("finalize-memref-to-llvm") + |> Beaver.Composer.append("finalize-memref-to-llvm") |> convert_func_to_llvm |> convert_index_to_llvm |> reconcile_unrealized_casts @@ -50,16 +53,16 @@ defmodule TosaTest do ir |> MLIR.Operation.from_module() - |> MLIR.Pass.Composer.nested("func.func", [ + |> Beaver.Composer.nested("func.func", [ tosa_to_linalg_named(), tosa_to_linalg(), tosa_to_arith() ]) - |> MLIR.Pass.Composer.append("one-shot-bufferize{bufferize-function-boundaries}") - |> MLIR.Pass.Composer.append("buffer-deallocation-pipeline") - |> MLIR.Pass.Composer.nested("func.func", "llvm-request-c-wrappers") + |> Beaver.Composer.append("one-shot-bufferize{bufferize-function-boundaries}") + |> Beaver.Composer.append("buffer-deallocation-pipeline") + |> Beaver.Composer.nested("func.func", "llvm-request-c-wrappers") |> test_lower_to_llvm - |> MLIR.Pass.Composer.run!() + |> Beaver.Composer.run!() jit = ir |> MLIR.ExecutionEngine.create!() diff --git a/test/type_infer_test.exs b/test/type_infer_test.exs index b34233354..c25d27d1e 100644 --- a/test/type_infer_test.exs +++ b/test/type_infer_test.exs @@ -28,8 +28,8 @@ defmodule TypeInferTest do end end end - |> MLIR.Operation.verify!() - |> MLIR.Transforms.canonicalize() - |> MLIR.Pass.Composer.run!() + |> MLIR.verify!() + |> MLIR.Transform.canonicalize() + |> Beaver.Composer.run!() end end From 111e4a14248f446f5a3ba73113cced50b5f6d3ca Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 00:35:34 +0800 Subject: [PATCH 02/19] Update gpu_test.exs --- test/gpu_test.exs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/gpu_test.exs b/test/gpu_test.exs index ead16e99c..15d365bee 100644 --- a/test/gpu_test.exs +++ b/test/gpu_test.exs @@ -11,7 +11,8 @@ defmodule GPUTest do # trap sigchld when running ptxas to generate fatbin System.trap_signal(:sigchld, fn -> :ok end) - assert MLIR.Module.create(ctx, File.read!("test/gpu-to-cubin.mlir")) + assert File.read!("test/gpu-to-cubin.mlir") + |> MLIR.Module.create(ctx: ctx) |> Beaver.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=fatbin}") |> Beaver.Composer.run!() |> to_string() =~ "gpu.binary @other_func_kernel" @@ -20,7 +21,7 @@ defmodule GPUTest do test "isa", %{ctx: ctx} do MLIR.Context.register_translations(ctx) - assert MLIR.Module.create(ctx, File.read!("test/gpu-to-cubin.mlir")) + assert MLIR.Module.create(File.read!("test/gpu-to-cubin.mlir")).(ctx) |> Beaver.Composer.append("gpu-lower-to-nvvm-pipeline{cubin-format=isa}") |> Beaver.Composer.run!() |> to_string() =~ "gpu.binary @other_func_kernel" From 19d61851f148963cf1e5c991a4cf1c31d3a43938 Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 03:59:12 +0800 Subject: [PATCH 03/19] use try after in with_registry --- lib/beaver/mlir/context.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/beaver/mlir/context.ex b/lib/beaver/mlir/context.ex index 9dbfe9649..24a46055d 100644 --- a/lib/beaver/mlir/context.ex +++ b/lib/beaver/mlir/context.ex @@ -12,8 +12,12 @@ defmodule Beaver.MLIR.Context do def with_registry(ctx, fun) when is_function(fun, 1) do registry = mlirDialectRegistryCreate() mlirContextAppendDialectRegistry(ctx, registry) - fun.(registry) - mlirDialectRegistryDestroy(registry) + + try do + fun.(registry) + after + mlirDialectRegistryDestroy(registry) + end end # create an interim registry and append all dialects to the context From 9ab3ab83ee7146c335e2d917c4a1b9be76f14af3 Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 22:44:12 +0800 Subject: [PATCH 04/19] address review --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33c5192fc..a852d793a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ Modules including `Beaver.Walker`, `Beaver.Composer` Utilities are the helper functions that help to generate or manipulate MLIR IR. They are implemented in Elixir and is designed to be used in the DSL part to further enhance it and improve ergonomics. ### Bindings -Modules including `Beaver.MLIR`, `Beaver.MLIR.Dialect`, `Beaver.MLIR.Pass`, `Beaver.MLIR.Transforms`, `Beaver.MLIR.ExecutionEngine` +Modules including `Beaver.MLIR`, `Beaver.MLIR.Dialect`, `Beaver.MLIR.Pass`, `Beaver.MLIR.Transform`, `Beaver.MLIR.ExecutionEngine` Bindings are the part that provides the interface to the MLIR CAPIs. It is implemented in Zig and is responsible for calling MLIR functions. Note that Beaver's bindings will try not to use `TableGen` and instead try to make use Elixir and Zig's meta-programming features to generate the bindings. diff --git a/README.md b/README.md index d842c997a..9a0cbde51 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ defmodule ToyPass do end use Beaver -import MLIR.Transforms +import MLIR.Transform ctx = MLIR.Context.create() ~m""" module { From 2a8c9680b80bdbcf49837575995b58f748f30fc1 Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 22:45:55 +0800 Subject: [PATCH 05/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a0cbde51..6f13ce394 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ defmodule ToyPass do def run(%MLIR.Operation{} = operation) do with 1 <- Beaver.Walker.regions(operation) |> Enum.count(), {:ok, _} <- - MLIR.Pattern.apply_(MLIR.Module.from_operation(operation), [replace_add_op(benefit: 2)]) do + MLIR.apply_(MLIR.Module.from_operation(operation), [replace_add_op(benefit: 2)]) do :ok else _ -> raise "unreachable" From 5a1e34be28e5ef71cfe82483d2c08023cbadbe96 Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 22:48:39 +0800 Subject: [PATCH 06/19] move with_diagnostics to ctx --- lib/beaver.ex | 35 ----------------------------------- lib/beaver/mlir/context.ex | 35 +++++++++++++++++++++++++++++++++++ test/diagnostic_test.exs | 3 ++- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/lib/beaver.ex b/lib/beaver.ex index c8d849882..94ad0515b 100644 --- a/lib/beaver.ex +++ b/lib/beaver.ex @@ -296,39 +296,4 @@ defmodule Beaver do "`>>>` operator is expected to be transformed away. Maybe you forget to put the expression inside the Beaver.mlir/1 macro's do block?" ) end - - @type result :: any() - @type handler_acc :: any() - @spec with_diagnostics( - MLIR.Context.t(), - (-> result), - (MLIR.Diagnostic.t(), handler_acc -> handler_acc) - | {(-> handler_acc), (MLIR.Diagnostic.t(), handler_acc -> handler_acc)} - ) :: {result, handler_acc} - - @doc """ - Run the given function with a diagnostic handler attached to the MLIR context. - """ - def with_diagnostics(%MLIR.Context{} = ctx, fun, handler) - when is_function(fun, 0) and is_function(handler, 2) do - with_diagnostics(%MLIR.Context{} = ctx, fun, {fn -> nil end, handler}) - end - - def with_diagnostics(%MLIR.Context{} = ctx, fun, {init, handler}) - when is_function(fun, 0) and is_function(init, 0) and is_function(handler, 2) do - {:ok, pid} = - GenServer.start( - Beaver.Capturer, - handler - ) - - handler_id = Beaver.Capturer.attach(ctx, pid) - - try do - {fun.(), Beaver.Capturer.collect(pid)} - after - Beaver.MLIR.Diagnostic.detach(ctx, handler_id) - :ok = GenServer.stop(pid) - end - end end diff --git a/lib/beaver/mlir/context.ex b/lib/beaver/mlir/context.ex index 24a46055d..4b266fa8d 100644 --- a/lib/beaver/mlir/context.ex +++ b/lib/beaver/mlir/context.ex @@ -50,4 +50,39 @@ defmodule Beaver.MLIR.Context do defdelegate destroy(ctx), to: MLIR.CAPI, as: :mlirContextDestroy defdelegate register_translations(ctx), to: MLIR.CAPI, as: :mlirRegisterAllLLVMTranslations + + @type result :: any() + @type handler_acc :: any() + @spec with_diagnostics( + MLIR.Context.t(), + (-> result), + (MLIR.Diagnostic.t(), handler_acc -> handler_acc) + | {(-> handler_acc), (MLIR.Diagnostic.t(), handler_acc -> handler_acc)} + ) :: {result, handler_acc} + + @doc """ + Run the given function with a diagnostic handler attached to the MLIR context. + """ + def with_diagnostics(%MLIR.Context{} = ctx, fun, handler) + when is_function(fun, 0) and is_function(handler, 2) do + with_diagnostics(%MLIR.Context{} = ctx, fun, {fn -> nil end, handler}) + end + + def with_diagnostics(%MLIR.Context{} = ctx, fun, {init, handler}) + when is_function(fun, 0) and is_function(init, 0) and is_function(handler, 2) do + {:ok, pid} = + GenServer.start( + Beaver.Capturer, + handler + ) + + handler_id = Beaver.Capturer.attach(ctx, pid) + + try do + {fun.(), Beaver.Capturer.collect(pid)} + after + Beaver.MLIR.Diagnostic.detach(ctx, handler_id) + :ok = GenServer.stop(pid) + end + end end diff --git a/test/diagnostic_test.exs b/test/diagnostic_test.exs index bbb11839b..fca4db714 100644 --- a/test/diagnostic_test.exs +++ b/test/diagnostic_test.exs @@ -1,6 +1,7 @@ defmodule DiagnosticTest do use Beaver.Case, async: true, diagnostic: :server alias Beaver.MLIR.Attribute + alias Beaver.MLIR defmodule DiagnosticTestHelper do def start_and_attach(ctx, cb) do @@ -62,7 +63,7 @@ defmodule DiagnosticTest do describe "with_diagnostics" do test "no init", %{ctx: ctx} do {%RuntimeError{}, txt} = - Beaver.with_diagnostics( + MLIR.Context.with_diagnostics( ctx, fn -> assert_raise RuntimeError, @err_msg, fn -> DiagnosticTestHelper.get_attr(ctx) end From 83271ef70a0d0cdb014b9e0e681e7c969ccc6917 Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 22:53:56 +0800 Subject: [PATCH 07/19] add with_symbol_table/2 --- bench/enif_add.ex | 20 +++++++++++--------- lib/beaver/mlir/operation.ex | 10 ++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/bench/enif_add.ex b/bench/enif_add.ex index 0938f56aa..048f3395c 100644 --- a/bench/enif_add.ex +++ b/bench/enif_add.ex @@ -10,18 +10,20 @@ defmodule AddENIF do @impl ENIFSupport def after_verification(op) do - s_table = op |> MLIR.Operation.from_module() |> MLIR.CAPI.mlirSymbolTableCreate() - found = MLIR.CAPI.mlirSymbolTableLookup(s_table, MLIR.StringRef.create("enif_make_int64")) + op + |> MLIR.Operation.from_module() + |> MLIR.Operation.with_symbol_table(fn s_table -> + found = MLIR.CAPI.mlirSymbolTableLookup(s_table, MLIR.StringRef.create("enif_make_int64")) - if MLIR.null?(found) do - raise "Function not found" - end + if MLIR.null?(found) do + raise "Function not found" + end - if not MLIR.Dialect.Func.external?(found) do - raise "Function is not external" - end + if not MLIR.Dialect.Func.external?(found) do + raise "Function is not external" + end + end) - MLIR.CAPI.mlirSymbolTableDestroy(s_table) op end diff --git a/lib/beaver/mlir/operation.ex b/lib/beaver/mlir/operation.ex index 73714ce2f..0c729e41f 100644 --- a/lib/beaver/mlir/operation.ex +++ b/lib/beaver/mlir/operation.ex @@ -162,4 +162,14 @@ defmodule Beaver.MLIR.Operation do mlirOperationRemoveAttributeByName(operation, MLIR.StringRef.create(attribute)) {attr, operation} end + + def with_symbol_table(%__MODULE__{} = op, fun) do + symbol_table = mlirSymbolTableCreate(op) + + try do + fun.(symbol_table) + after + mlirSymbolTableDestroy(symbol_table) + end + end end From e996e2bd033361af70c5ffcabc91ff0739589972 Mon Sep 17 00:00:00 2001 From: tsai Date: Sat, 2 Nov 2024 22:54:55 +0800 Subject: [PATCH 08/19] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a852d793a..62fb1931d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,7 +57,7 @@ Bindings are the part that provides the interface to the MLIR CAPIs. It is imple - Install Vulkan SDK (global installation is required), reference: https://vulkan.lunarg.com/sdk/home - Setting environment variable by adding commands these to your bash/zsh profile: - ``` + ```bash # you might need to change the version here cd $HOME/VulkanSDK/1.3.216.0/ source setup-env.sh From dbd062875eacc76c0e7e54ae561cb8f23406d6b4 Mon Sep 17 00:00:00 2001 From: tsai Date: Sun, 3 Nov 2024 03:03:20 +0800 Subject: [PATCH 09/19] address review --- CONTRIBUTING.md | 4 ++-- lib/beaver/mlir.ex | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62fb1931d..32db59d94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ Bindings are the part that provides the interface to the MLIR CAPIs. It is imple git clone https://github.com/beaver-lodge/beaver.git git clone https://github.com/beaver-lodge/kinda.git ``` -- Make sure LLVM environment variable is set properly, otherwise it might fail to build +- Make sure LLVM environment variable is set properly, as otherwise it might fail to build ```bash echo $LLVM_CONFIG_PATH @@ -145,7 +145,7 @@ cmake-format -i native/**/CMakeLists.txt native/**/*.cmake LLVM/MLIR is a giant project, and built around that Beaver have thousands of functions. To properly ship LLVM/MLIR and streamline the development process, we need to carefully break the functionalities at different level into different Erlang apps under the same umbrella. - `:beaver`: Elixir and C/C++ hybrid. - - Top level app ships the high level functionalities including IR generation and pattern definition. + - Top-level app ships the high-level functionalities including IR generation and pattern definition. - MLIR CAPI wrappers built by parsing LLVM/MLIR CAPI C headers and some middle level helper functions to hide the C pointer related operations. This app will add the loaded MLIR C library and managed MLIR context to Erlang supervisor tree. Rust is also used in this app, but mainly for LLVM/MLIR CMake integration. - All the Ops defined in stock MLIR dialects, built by querying the registry. This app will ship MLIR Ops with Erlang idiomatic practices like behavior compliance. - `:kinda`: Elixir and Zig hybrid, generating NIFs from MLIR C headers. Repo: https://github.com/beaver-lodge/kinda diff --git a/lib/beaver/mlir.ex b/lib/beaver/mlir.ex index 9399c5270..7a1130f9d 100644 --- a/lib/beaver/mlir.ex +++ b/lib/beaver/mlir.ex @@ -31,9 +31,9 @@ defmodule Beaver.MLIR do Many MLIR operations can return null values. Use `null?/1` to safely check entities before performing operations that require non-null values. - ## Name spaces to include difference kinds of CAPI delegates - - `Beaver.MLIR.***`: APIs to related to lifecycle, including creating and destroying MLIR entities. - - `Beaver.MLIR`: APIs like `Beaver.MLIR.dump!/1` or `Beaver.MLIR.null?/1`. These are standard features are generally expected in any MLIR tools + ## Name spaces to include different kinds of CAPI delegates + - `Beaver.MLIR.***`: APIs related to lifecycle, including creating and destroying MLIR entities. + - `Beaver.MLIR`: APIs like `Beaver.MLIR.dump!/1` or `Beaver.MLIR.null?/1`. These are standard features generally expected in any MLIR tools. """ defp extract_entity_name(m) do From 460eaf83e2831ea6204d526d512800b1c97d6e56 Mon Sep 17 00:00:00 2001 From: tsai Date: Sun, 3 Nov 2024 03:04:38 +0800 Subject: [PATCH 10/19] address reviews --- lib/beaver/changeset.ex | 2 +- lib/beaver/pattern.ex | 2 +- test/cf_test.exs | 2 +- test/support/elixir_ast_dialect.ex | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/beaver/changeset.ex b/lib/beaver/changeset.ex index 9688ac691..fbb4359d8 100644 --- a/lib/beaver/changeset.ex +++ b/lib/beaver/changeset.ex @@ -132,7 +132,7 @@ defmodule Beaver.Changeset do def add_argument( %__MODULE__{operands: operands} = changeset, - %Beaver.MLIR.Value{} = operand + %MLIR.Value{} = operand ) do %__MODULE__{changeset | operands: operands ++ [operand]} end diff --git a/lib/beaver/pattern.ex b/lib/beaver/pattern.ex index aa75ebfef..c08529a3a 100644 --- a/lib/beaver/pattern.ex +++ b/lib/beaver/pattern.ex @@ -250,7 +250,7 @@ defmodule Beaver.Pattern do end end - defp result(%Env{blk: block, ctx: ctx}, %Beaver.MLIR.Value{} = v, i) + defp result(%Env{blk: block, ctx: ctx}, %MLIR.Value{} = v, i) when is_integer(i) do mlir blk: block, ctx: ctx do PDL.result(v, index: Beaver.MLIR.Attribute.integer(Beaver.MLIR.Type.i32(), i)) >>> diff --git a/test/cf_test.exs b/test/cf_test.exs index 398c71505..5cc26605f 100644 --- a/test/cf_test.exs +++ b/test/cf_test.exs @@ -150,7 +150,7 @@ defmodule CfTest do # at some point you should see logging of node not supported for MLIR CAPI Value, # let's add the match to disable this kind of logging - defp gen_mlir(%Beaver.MLIR.Value{} = mlir, acc) do + defp gen_mlir(%MLIR.Value{} = mlir, acc) do {mlir, acc} end diff --git a/test/support/elixir_ast_dialect.ex b/test/support/elixir_ast_dialect.ex index 236e69c5b..ba5277c31 100644 --- a/test/support/elixir_ast_dialect.ex +++ b/test/support/elixir_ast_dialect.ex @@ -56,7 +56,7 @@ defmodule ElixirAST do {:__block__, _, values} -> values |> List.last() - %Beaver.MLIR.Value{} = v -> + %MLIR.Value{} = v -> v end From 5ed93158777e789f8d65fbcb0e9419ec0d6f0a2a Mon Sep 17 00:00:00 2001 From: tsai Date: Sun, 3 Nov 2024 03:05:19 +0800 Subject: [PATCH 11/19] Update context.ex --- lib/beaver/mlir/context.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/beaver/mlir/context.ex b/lib/beaver/mlir/context.ex index 4b266fa8d..5136999a1 100644 --- a/lib/beaver/mlir/context.ex +++ b/lib/beaver/mlir/context.ex @@ -22,15 +22,11 @@ defmodule Beaver.MLIR.Context do # create an interim registry and append all dialects to the context defp load_all_dialects(ctx) do - registry = mlirDialectRegistryCreate() - with_registry(ctx, fn registry -> mlirRegisterAllDialects(registry) mlirContextAppendDialectRegistry(ctx, registry) mlirContextLoadAllAvailableDialects(ctx) end) - - mlirDialectRegistryDestroy(registry) end @type context_option :: {:allow_unregistered, boolean()} | {:all_dialects, boolean()} From 4c7640c8229ccb9b56a7e51d907430571d28cd80 Mon Sep 17 00:00:00 2001 From: tsai Date: Sun, 3 Nov 2024 03:13:34 +0800 Subject: [PATCH 12/19] Update mlir.ex --- lib/beaver/mlir.ex | 59 +++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/lib/beaver/mlir.ex b/lib/beaver/mlir.ex index 7a1130f9d..362a644ca 100644 --- a/lib/beaver/mlir.ex +++ b/lib/beaver/mlir.ex @@ -267,8 +267,8 @@ defmodule Beaver.MLIR do {:ok, module} -> module - _ -> - raise "failed to apply pattern" + {:error, msg} -> + raise msg end end @@ -280,33 +280,44 @@ defmodule Beaver.MLIR do def apply_(op, patterns, opts \\ @apply_default_opts) when is_list(patterns) do if MLIR.null?(op), do: raise("op is null") ctx = MLIR.Operation.from_module(op) |> MLIR.context() - pattern_module = MLIR.Location.from_env(__ENV__, ctx: ctx) |> MLIR.Module.empty() - block = Beaver.MLIR.Module.body(pattern_module) - - for p <- patterns do - p = p.(ctx, block) - - if opts[:debug] do - p |> MLIR.dump!() - end - end - - MLIR.verify!(pattern_module) - MLIR.verify!(op) - pdl_pat_mod = mlirPDLPatternModuleFromModule(pattern_module) - - frozen_pat_set = - pdl_pat_mod |> mlirRewritePatternSetFromPDLPatternModule() |> mlirFreezeRewritePattern() - result = beaverModuleApplyPatternsAndFoldGreedily(op, frozen_pat_set) - mlirPDLPatternModuleDestroy(pdl_pat_mod) - mlirFrozenRewritePatternSetDestroy(frozen_pat_set) - MLIR.Module.destroy(pattern_module) + {result, err} = + MLIR.Context.with_diagnostics( + ctx, + fn -> + pattern_module = MLIR.Location.from_env(__ENV__, ctx: ctx) |> MLIR.Module.empty() + block = Beaver.MLIR.Module.body(pattern_module) + + for p <- patterns do + p = p.(ctx, block) + + if opts[:debug] do + p |> MLIR.dump!() + end + end + + MLIR.verify!(pattern_module) + MLIR.verify!(op) + pdl_pat_mod = mlirPDLPatternModuleFromModule(pattern_module) + + frozen_pat_set = + pdl_pat_mod + |> mlirRewritePatternSetFromPDLPatternModule() + |> mlirFreezeRewritePattern() + + res = beaverModuleApplyPatternsAndFoldGreedily(op, frozen_pat_set) + mlirPDLPatternModuleDestroy(pdl_pat_mod) + mlirFrozenRewritePatternSetDestroy(frozen_pat_set) + MLIR.Module.destroy(pattern_module) + res + end, + &"#{&2} #{MLIR.to_string(&1)}" + ) if MLIR.LogicalResult.success?(result) do {:ok, op} else - {:error, "failed to apply pattern set"} + {:error, "failed to apply pattern set. #{err}"} end end end From ddeebc999b01262cbd292ec7f23f3512689c03af Mon Sep 17 00:00:00 2001 From: tsai Date: Sun, 3 Nov 2024 03:18:07 +0800 Subject: [PATCH 13/19] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32db59d94..42f4d73a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,8 @@ Bindings are the part that provides the interface to the MLIR CAPIs. It is imple ## Development -1. Install Elixir, https://elixir-lang.org/install.html -2. Install Zig, https://ziglang.org/learn/getting-started/#installing-zig +1. Install Elixir, [see installation guide](https://elixir-lang.org/install.html) +2. Install Zig, [see installation guide](https://ziglang.org/learn/getting-started/#installing-zig) 3. Install LLVM/MLIR - Option 1: Install with pip From e326a1f87f9f8fb432c6626c82ec5bc2919b659e Mon Sep 17 00:00:00 2001 From: tsai Date: Sun, 3 Nov 2024 03:22:53 +0800 Subject: [PATCH 14/19] Update elixir.yml --- .github/workflows/elixir.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 3d9a766c2..dd20394e7 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -128,6 +128,7 @@ jobs: - name: Run dev build run: | mix + mix credo mix elixir_make.checksum --only-local --ignore-unavailable --print mix hex.build - name: Run overhead profiling From a028dc45e6f787bccd288ed414b4b8e2700c0007 Mon Sep 17 00:00:00 2001 From: tsai Date: Mon, 4 Nov 2024 08:56:24 +0800 Subject: [PATCH 15/19] Update CONTRIBUTING.md --- CONTRIBUTING.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42f4d73a0..c46ca8fa9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,21 +139,3 @@ BEAVER_BUILD_CMAKE=1 mix hex.publish python3 -m pip install cmake-format cmake-format -i native/**/CMakeLists.txt native/**/*.cmake ``` - -## Erlang apps in Beaver - -LLVM/MLIR is a giant project, and built around that Beaver have thousands of functions. To properly ship LLVM/MLIR and streamline the development process, we need to carefully break the functionalities at different level into different Erlang apps under the same umbrella. - -- `:beaver`: Elixir and C/C++ hybrid. - - Top-level app ships the high-level functionalities including IR generation and pattern definition. - - MLIR CAPI wrappers built by parsing LLVM/MLIR CAPI C headers and some middle level helper functions to hide the C pointer related operations. This app will add the loaded MLIR C library and managed MLIR context to Erlang supervisor tree. Rust is also used in this app, but mainly for LLVM/MLIR CMake integration. - - All the Ops defined in stock MLIR dialects, built by querying the registry. This app will ship MLIR Ops with Erlang idiomatic practices like behavior compliance. -- `:kinda`: Elixir and Zig hybrid, generating NIFs from MLIR C headers. Repo: https://github.com/beaver-lodge/kinda - -## Miscellaneous - -Some other notes on consuming and development - -- Only `:beaver` and `:kinda` are designed to be used as stand-alone app being directly consumed by other apps. -- `:manx` could only work with Nx. -- Although `:kinda` is built for Beaver, any Erlang/Elixir app with interest bundling some C API could take advantage of it as well. From b15065a090a9170f417bb1c16a2d133cf9127e30 Mon Sep 17 00:00:00 2001 From: tsai Date: Mon, 4 Nov 2024 19:32:02 +0800 Subject: [PATCH 16/19] refactor pass runner with supervisor --- lib/beaver/application.ex | 9 +++++- lib/beaver/composer.ex | 51 ++++++++++++++++++++++++++++---- lib/beaver/mlir/external_pass.ex | 46 ++-------------------------- lib/beaver/mlir/pass.ex | 9 ++---- lib/beaver/pass_runner.ex | 8 +++-- test/capi_test.exs | 6 ++-- 6 files changed, 67 insertions(+), 62 deletions(-) diff --git a/lib/beaver/application.ex b/lib/beaver/application.ex index 2a2fe88a1..06435b167 100644 --- a/lib/beaver/application.ex +++ b/lib/beaver/application.ex @@ -3,6 +3,13 @@ defmodule Beaver.Application do require Logger @moduledoc false def start(_type, _args) do - Supervisor.start_link(Beaver.MLIR.Pass.global_registrar_child_specs(), strategy: :one_for_one) + Supervisor.start_link( + Beaver.MLIR.Pass.global_registrar_child_specs() ++ + [ + {DynamicSupervisor, name: Beaver.Composer.DynamicSupervisor, strategy: :one_for_one}, + {Registry, keys: :unique, name: Beaver.Composer.Registry} + ], + strategy: :one_for_one + ) end end diff --git a/lib/beaver/composer.ex b/lib/beaver/composer.ex index 92e09fb93..943b18ca2 100644 --- a/lib/beaver/composer.ex +++ b/lib/beaver/composer.ex @@ -31,16 +31,57 @@ defmodule Beaver.Composer do nested(composer_or_op, op_name, [pass]) end - defp create_pass(pass_module) when is_atom(pass_module) do - MLIR.ExternalPass.create(pass_module) + defp op_name_from_persistent_attributes(pass_module) do + op_name = pass_module.__info__(:attributes)[:root_op] || [] + op_name = op_name |> List.first() + op_name || "builtin.module" end - defp create_pass(%MLIR.Pass{} = pass) do + # Create an external pass. + defp do_create_pass(pid, argument, description, op) do + MLIR.CAPI.beaver_raw_create_mlir_pass( + MLIR.StringRef.create(argument).ref, + MLIR.StringRef.create(argument).ref, + MLIR.StringRef.create(description).ref, + MLIR.StringRef.create(op).ref, + pid + ) + |> then(&%MLIR.Pass{ref: &1, handler: pid}) + end + + @supervisor __MODULE__.DynamicSupervisor + @registry __MODULE__.Registry + defp create_pass(argument, desc, op, run) do + spec = + {Beaver.PassRunner, [run, name: {:via, Registry, {@registry, :"#{argument}-#{op}"}}]} + + case DynamicSupervisor.start_child(@supervisor, spec) do + {:ok, pid} -> + pid + + {:error, {:already_started, pid}} -> + pid + + {:error, e} -> + raise Application.format_error(e) + end + |> do_create_pass(argument, desc, op) + end + + def create_pass(%MLIR.Pass{} = pass) do pass end - defp create_pass({argument, op, run}) when is_bitstring(op) and is_function(run) do - MLIR.ExternalPass.create({argument, op, run}) + def create_pass({argument, op, run}) when is_function(run) do + description = "beaver generated pass of #{Function.info(run) |> inspect}" + create_pass(argument, description, op, run) + end + + def create_pass(pass_module) do + description = "beaver generated pass of #{pass_module}" + op_name = op_name_from_persistent_attributes(pass_module) + name = Atom.to_string(pass_module) + create_pass(name, description, op_name, &pass_module.run/1) end defp add_pipeline(%MLIR.OpPassManager{} = pm, pipeline_str) diff --git a/lib/beaver/mlir/external_pass.ex b/lib/beaver/mlir/external_pass.ex index 4f8852282..d4abd5a5a 100644 --- a/lib/beaver/mlir/external_pass.ex +++ b/lib/beaver/mlir/external_pass.ex @@ -1,48 +1,6 @@ defmodule Beaver.MLIR.ExternalPass do @moduledoc false - # Lower level API to work with MLIR's external pass (pass defined in C). Use Beaver.MLIR.Pass for idiomatic Erlang behavior. - alias Beaver.MLIR - alias Beaver.MLIR.CAPI + # Lower level API to work with MLIR CAPI's external pass (pass defined in C). + # Note that external pass is a C API specific concept, not a concept generally available in MLIR, so we don't expose it from Elixir. use Kinda.ResourceKind, forward_module: Beaver.Native - - defp op_name_from_persistent_attributes(pass_module) do - op_name = pass_module.__info__(:attributes)[:root_op] || [] - op_name = op_name |> List.first() - op_name || "builtin.module" - end - - defp do_create(name, description, op, run) do - {:ok, pid} = GenServer.start_link(Beaver.PassRunner, run) - description = description |> MLIR.StringRef.create() - op_name = op |> MLIR.StringRef.create() - name = name |> MLIR.StringRef.create() - argument = name - - ref = - CAPI.beaver_raw_create_mlir_pass( - name.ref, - argument.ref, - description.ref, - op_name.ref, - pid - ) - - %MLIR.Pass{ref: ref, handler: pid} - end - - @doc """ - Create a pass by passing a callback module - """ - - def create({name, op, run}) when is_bitstring(op) and is_function(run) do - description = "beaver generated pass of #{Function.info(run) |> inspect}" - do_create(name, description, op, run) - end - - def create(pass_module) when is_atom(pass_module) do - description = "beaver generated pass of #{pass_module}" - op_name = op_name_from_persistent_attributes(pass_module) - name = Atom.to_string(pass_module) - do_create(name, description, op_name, &pass_module.run/1) - end end diff --git a/lib/beaver/mlir/pass.ex b/lib/beaver/mlir/pass.ex index 5f314d7d4..9d8cdec86 100644 --- a/lib/beaver/mlir/pass.ex +++ b/lib/beaver/mlir/pass.ex @@ -2,14 +2,9 @@ defmodule Beaver.MLIR.Pass do @moduledoc """ This module defines functions working with MLIR #{__MODULE__ |> Module.split() |> List.last()}. """ - alias Beaver.MLIR - - use Kinda.ResourceKind, - fields: [handler: nil], - forward_module: Beaver.Native - - @callback run(MLIR.Operation.t()) :: :ok | :error + use Kinda.ResourceKind, fields: [handler: nil], forward_module: Beaver.Native + @callback run(MLIR.Operation.t()) :: any() defmacro __using__(opts) do quote do diff --git a/lib/beaver/pass_runner.ex b/lib/beaver/pass_runner.ex index b4d38e171..1fd211c28 100644 --- a/lib/beaver/pass_runner.ex +++ b/lib/beaver/pass_runner.ex @@ -6,9 +6,13 @@ defmodule Beaver.PassRunner do """ use GenServer + def start_link([run | opts]) do + GenServer.start_link(__MODULE__, run, opts) + end + @impl true - def init(fun) do - {:ok, %{run: fun}} + def init(run) do + {:ok, %{run: run}} end @impl true diff --git a/test/capi_test.exs b/test/capi_test.exs index 56326ab95..28211a1ff 100644 --- a/test/capi_test.exs +++ b/test/capi_test.exs @@ -189,7 +189,7 @@ defmodule MlirTest do module = create_adder_module(ctx) assert not MLIR.null?(module) type_id_allocator = mlirTypeIDAllocatorCreate() - external = %MLIR.Pass{} = MLIR.ExternalPass.create(TestPass) + external = %MLIR.Pass{} = Beaver.Composer.create_pass(TestPass) pm = mlirPassManagerCreate(ctx) mlirPassManagerAddOwnedPass(pm, external) mlirPassManagerAddOwnedPass(pm, mlirCreateTransformsCSE()) @@ -204,7 +204,7 @@ defmodule MlirTest do test "Run a func operation pass", %{ctx: ctx} do module = create_adder_module(ctx) assert not MLIR.null?(module) - external = %MLIR.Pass{} = MLIR.ExternalPass.create(TestFuncPass) + external = %MLIR.Pass{} = Beaver.Composer.create_pass(TestFuncPass) pm = mlirPassManagerCreate(ctx) npm = mlirPassManagerGetNestedUnder(pm, MLIR.StringRef.create("func.func")) mlirOpPassManagerAddOwnedPass(npm, external) @@ -217,7 +217,7 @@ defmodule MlirTest do test "Run pass with patterns", %{ctx: ctx} do module = create_redundant_transpose_module(ctx) assert not MLIR.null?(module) - external = %MLIR.Pass{} = MLIR.ExternalPass.create(TestFuncPass) + external = %MLIR.Pass{} = Beaver.Composer.create_pass(TestFuncPass) pm = mlirPassManagerCreate(ctx) npm = mlirPassManagerGetNestedUnder(pm, MLIR.StringRef.create("func.func")) mlirOpPassManagerAddOwnedPass(npm, external) From d58f7b7d12354128c48ac3a61c9f5ebe3062a10d Mon Sep 17 00:00:00 2001 From: tsai Date: Mon, 4 Nov 2024 19:51:51 +0800 Subject: [PATCH 17/19] Update composer.ex --- lib/beaver/composer.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/beaver/composer.ex b/lib/beaver/composer.ex index 943b18ca2..4dd2c0cbc 100644 --- a/lib/beaver/composer.ex +++ b/lib/beaver/composer.ex @@ -39,9 +39,11 @@ defmodule Beaver.Composer do # Create an external pass. defp do_create_pass(pid, argument, description, op) do + argument_ref = MLIR.StringRef.create(argument).ref + MLIR.CAPI.beaver_raw_create_mlir_pass( - MLIR.StringRef.create(argument).ref, - MLIR.StringRef.create(argument).ref, + argument_ref, + argument_ref, MLIR.StringRef.create(description).ref, MLIR.StringRef.create(op).ref, pid From 725b477a8cdbb9cee7b76b4c6eb4b0c04fe5c316 Mon Sep 17 00:00:00 2001 From: tsai Date: Mon, 4 Nov 2024 20:08:17 +0800 Subject: [PATCH 18/19] move child specs out of app file --- lib/beaver/application.ex | 11 +++-------- lib/beaver/composer.ex | 8 ++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/beaver/application.ex b/lib/beaver/application.ex index 06435b167..e90c9defe 100644 --- a/lib/beaver/application.ex +++ b/lib/beaver/application.ex @@ -3,13 +3,8 @@ defmodule Beaver.Application do require Logger @moduledoc false def start(_type, _args) do - Supervisor.start_link( - Beaver.MLIR.Pass.global_registrar_child_specs() ++ - [ - {DynamicSupervisor, name: Beaver.Composer.DynamicSupervisor, strategy: :one_for_one}, - {Registry, keys: :unique, name: Beaver.Composer.Registry} - ], - strategy: :one_for_one - ) + [Beaver.MLIR.Pass.global_registrar_child_specs(), Beaver.Composer.pass_runner_child_specs()] + |> List.flatten() + |> Supervisor.start_link(strategy: :one_for_one) end end diff --git a/lib/beaver/composer.ex b/lib/beaver/composer.ex index 4dd2c0cbc..ad11df728 100644 --- a/lib/beaver/composer.ex +++ b/lib/beaver/composer.ex @@ -221,4 +221,12 @@ defmodule Beaver.Composer do {:error, "Unexpected failure running passes"} end end + + @doc false + def pass_runner_child_specs() do + [ + {DynamicSupervisor, name: @supervisor, strategy: :one_for_one}, + {Registry, keys: :unique, name: @registry} + ] + end end From 2eb341e8e5fe42f86fed14818fa65aedcdcc9bce Mon Sep 17 00:00:00 2001 From: tsai Date: Mon, 4 Nov 2024 20:11:18 +0800 Subject: [PATCH 19/19] Update elixir.ex --- lib/beaver/exterior/elixir.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/beaver/exterior/elixir.ex b/lib/beaver/exterior/elixir.ex index dc43f3f7d..3fc244772 100644 --- a/lib/beaver/exterior/elixir.ex +++ b/lib/beaver/exterior/elixir.ex @@ -1,6 +1,6 @@ defmodule Beaver.MLIR.Dialect.Elixir do @moduledoc """ - This module defines Elixir dialect to represent Elixir AST in MLIR. This is at a moment a placeholder for future development. + This module defines Elixir dialect to represent Elixir AST in MLIR. This is at the moment a placeholder for future development. """ use Beaver.MLIR.Dialect, dialect: "elixir", @@ -9,7 +9,7 @@ end defmodule Beaver.Exterior.Elixir do @moduledoc """ - This module defines the Exterior for Elixir dialect. This is at a moment a placeholder for future development. + This module defines the Exterior for Elixir dialect. This is at the moment a placeholder for future development. """ alias Beaver.MLIR @behaviour Beaver.Exterior