lib/beaver/mlir.ex

defmodule Beaver.MLIR do
  @moduledoc """
  Provide macros to insert MLIR context and IR element of structure. These macros are designed to mimic the behavior and aesthetics of `__MODULE__`, `__CALLER__` in Elixir.
  Its distinguished form is to indicate this should not be expected to be a function or a macro works like a function.
  """
  require Logger
  alias Beaver.MLIR.CAPI
  require Beaver.MLIR.CAPI

  alias Beaver.MLIR.{
    Value,
    Attribute,
    Type,
    Block,
    Location,
    Module,
    Operation,
    AffineMap,
    Dialect
  }

  alias Beaver.MLIR.CAPI.{
    MlirAffineExpr,
    MlirIntegerSet,
    MlirOpPassManager,
    MlirPassManager
  }

  defp dump_if_not_null(ir, dumper) do
    if is_null(ir) do
      {:error, "can't dump null"}
    else
      dumper.(ir)
    end
  end

  @type dump_opts :: [generic: boolean()]
  @spec dump(any(), dump_opts()) :: :ok
  @spec dump!(any(), dump_opts()) :: any()
  def dump(mlir, opts \\ [])

  def dump(%__MODULE__.Module{} = mlir, opts) do
    CAPI.mlirModuleGetOperation(mlir)
    |> dump(opts)
  end

  def dump(%__MODULE__.Operation{} = mlir, opts) do
    if opts[:generic] do
      dump_if_not_null(mlir, &CAPI.beaverOperationDumpGeneric/1)
    else
      dump_if_not_null(mlir, &CAPI.mlirOperationDump/1)
    end
  end

  def dump(%Attribute{} = mlir, _opts) do
    dump_if_not_null(mlir, &CAPI.mlirAttributeDump/1)
  end

  def dump(%Value{} = mlir, _opts) do
    dump_if_not_null(mlir, &CAPI.mlirValueDump/1)
  end

  def dump(%MlirAffineExpr{} = mlir, _opts) do
    dump_if_not_null(mlir, &CAPI.mlirAffineExprDump/1)
  end

  def dump(%AffineMap{} = mlir, _opts) do
    dump_if_not_null(mlir, &CAPI.mlirAffineMapDump/1)
  end

  def dump(%MlirIntegerSet{} = mlir, _opts) do
    dump_if_not_null(mlir, &CAPI.mlirIntegerSetDump/1)
  end

  def dump(%Type{} = mlir, _opts) do
    dump_if_not_null(mlir, &CAPI.mlirTypeDump/1)
  end

  def dump(_, _) do
    {:error, "not a mlir element can be dumped"}
  end

  def dump!(mlir, opts \\ [])

  def dump!(mlir, opts) do
    case dump(mlir, opts) do
      :ok ->
        mlir

      {:error, msg} ->
        raise msg
    end
  end

  def is_null(%Attribute{} = v) do
    CAPI.beaverAttributeIsNull(v) |> Beaver.Native.to_term()
  end

  def is_null(%Operation{} = v) do
    CAPI.beaverOperationIsNull(v) |> Beaver.Native.to_term()
  end

  def is_null(%Module{} = m) do
    CAPI.beaverModuleIsNull(m) |> Beaver.Native.to_term()
  end

  def is_null(%Block{} = v) do
    CAPI.beaverBlockIsNull(v) |> Beaver.Native.to_term()
  end

  def is_null(%Value{} = v) do
    CAPI.beaverValueIsNull(v) |> Beaver.Native.to_term()
  end

  def is_null(%Dialect{} = v) do
    CAPI.beaverDialectIsNull(v) |> Beaver.Native.to_term()
  end

  def to_string(%Attribute{ref: ref}) do
    CAPI.beaver_raw_beaver_attribute_print(ref) |> Beaver.Native.check!()
  end

  def to_string(%Value{ref: ref}) do
    CAPI.beaver_raw_beaver_value_print(ref) |> Beaver.Native.check!()
  end

  def to_string(%Operation{ref: ref}) do
    CAPI.beaver_raw_beaver_operation_print(ref)
    |> Beaver.Native.check!()
  end

  def to_string(%__MODULE__.Module{} = module) do
    module |> __MODULE__.Operation.from_module() |> __MODULE__.to_string()
  end

  def to_string(%Type{ref: ref}) do
    CAPI.beaver_raw_beaver_type_print(ref) |> Beaver.Native.check!()
  end

  def to_string(%AffineMap{ref: ref}) do
    CAPI.beaver_raw_beaver_affine_map_print(ref) |> Beaver.Native.check!()
  end

  def to_string(%Location{ref: ref}) do
    CAPI.beaver_raw_beaver_location_print(ref) |> Beaver.Native.check!()
  end

  def to_string(%MlirOpPassManager{ref: ref}) do
    CAPI.beaver_raw_beaver_pm_print(ref) |> Beaver.Native.check!()
  end

  def to_string(%MlirPassManager{} = pm) do
    pm |> CAPI.mlirPassManagerGetAsOpPassManager() |> __MODULE__.to_string()
  end
end