Skip to content

llvmliteir

LLVM-IR builder.

Classes:

Functions:

  • safe_pop

    Implement a safe pop operation for lists.

LLVMLiteIR

LLVMLiteIR()

Bases: Builder

LLVM-IR transpiler and compiler.

Methods:

  • build

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

  • module

    Create a new ASTx Module.

  • run

    Run the generated executable.

  • translate

    Transpile ASTx to LLVM-IR.

Source code in src/irx/builders/llvmliteir.py
1646
1647
1648
1649
def __init__(self) -> None:
    """Initialize LLVMIR."""
    super().__init__()
    self.translator: LLVMLiteIRVisitor = LLVMLiteIRVisitor()

build

build(node: 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
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
def build(self, node: astx.AST, output_file: str) -> None:
    """Transpile the ASTx to LLVM-IR and build it to an executable file."""
    self.translator = LLVMLiteIRVisitor()
    result = self.translator.translate(node)

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

    with tempfile.NamedTemporaryFile(suffix="", delete=True) 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

    # fix xh typing
    clang: Callable[..., Any] = xh.clang

    clang(
        file_path_o,
        "-o",
        self.output_file,
    )
    os.chmod(self.output_file, 0o755)

module

module() -> Module

Create a new ASTx Module.

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

run

run() -> str

Run the generated executable.

Source code in src/irx/builders/base.py
91
92
93
def run(self) -> str:
    """Run the generated executable."""
    return run_command([self.output_file])

translate

translate(expr: AST) -> str

Transpile ASTx to LLVM-IR.

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

LLVMLiteIRVisitor

LLVMLiteIRVisitor()

Bases: BuilderVisitor

LLVM-IR Translator.

Methods:

  • create_entry_block_alloca

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

  • get_function

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

  • initialize

    Initialize self.

  • promote_operands

    Promote two LLVM IR numeric operands to a common type.

  • translate

    Translate an ASTx expression to string.

  • visit

    Translate ASTx LiteralInt16 to LLVM-IR.

Source code in src/irx/builders/llvmliteir.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def __init__(self) -> None:
    """Initialize LLVMTranslator object."""
    super().__init__()

    # named_values as instance variable so it isn't shared across instances
    self.named_values: dict[str, Any] = {}
    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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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.FLOAT16_TYPE = ir.HalfType()
    self._llvm.DOUBLE_TYPE = ir.DoubleType()
    self._llvm.BOOLEAN_TYPE = ir.IntType(1)
    self._llvm.INT8_TYPE = ir.IntType(8)
    self._llvm.INT16_TYPE = ir.IntType(16)
    self._llvm.INT32_TYPE = ir.IntType(32)
    self._llvm.INT64_TYPE = ir.IntType(64)
    self._llvm.VOID_TYPE = ir.VoidType()
    self._llvm.STRING_TYPE = ir.LiteralStructType(
        [ir.IntType(32), ir.IntType(8).as_pointer()]
    )
    self._llvm.ASCII_STRING_TYPE = ir.IntType(8).as_pointer()
    self._llvm.UTF8_STRING_TYPE = self._llvm.STRING_TYPE

promote_operands

promote_operands(
    lhs: Value, rhs: Value
) -> tuple[Value, Value]

Promote two LLVM IR numeric operands to a common type.

Parameters:

  • lhs (Value) –

    The left-hand operand.

  • rhs (Value) –

    The right-hand operand.

Returns:

  • tuple[Value, Value]

    A tuple containing the promoted operands.

Source code in src/irx/builders/llvmliteir.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def promote_operands(
    self, lhs: ir.Value, rhs: ir.Value
) -> tuple[ir.Value, ir.Value]:
    """
    Promote two LLVM IR numeric operands to a common type.

    Parameters
    ----------
    lhs : ir.Value
        The left-hand operand.
    rhs : ir.Value
        The right-hand operand.

    Returns
    -------
    tuple[ir.Value, ir.Value]
        A tuple containing the promoted operands.
    """
    if lhs.type == rhs.type:
        return lhs, rhs

    # perform sign extension (for integer operands)
    if isinstance(lhs.type, ir.IntType) and isinstance(
        rhs.type, ir.IntType
    ):
        if lhs.type.width < rhs.type.width:
            lhs = self._llvm.ir_builder.sext(lhs, rhs.type, "promote_lhs")
        elif lhs.type.width > rhs.type.width:
            rhs = self._llvm.ir_builder.sext(rhs, lhs.type, "promote_rhs")
        return lhs, rhs

    # ranking dictionary for floating point types
    fp_types_order = {"float": 1, "double": 2}

    lhs_str = str(lhs.type)
    rhs_str = str(rhs.type)

    # perform floating-point extension
    if lhs_str in fp_types_order and rhs_str in fp_types_order:
        if fp_types_order[lhs_str] < fp_types_order[rhs_str]:
            lhs = self._llvm.ir_builder.fpext(lhs, rhs.type, "promote_lhs")
        elif fp_types_order[lhs_str] > fp_types_order[rhs_str]:
            rhs = self._llvm.ir_builder.fpext(rhs, lhs.type, "promote_rhs")
        return lhs, rhs

    return lhs, rhs

translate

translate(node: AST) -> str

Translate an ASTx expression to string.

Source code in src/irx/builders/llvmliteir.py
126
127
128
129
def translate(self, node: astx.AST) -> str:
    """Translate an ASTx expression to string."""
    self.visit(node)
    return str(self._llvm.module)

visit

visit(node: LiteralInt16) -> None

Translate ASTx LiteralInt16 to LLVM-IR.

Source code in src/irx/builders/llvmliteir.py
1635
1636
1637
1638
1639
@dispatch  # type: ignore[no-redef]
def visit(self, node: astx.LiteralInt16) -> None:
    """Translate ASTx LiteralInt16 to LLVM-IR."""
    result = ir.Constant(self._llvm.INT16_TYPE, node.value)
    self.result_stack.append(result)

VariablesLLVM

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

Methods:

  • get_data_type

    Get the LLVM data type for the given type name.

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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 == "float32":
        return self.FLOAT_TYPE
    elif type_name == "float16":
        return self.FLOAT16_TYPE
    elif type_name == "double":
        return self.DOUBLE_TYPE
    elif type_name == "boolean":
        return self.BOOLEAN_TYPE
    elif type_name == "int8":
        return self.INT8_TYPE
    elif type_name == "int16":
        return self.INT16_TYPE
    elif type_name == "int32":
        return self.INT32_TYPE
    elif type_name == "int64":
        return self.INT64_TYPE
    elif type_name == "char":
        return self.INT8_TYPE
    elif type_name == "string":
        return self.STRING_TYPE
    elif type_name == "stringascii":
        return self.ASCII_STRING_TYPE
    elif type_name == "utf8string":
        return self.UTF8_STRING_TYPE
    elif type_name == "nonetype":
        return self.VOID_TYPE

    raise Exception(f"[EE]: Type name {type_name} not valid.")

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
23
24
25
26
27
28
29
@typechecked
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