Skip to content

Convert

convert

Exporting LLVM IR.

The convert_module() function converts an xDSL module containing LLVM dialect operations to an llvmlite.ir.Module. The same conversion is available through the CLI command xdsl-opt -t llvm.

For example, take the following hello.mlir file:

builtin.module {
  llvm.mlir.global internal constant @hello_str("Hello, World!\0A\00") {addr_space = 0 : i32} : !llvm.array<15 x i8>

  llvm.func @puts(!llvm.ptr)

  llvm.func @main() -> i32 {
    %0 = llvm.mlir.addressof @hello_str : !llvm.ptr
    llvm.call @puts(%0) : (!llvm.ptr) -> ()
    %ret = llvm.mlir.constant(0 : i32) : i32
    llvm.return %ret : i32
  }
}
xdsl-opt -t llvm hello.mlir | lli  # Hello, World!

The same conversion is available from the Python API, with the following hello.py file:

# /// script
# requires-python = ">=3.11"
# dependencies = ["xdsl[llvm]"]
# ///
import sys
from xdsl.context import Context
from xdsl.parser import Parser
from xdsl.dialects import get_all_dialects
from xdsl.backend.llvm.convert import convert_module

ctx = Context()

for name, factory in get_all_dialects().items():
    ctx.register_dialect(name, factory)

with open(sys.argv[1]) as f:
    module = Parser(ctx, f.read()).parse_module()

print(convert_module(module, fallback_target_triple=None))
uv run hello.py hello.mlir | lli  # Hello, World!

LLVMTarget dataclass

Bases: Target

Source code in xdsl/backend/llvm/convert.py
@dataclass(frozen=True)
class LLVMTarget(Target):
    name = "llvm"

    def emit(self, ctx: Context, module: ModuleOp, output: IO[str]) -> None:
        llvm_module = convert_module(module, fallback_target_triple=None)
        print(llvm_module, file=output)

name = 'llvm' class-attribute instance-attribute

__init__() -> None

emit(ctx: Context, module: ModuleOp, output: IO[str]) -> None

Source code in xdsl/backend/llvm/convert.py
def emit(self, ctx: Context, module: ModuleOp, output: IO[str]) -> None:
    llvm_module = convert_module(module, fallback_target_triple=None)
    print(llvm_module, file=output)

convert_module(module: ModuleOp, *, fallback_target_triple: str | None, data_layout: str = '') -> ir.Module

Convert an xDSL module to an LLVM module.

Parameters:

Name Type Description Default
module ModuleOp

The xDSL module to convert.

required
fallback_target_triple str | None

The target triple to use when the module does not carry an llvm.target_triple attribute. The triple of the resulting module is resolved as follows:

  1. If the module has an llvm.target_triple attribute, its value is always used and fallback_target_triple is ignored.
  2. Otherwise, if fallback_target_triple is not None, it is used as-is.
  3. Otherwise (it is None), the host's default triple, as reported by llvmlite.binding.get_default_triple(), is used.
required
data_layout str

The data layout to set on the resulting module. If empty, no data layout is set.

''

Returns:

Type Description
Module

The corresponding llvmlite IR module.

Raises:

Type Description
LLVMTranslationException

If the llvm.target_triple attribute is present but is not a StringAttr.

NotImplementedError

If the module contains an op that is not a llvm.func or llvm.global.

Source code in xdsl/backend/llvm/convert.py
def convert_module(
    module: ModuleOp,
    *,
    fallback_target_triple: str | None,
    data_layout: str = "",
) -> ir.Module:
    """
    Convert an xDSL module to an LLVM module.

    Args:
        module: The xDSL module to convert.
        fallback_target_triple: The target triple to use when the module does not
            carry an ``llvm.target_triple`` attribute. The triple of the resulting
            module is resolved as follows:

            1. If the module has an ``llvm.target_triple`` attribute, its value is
               always used and ``fallback_target_triple`` is ignored.
            2. Otherwise, if ``fallback_target_triple`` is not ``None``, it is used
               as-is.
            3. Otherwise (it is ``None``), the host's default triple, as reported by
               ``llvmlite.binding.get_default_triple()``, is used.
        data_layout: The data layout to set on the resulting module. If empty, no
            data layout is set.

    Returns:
        The corresponding llvmlite IR module.

    Raises:
        LLVMTranslationException: If the ``llvm.target_triple`` attribute is present
            but is not a ``StringAttr``.
        NotImplementedError: If the module contains an op that is not a ``llvm.func`` or ``llvm.global``.
    """
    llvm_module = ir.Module()
    module_triple = module.attributes.get("llvm.target_triple")
    if module_triple is not None:
        if not isinstance(module_triple, StringAttr):
            raise LLVMTranslationException(
                f"Unsupported llvm.target_triple attribute: {module_triple}"
            )
        llvm_module.triple = module_triple.data
    else:
        if fallback_target_triple is None:
            from llvmlite import binding

            fallback_target_triple = binding.get_default_triple()
        llvm_module.triple = fallback_target_triple
    if data_layout:
        llvm_module.data_layout = data_layout

    global_ops: list[llvm.GlobalOp] = []
    func_ops: list[llvm.FuncOp] = []
    for op in module.ops:
        match op:
            case llvm.GlobalOp():
                global_ops.append(op)
            case llvm.FuncOp():
                func_ops.append(op)
            case _:
                raise NotImplementedError(
                    f"Conversion not implemented for op: {op.name}"
                )

    # Convert globals first so that addressof lookups can always find them
    for global_op in global_ops:
        _convert_global(global_op, llvm_module)

    # Declare all functions (enables forward references)
    for op in func_ops:
        _declare_func(op, llvm_module)

    # Generate function bodies
    for func_op in func_ops:
        if func_op.body.blocks:
            _convert_func(func_op, llvm_module)

    return llvm_module