Skip to content

llvmliteir

LLVM-IR builder.

Classes:

Functions:

  • emit_int_div

    Emit signed or unsigned vector integer division.

  • is_fp_type

    Return True if t is any floating-point LLVM type.

  • is_vector

    Return True if v is an LLVM vector value.

  • safe_pop

    Implement a safe pop operation for lists.

  • splat_scalar

    Broadcast a scalar to all lanes of a vector.

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
2294
2295
2296
2297
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
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
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.

  • fp_rank

    Rank floating-point types: half < float < double.

  • 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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
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

fp_rank

fp_rank(t: Type) -> int

Rank floating-point types: half < float < double.

Source code in src/irx/builders/llvmliteir.py
318
319
320
321
322
323
324
325
326
def fp_rank(self, t: ir.Type) -> int:
    """Rank floating-point types: half < float < double."""
    if isinstance(t, ir.HalfType):
        return 1
    if isinstance(t, ir.FloatType):
        return 2
    if isinstance(t, ir.DoubleType):
        return 3
    return 0

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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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
235
236
237
238
239
240
241
242
243
244
def initialize(self) -> None:
    """Initialize self."""
    self._llvm = VariablesLLVM()
    self._llvm.module = ir.module.Module("Arx")
    # Initialize native-sized types (size_t, pointer width)
    self._init_native_size_types()

    # 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
    # Composite types
    self._llvm.TIMESTAMP_TYPE = ir.LiteralStructType(
        [
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
        ]
    )
    self._llvm.DATETIME_TYPE = ir.LiteralStructType(
        [
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
            self._llvm.INT32_TYPE,
        ]
    )
    # Platform-sized unsigned integer (assume 64-bit for CI targets)
    self._llvm.SIZE_T_TYPE = ir.IntType(64)

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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
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

    lhs_fp_rank = self.fp_rank(lhs.type)
    rhs_fp_rank = self.fp_rank(rhs.type)

    if lhs_fp_rank > 0 and rhs_fp_rank > 0:
        # make both the wider FP
        if lhs_fp_rank < rhs_fp_rank:
            lhs = self._llvm.ir_builder.fpext(lhs, rhs.type, "promote_lhs")
        elif lhs_fp_rank > rhs_fp_rank:
            rhs = self._llvm.ir_builder.fpext(rhs, lhs.type, "promote_rhs")
        return lhs, rhs

    # If one is int and other is FP, convert int -> FP (sitofp),
    if isinstance(lhs.type, ir.IntType) and rhs_fp_rank > 0:
        target_fp = rhs.type
        lhs_fp = self._llvm.ir_builder.sitofp(lhs, target_fp, "int_to_fp")
        # Now if rhs is narrower/wider, adjust (rhs already target_fp here)
        return lhs_fp, rhs

    if isinstance(rhs.type, ir.IntType) and lhs_fp_rank > 0:
        target_fp = lhs.type
        rhs_fp = self._llvm.ir_builder.sitofp(rhs, target_fp, "int_to_fp")
        return lhs, rhs_fp

    return lhs, rhs

translate

translate(node: AST) -> str

Translate an ASTx expression to string.

Source code in src/irx/builders/llvmliteir.py
178
179
180
181
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
2283
2284
2285
2286
2287
@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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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.")

emit_int_div

emit_int_div(
    ir_builder: "ir.IRBuilder",
    lhs: "ir.Value",
    rhs: "ir.Value",
    unsigned: bool,
) -> "ir.Instruction"

Emit signed or unsigned vector integer division.

Source code in src/irx/builders/llvmliteir.py
45
46
47
48
49
50
51
52
53
54
55
56
def emit_int_div(
    ir_builder: "ir.IRBuilder",
    lhs: "ir.Value",
    rhs: "ir.Value",
    unsigned: bool,
) -> "ir.Instruction":
    """Emit signed or unsigned vector integer division."""
    return (
        ir_builder.udiv(lhs, rhs, name="vdivtmp")
        if unsigned
        else ir_builder.sdiv(lhs, rhs, name="vdivtmp")
    )

is_fp_type

is_fp_type(t: 'ir.Type') -> bool

Return True if t is any floating-point LLVM type.

Source code in src/irx/builders/llvmliteir.py
32
33
34
35
36
37
def is_fp_type(t: "ir.Type") -> bool:
    """Return True if t is any floating-point LLVM type."""
    fp_types = [HalfType, FloatType, DoubleType]
    if FP128Type is not None:
        fp_types.append(FP128Type)
    return isinstance(t, tuple(fp_types))

is_vector

is_vector(v: 'ir.Value') -> bool

Return True if v is an LLVM vector value.

Source code in src/irx/builders/llvmliteir.py
40
41
42
def is_vector(v: "ir.Value") -> bool:
    """Return True if v is an LLVM vector value."""
    return isinstance(getattr(v, "type", None), VectorType)

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
71
72
73
74
75
76
77
@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

splat_scalar

splat_scalar(
    ir_builder: "ir.IRBuilder",
    scalar: "ir.Value",
    vec_type: "ir.VectorType",
) -> "ir.Value"

Broadcast a scalar to all lanes of a vector.

Source code in src/irx/builders/llvmliteir.py
59
60
61
62
63
64
65
66
67
68
def splat_scalar(
    ir_builder: "ir.IRBuilder", scalar: "ir.Value", vec_type: "ir.VectorType"
) -> "ir.Value":
    """Broadcast a scalar to all lanes of a vector."""
    zero_i32 = ir.Constant(ir.IntType(32), 0)
    undef_vec = ir.Constant(vec_type, ir.Undefined)
    v0 = ir_builder.insert_element(undef_vec, scalar, zero_i32)
    mask_ty = ir.VectorType(ir.IntType(32), vec_type.count)
    mask = ir.Constant(mask_ty, [0] * vec_type.count)
    return ir_builder.shuffle_vector(v0, undef_vec, mask)