Skip to content

llvmliteir

LLVM-IR builder.

LLVMLiteIR

LLVMLiteIR()

Bases: Builder

LLVM-IR transpiler and compiler.

Source code in src/irx/builders/llvmliteir.py
753
754
755
756
def __init__(self) -> None:
    """Initialize LLVMIR."""
    super().__init__()
    self.translator: LLVMLiteIRVisitor = LLVMLiteIRVisitor()

build

build(expr: AST, output_file: str) -> None

Transpile the ASTx to LLVM-IR and build it to an executable file.

Source code in src/irx/builders/llvmliteir.py
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
def build(self, expr: astx.AST, output_file: str) -> None:
    """Transpile the ASTx to LLVM-IR and build it to an executable file."""
    result = self.translate(expr)

    result_mod = llvm.parse_assembly(result)
    result_object = self.translator.target_machine.emit_object(result_mod)

    with tempfile.NamedTemporaryFile(suffix="", delete=False) as temp_file:
        self.tmp_path = temp_file.name

    file_path_o = f"{self.tmp_path}.o"

    with open(file_path_o, "wb") as f:
        f.write(result_object)

    self.output_file = output_file

    run_command(
        [
            "clang",
            file_path_o,
            "-o",
            self.output_file,
        ]
    )

module

module() -> Module

Create a new ASTx Module.

Source code in src/irx/builders/base.py
133
134
135
def module(self) -> astx.Module:
    """Create a new ASTx Module."""
    return astx.Module()

run

run() -> None

Run the generated executable.

Source code in src/irx/builders/llvmliteir.py
784
785
786
def run(self) -> None:
    """Run the generated executable."""
    sh([self.output_file])

translate

translate(expr: AST) -> str

Transpile ASTx to LLVM-IR.

Source code in src/irx/builders/base.py
137
138
139
def translate(self, expr: astx.AST) -> str:
    """Transpile ASTx to LLVM-IR."""
    return self.translator.translate(expr)

LLVMLiteIRVisitor

LLVMLiteIRVisitor()

Bases: BuilderVisitor

LLVM-IR Translator.

Source code in src/irx/builders/llvmliteir.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def __init__(self) -> None:
    """Initialize LLVMTranslator object."""
    super().__init__()
    self.function_protos: dict[str, astx.FunctionPrototype] = {}
    self.result_stack: list[ir.Value | ir.Function] = []

    self.initialize()

    self.target = llvm.Target.from_default_triple()
    self.target_machine = self.target.create_target_machine(
        codemodel="small"
    )

    self._add_builtins()

create_entry_block_alloca

create_entry_block_alloca(var_name: str, type_name: str) -> Any

Create an alloca instruction in the entry block of the function.

This is used for mutable variables, etc.

Parameters:

  • fn
  • var_name (str) –
  • type_name (str) –

Returns:

  • An llvm allocation instance.
Source code in src/irx/builders/llvmliteir.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def create_entry_block_alloca(
    self, var_name: str, type_name: str
) -> Any:  # llvm.AllocaInst
    """
    Create an alloca instruction in the entry block of the function.

    This is used for mutable variables, etc.

    Parameters
    ----------
    fn: The llvm function
    var_name: The variable name
    type_name: The type name

    Returns
    -------
      An llvm allocation instance.
    """
    self._llvm.ir_builder.position_at_start(
        self._llvm.ir_builder.function.entry_basic_block
    )
    alloca = self._llvm.ir_builder.alloca(
        self._llvm.get_data_type(type_name), None, var_name
    )
    self._llvm.ir_builder.position_at_end(self._llvm.ir_builder.block)
    return alloca

get_function

get_function(name: str) -> Optional[Function]

Put the function defined by the given name to result stack.

Source code in src/irx/builders/llvmliteir.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def get_function(self, name: str) -> Optional[ir.Function]:
    """
    Put the function defined by the given name to result stack.

    Parameters
    ----------
        name: Function name
    """
    if name in self._llvm.module.globals:
        return self._llvm.module.get_global(name)

    if name in self.function_protos:
        self.visit(self.function_protos[name])
        return cast(ir.Function, self.result_stack.pop())

    return None

initialize

initialize() -> None

Initialize self.

Source code in src/irx/builders/llvmliteir.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def initialize(self) -> None:
    """Initialize self."""
    # self._llvm.context = ir.context.Context()
    self._llvm = VariablesLLVM()
    self._llvm.module = ir.module.Module("Arx")

    # initialize the target registry etc.
    llvm.initialize()
    llvm.initialize_all_asmprinters()
    llvm.initialize_all_targets()
    llvm.initialize_native_target()
    llvm.initialize_native_asmparser()
    llvm.initialize_native_asmprinter()

    # Create a new builder for the module.
    self._llvm.ir_builder = ir.IRBuilder()

    # Data Types
    self._llvm.FLOAT_TYPE = ir.FloatType()
    self._llvm.DOUBLE_TYPE = ir.DoubleType()
    self._llvm.INT8_TYPE = ir.IntType(8)
    self._llvm.INT32_TYPE = ir.IntType(32)
    self._llvm.VOID_TYPE = ir.VoidType()

translate

translate(expr: AST) -> str

Translate an ASTx expression to string.

Source code in src/irx/builders/llvmliteir.py
105
106
107
108
def translate(self, expr: astx.AST) -> str:
    """Translate an ASTx expression to string."""
    self.visit(expr)
    return str(self._llvm.module)

visit

visit(expr: VariableDeclaration) -> None

Translate ASTx Variable to LLVM-IR.

Source code in src/irx/builders/llvmliteir.py
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
@dispatch  # type: ignore[no-redef]
def visit(self, expr: astx.VariableDeclaration) -> None:
    """Translate ASTx Variable to LLVM-IR."""
    if self.named_values.get(expr.name):
        raise Exception(f"Variable already declared: {expr.name}")

    # Emit the initializer
    if expr.value is not None:
        self.visit(expr.value)
        init_val = self.result_stack.pop()
        if init_val is None:
            raise Exception("Initializer code generation failed.")
    else:
        # If not specified, use 0 as the initializer.
        # note: it should create something according to the defined type
        init_val = ir.Constant(self._llvm.get_data_type("int32"), 0)

    # Create an alloca in the entry block.
    # note: it should create the type according to the defined type
    alloca = self.create_entry_block_alloca(expr.name, "int32")

    # Store the initial value.
    self._llvm.ir_builder.store(init_val, alloca)

    # Remember this binding.
    self.named_values[expr.name] = alloca

VariablesLLVM

Store all the LLVM variables that is used for the code generation.

get_data_type

get_data_type(type_name: str) -> Type

Get the LLVM data type for the given type name.

Returns:

  • ir.Type: The LLVM data type.
Source code in src/irx/builders/llvmliteir.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def get_data_type(self, type_name: str) -> ir.types.Type:
    """
    Get the LLVM data type for the given type name.

    Parameters
    ----------
        type_name (str): The name of the type.

    Returns
    -------
        ir.Type: The LLVM data type.
    """
    if type_name == "float":
        return self.FLOAT_TYPE
    elif type_name == "double":
        return self.DOUBLE_TYPE
    elif type_name == "int8":
        return self.INT8_TYPE
    elif type_name == "int32":
        return self.INT32_TYPE
    elif type_name == "char":
        return self.INT8_TYPE
    elif type_name == "void":
        return self.VOID_TYPE

    raise Exception("[EE]: type_name not valid.")

run_command

run_command(command: list[str]) -> None

Run a command in the operating system.

Source code in src/irx/builders/llvmliteir.py
21
22
23
24
25
26
def run_command(command: list[str]) -> None:
    """Run a command in the operating system."""
    try:
        subprocess.run(command, check=True)
    except subprocess.CalledProcessError as e:
        print(f"An error occurred: {e}")

safe_pop

safe_pop(lst: list[Value | Function]) -> Value | Function

Implement a safe pop operation for lists.

Source code in src/irx/builders/llvmliteir.py
30
31
32
33
34
35
def safe_pop(lst: list[ir.Value | ir.Function]) -> ir.Value | ir.Function:
    """Implement a safe pop operation for lists."""
    try:
        return lst.pop()
    except IndexError:
        return None