llvmlite

repository·main·Indexed 25 days ago

https://github.com/numba/llvmlite

A lightweight LLVM Python binding designed for writing JIT compilers. It provides a subset of the LLVM API, focusing on the IR builder, optimizer, and JIT compiler components. It serves as a dependency for Numba and supports static linking to LLVM to ensure API stability and distribution compatibility.

Tokens
19.8K
Snippets
24
Records
128
Agent score
81%

What's inside llvmlite

  1. Use the llvmlite.binding module to interact with LLVM

    main
    The llvmlite.binding module is the primary interface for interacting with the LLVM library. It provides Python classes that closely mirror the LLVM C++ API. Note that llvmlite does not expose the entire LLVM API; it only mirrors a subset of functionalities that are specifically useful for implementing JIT compilers (such as Numba).
  2. Build LLVM Intermediate Representation with llvmlite.ir

    main

    The llvmlite.ir module provides classes and utilities for constructing the LLVM Intermediate Representation (IR) of native functions.

    Key Mental Model: While the APIs are designed to resemble LLVM's C++ APIs, they do not call into LLVM directly during construction. Instead, they build a pure Python representation of the IR. This allows you to programmatically define function logic, types, and control flow in Python before passing the resulting IR to the LLVM backend for compilation.

    Prerequisites: To use this module effectively, you should be familiar with the concepts defined in the LLVM Language Reference.

  3. Overview of llvmlite architecture

    main

    llvmlite is a lightweight LLVM Python binding designed for writing JIT compilers. It uses a three-layered approach:

    1. C Wrapper: A small C wrapper around the specific parts of the LLVM C++ API required (not including everything exposed by the LLVM C API).
    2. ctypes Wrapper: A Python wrapper around the C API using ctypes.
    3. Pure Python IR Builder: A pure Python implementation of the subset of the LLVM IR builder needed for JIT compilation.

    Key architectural benefits include:

    • Stability: Most of llvmlite uses the LLVM C API, which is stable and requires low maintenance when LLVM versions change.
    • Safety: Materializing an LLVM module calls LLVM's IR parser, which provides better error messages and avoids process aborts/segfaults common with step-by-step C++ API building.
    • Ease of Installation: The binding is a plain DLL accessed via ctypes rather than a Python C-extension, avoiding complex C++ 11 compatibility or compiler requirement issues.
    • Decoupling: The IR builder is pure Python, decoupling it from frequently changing LLVM C++ APIs.
  4. Handle Typed vs Opaque Pointers

    main

    The IR layer supports both Typed Pointers (pointers to a specific type) and Opaque Pointers (pointers without an associated pointee type). Support for Typed Pointers is being phased out in favor of Opaque Pointers.

    Typed Pointers

    Created using PointerType(pointee, addrspace=0), where pointee is the type being pointed to.

    Opaque Pointers

    Created using PointerType(addrspace=0). These do not take a pointee argument.

    Disabling Typed Pointers

    To force the IR layer to print Typed Pointers as Opaque Pointers, you can use one of the following methods:

    Environment Variable:

    LLVMLITE_ENABLE_IR_LAYER_TYPED_POINTERS=0

    Python Attribute: Set llvmlite.ir_layer_typed_pointers_enabled = False immediately after importing llvmlite and before using any other functionality.

    import llvmlite
    llvmlite.ir_layer_typed_pointers_enabled = False
    # ... continue using llvmlite ...
  5. Understand LLVM Intermediate Representation (IR) components

    main

    When building JIT compilers with llvmlite, you work with LLVM Intermediate Representation (IR), which is a high-level assembly-like language. The core building blocks of an IR program include:

    • Module: The top-level compilation unit. A module contains function declarations, function definitions, global variables, and metadata.
    • Function Declaration: A specification of a function's prototype (argument types, return types, calling convention) without an implementation (similar to extern in C).
    • Function Definition: A function prototype combined with a body that implements the function.
    • Global Value: A named value accessible to all members of a module.
    • Global Variable: A specific type of global value that is a constant pointer to a module-allocated slot of a given type.
    • Instruction: The fundamental procedural elements used to implement functions.
    • Metadata: Optional, non-critical information attached to instructions, functions, or other code (e.g., branch likelihood or source code locations).
  6. Understand the llvmlite architecture and philosophy

    main

    Unlike other bindings that expose the LLVM C++ API directly, llvmlite is designed specifically for JIT compilers by decoupling module construction from compilation. This is achieved through two distinct layers:

    1. IR Layer: Responsible for constructing the LLVM Intermediate Representation (IR) in pure Python, building modules function by function and instruction by instruction.
    2. Binding Layer: Responsible for taking the textual IR and feeding it into LLVM's parsing API. This layer returns a thin wrapper around the LLVM C++ module object.

    Key Constraint: Once the IR is passed to the binding layer and parsed, the module's source code cannot be modified. This design choice prioritizes maintenance stability and performance over the direct C++ API mapping found in older libraries like llvmpy.

  7. Understand control flow: Basic blocks, Labels, and Terminators

    main

    Control flow within an LLVM function is managed through the following structures:

    • Basic Block: A sequence of instructions that always starts with a Label and ends with a Terminator. No instruction inside a basic block can transfer control out of the block except for the terminator.
    • Label: A branch target inside a function that denotes the start of a Basic Block.
    • Terminator (Terminator Instruction): An instruction that explicitly transfers control to another part of the program (e.g., branches or function returns) instead of proceeding to the next sequential instruction.
  8. Distinguish between IR Modules and Binding Layer Modules

    main

    In llvmlite, there is a distinction between the IR layer and the binding layer. While they represent similar concepts, they serve different purposes and use different APIs:

    1. IR Layer (llvmlite.ir.Module): Used to build and group functions together (the construction phase).
    2. Binding Layer (llvmlite.binding.ModuleRef): Used to access compilation, linking, and execution of code (the execution/backend phase).

    To transition from the IR layer to the binding layer, you must parse the IR into a ModuleRef using factory functions like parse_assembly.

  9. Coding conventions for llvmlite

    main

    When contributing code, adhere to the following standards:

    • Python: All Python code must follow PEP 8.
    • C++: All C++ code must be formatted using clang-format-13. This package is available in the conda-forge channel.
    • Style: Code and documentation should generally be constrained to 80 columns to ensure readability in code review tools.