Merge pull request #9 from LLVMParty/api-ux

Add JIT/optimize/intrinsic/object support
This commit is contained in:
Duncan Ogilvie
2026-05-14 01:11:26 +02:00
committed by GitHub
53 changed files with 4662 additions and 3652 deletions
+13 -3
View File
@@ -57,6 +57,14 @@ uv run python examples/transform_replace_add.py
More examples worth browsing:
- [`examples/intrinsic_memcpy.py`](examples/intrinsic_memcpy.py) - call an LLVM intrinsic by name with `Builder.intrinsic(...)`
- [`examples/optimize_module.py`](examples/optimize_module.py) - optimize a module with a PassBuilder pipeline string
- [`examples/optimize_function.py`](examples/optimize_function.py) - optimize one function with a function-level PassBuilder pipeline string
- [`examples/emit_object_assembly.py`](examples/emit_object_assembly.py) - emit host object code and assembly from a module
- [`examples/jit_add.py`](examples/jit_add.py) - JIT-compile IR, call it through ctypes, and register a Python callback
- [`examples/instruction_metadata.py`](examples/instruction_metadata.py) - attach custom metadata to an instruction and print the IR
- [`examples/named_metadata.py`](examples/named_metadata.py) - create module named metadata and print the IR
- [`examples/metadata_debug_info.py`](examples/metadata_debug_info.py) - attach metadata, create debug info, and use debug-location scopes
- [`examples/transform_replace_add.py`](examples/transform_replace_add.py) - simple IR transformation using operands, RAUW, and instruction deletion
- [`examples/bc-stats.py`](examples/bc-stats.py) - print per-function instruction histograms from LLVM IR
- [`examples/bc-graphviz.py`](examples/bc-graphviz.py) - emit a GraphViz-style control-flow graph from LLVM IR
@@ -66,8 +74,10 @@ More examples worth browsing:
- Pythonic wrappers for contexts, modules, types, values, functions, basic blocks, builders, metadata, debug info, object files, targets, target machines, and pass builder options
- IR construction, traversal, and transformation helpers: constants, globals, PHI/control flow/memory/cast/cmp instructions, operands, predecessors, RAUW, split blocks, move/clone/erase instructions
- IR and bitcode parsing/writing, lazy bitcode modules, module cloning/linking, module flags, diagnostics, attributes, COMDATs, calling conventions, linkage/visibility/storage controls
- Target initialization/lookup, data layouts, object/assembly emission through target machines, and PassBuilder pipeline execution
- IR and bitcode parsing/writing, lazy bitcode modules, module cloning/linking, diagnostics, attributes, COMDATs, calling conventions, linkage/visibility/storage controls
- Metadata/debug-info APIs including named metadata views, module flag views, instruction/global metadata mappings, DIBuilder recipes, and debug-location scopes
- Target lookup, data layouts, `Module.emit_object()`, `Module.emit_assembly()`, target-machine emission, and PassBuilder pipeline execution via `Module.optimize()`, `Function.optimize()`, and `Module.run_passes()`
- Generic intrinsic calls with `Builder.intrinsic(...)` and in-process JIT execution through the LLVM-C ORC LLJIT API
- Lifetime/validity guards that turn many use-after-free, disposed-object, null-reference, and wrong-kind mistakes into Python exceptions instead of hard crashes
- Auto-generated typed `.pyi` stubs for IDEs/type checkers
- Golden-master tests, Python regression scripts, examples, and vendored `llvm-c-test` lit tests, including CI coverage against installed wheels
@@ -84,7 +94,7 @@ For development documentation, see `devdocs/README.md`.
## Known limitations
- The API is not stable yet; method names and ownership rules may still change.
- Scope follows the LLVM-C API, not the full LLVM C++ API. A high-level JIT/ExecutionEngine API is not currently exposed.
- Scope follows the LLVM-C API, not the full LLVM C++ API. JIT support is intentionally limited to what is exposed cleanly through LLVM-C ORC LLJIT.
- Docs are currently the README, examples, `devdocs/`, and the generated `.pyi` stub; there is no hosted API reference yet.
- Prebuilt wheels currently target CPython 3.12+ stable ABI on Linux x86_64/aarch64, macOS arm64/x86_64, and Windows x86_64. Other platforms require a source build.
+17 -5
View File
@@ -12,8 +12,8 @@ The earliest API mirrored the LLVM C API directly. While this made initial imple
# Current: Pythonic object-oriented API
i32 = ctx.types.i32
const = i32.constant(42)
func.attributes.add(attr)
inst.set_metadata(kind, md)
func.attributes.add("noreturn")
inst.metadata["llvm.loop"] = loop_md
```
---
@@ -94,8 +94,10 @@ Python developers expect `object.operation()`, not `module.operation(object)`.
**Acceptable globals**:
- Ownerless factories: `create_context()`
- Initialization: `initialize_all_targets()`
- Registry lookups: `get_md_kind_id()` (no object owns the metadata registry)
- Target and intrinsic registry lookups that do not mutate IR
LLVM target/disassembler registries are initialized when the module is imported;
public initialization functions add no user-facing value.
Factories with a natural owner should be static methods instead (for example,
`BinaryManager.from_file()` rather than a module-level binary factory).
@@ -124,7 +126,17 @@ This catches errors with clear messages while keeping the API simple.
---
### 8. Method Chaining Potential
### 8. C API Anchors in Binding Docs
**Principle**: Every public binding method or property should document the LLVM-C API it uses with a `<sub>C API: ...</sub>` line.
**Why**: The C API anchor keeps the binding grounded in LLVM's actual surface, makes generated stubs searchable, and lets future maintainers or agents verify behavior against LLVM headers.
If a method is a pure convenience wrapper, list the primary LLVM-C calls it composes. If LLVM-C lacks a required operation, document that as `<sub>C API limitation</sub>`.
---
### 9. Method Chaining Potential
**Principle**: Methods returning values enable fluent APIs.
+63 -2
View File
@@ -38,6 +38,55 @@ rg "replace_all_uses_with|erase_from_parent|split_basic_block|const_string" .ven
- `devdocs/lit-tests.md`
- `devdocs/DEBUGGING.md`
## High-Level UX Helpers
These APIs wrap common workflows that previously required LLVM-C-style boilerplate:
```python
# Intrinsics by name.
builder.intrinsic("llvm.sqrt", [x], overloaded_types=[x.type])
# Explicit PassBuilder pipeline optimization.
mod.optimize("default<O2>", target_machine=tm)
func.optimize("mem2reg,instcombine,simplifycfg", target_machine=tm)
# Direct object/assembly emission. Optimize explicitly first when desired.
obj = mod.emit_object(target_machine=tm)
asm = mod.emit_assembly(target_machine=tm)
# Host target machine convenience.
tm = llvm.TargetMachine.host()
# LLVM-C ORC LLJIT.
with llvm.JIT.host() as jit:
jit.add_module(mod) # invalidates mod on success
addr = jit.lookup("compiled_function")
# Metadata by name, without public kind IDs.
text = ctx.md_string("frontend")
assert text.is_string
assert text.string == "frontend"
node = ctx.md_node([text])
for operand in node.operands:
assert operand.string == "frontend"
inst.metadata["llvm.loop"] = loop_md
md = inst.metadata.get("llvm.loop")
src_inst.metadata.copy_to(dst_inst)
del inst.metadata["llvm.loop"]
# Named metadata and module flags.
mod.named_metadata["llvm.dbg.cu"].append(compile_unit)
mod.module_flags.add("Debug Info Version", llvm.ModuleFlagBehavior.Warning, md)
# Debug-location scopes.
with builder.debug_location(line=12, column=4, scope=subprogram):
inst = builder.add(a, b, "sum")
```
`builder.intrinsic(..., overloaded_types=[...])` uses LLVM's intrinsic
overload-disambiguation type list. This list selects the intrinsic declaration;
it is not the same as the call operand list.
## Value API Validity Matrix
This section documents when `llvm.Value` accessors are valid to call. Invalid
@@ -68,7 +117,7 @@ calls should raise `llvm.LLVMAssertionError` (not crash).
- Global value:
- `global_value_type`, `unnamed_address`
- `linkage`, `visibility`, `dll_storage_class`
- `global_copy_all_metadata`
- `metadata` mapping view; use `value.metadata.copy_to(other)` for bulk copies
- Global object:
- `comdat`, `set_comdat`
- `section`
@@ -90,7 +139,7 @@ calls should raise `llvm.LLVMAssertionError` (not crash).
- Any instruction:
- `opcode`, `opcode_name`
- `instruction_get_all_metadata_other_than_debug_loc`
- `metadata` mapping view; use `inst.metadata.copy_to(other)` for bulk copies
- `next_instruction`, `prev_instruction`
- `remove_from_parent`, `erase_from_parent`, `delete_instruction`,
`instruction_clone`
@@ -216,6 +265,12 @@ This section captures guard preconditions for wrapper classes other than
- `set_body` requires identified opaque struct type (not literal, not already
non-opaque).
### Type factory aliases
- `types` is available on `Context`, `Module`, `Function`, `BasicBlock`, and `Value`.
- All aliases for objects in the same context compare equal:
`ctx.types == mod.types == fn.types == bb.types == value.types`.
### BasicBlock (`llvm.BasicBlock`)
- `terminator` requires the block to have a terminator.
@@ -245,6 +300,9 @@ This section captures guard preconditions for wrapper classes other than
- `return_attributes` for return-value attributes.
- `param_attributes(i)` for parameter attributes, using a 0-based Python index.
- Use `llvm.Attribute.enum/type/string(...)` or `slot.add("noreturn")`.
- Use `llvm.Attribute.memory(ctx, "none")` or `slot.add_memory("read")` for `memory(...)` effects without raw encoded integers.
- `create_builder()` positions in the function entry block and creates an
`entry` block when the function has no blocks yet.
- `block_address` requires block ownership by that function.
Prefer `bb.block_address()` when the function can be inferred.
- Parent navigation:
@@ -258,6 +316,9 @@ This section captures guard preconditions for wrapper classes other than
- Positioning:
- `position_before(inst)` requires an instruction attached to a block.
- `position_at(bb, inst)` requires `inst` to be an instruction in `bb`.
- Memory allocation:
- `alloca(ty, name="")` creates a scalar alloca.
- `alloca(ty, count, name="")` creates an array alloca.
- Instruction-only insertion helpers:
- `insert_into_builder_with_name(instr)` requires instruction value.
- `add_metadata_to_inst(instr)` requires instruction value.
+327
View File
@@ -0,0 +1,327 @@
# API UX Cleanup Plan
## Goal
Make the remaining high-value workflows clean from a Python user's perspective while keeping useful LLVM controls available. This plan covers five areas:
1. generic intrinsic calls,
2. explicit module optimization by pass pipeline,
3. object/assembly emission convenience,
4. JIT through LLVM-C,
5. metadata/debug-info cleanup.
The API should let users write the operation they mean without requiring LLVM-C boilerplate.
## Design rules
- Keep useful LLVM concepts public.
- Hide accidental LLVM-C scaffolding from common workflows.
- Prefer object methods over global helper sequences.
- Prefer names over registry IDs when the user's input is naturally a name.
- Prefer explicit ownership rules over implicit invalidation.
- Raise Python exceptions before LLVM can crash or leave IR half-mutated.
## Phase 1: Generic intrinsic calls
### User problem
Calling an intrinsic currently requires a multi-step sequence: look up an intrinsic ID, get a declaration from a module, construct or know the function type, then call the declaration. That is too much ceremony for a user who already knows the intrinsic name and call operands.
### Target UX
```python
builder.intrinsic(
"llvm.memcpy",
[dst, src, n, is_volatile],
overloaded_types=[dst.type, src.type, n.type],
)
builder.intrinsic("llvm.sqrt", [x], overloaded_types=[x.type])
builder.intrinsic("llvm.trap", [])
```
### What `overloaded_types` means
`overloaded_types` is LLVM's overload-disambiguation type list. It is not the same as the call argument list.
LLVM has intrinsics whose declarations depend on one or more types. LLVM-C requires those types when retrieving the intrinsic declaration. Examples:
- `llvm.sqrt` is overloaded by floating-point or vector type.
- overflow intrinsics are overloaded by integer type.
- memory intrinsics are overloaded by pointer address spaces and length type.
The helper should pass `overloaded_types` to the existing intrinsic declaration machinery internally.
### Public API shape
```python
builder.intrinsic(
name: str,
args: Iterable[llvm.Value],
*,
overloaded_types: Iterable[llvm.Type] = (),
name_hint: str = "",
) -> llvm.Value
```
### Behavior
- Look up the intrinsic by name internally.
- Raise `LLVMAssertionError` if the name is unknown.
- Raise a clear error if the intrinsic is overloaded and `overloaded_types` is empty.
- Get the intrinsic declaration in the current module.
- Build and return the call instruction.
- Keep existing lower-level intrinsic APIs public for advanced users.
### Tests
- Non-overloaded intrinsic call.
- Overloaded floating-point intrinsic call.
- Memory intrinsic call.
- Unknown intrinsic name error.
- Missing overload types error.
- Generated module verifies.
## Phase 2: Explicit module/function optimization helpers
### User problem
Running passes is possible, but the current method name is low-level and does not read like a user task. Users should be able to say “optimize this module” or “optimize this function” with a PassBuilder pipeline directly.
### Target UX
```python
mod.optimize("default<O2>")
mod.optimize("default<Os>", target_machine=tm)
mod.optimize("function(mem2reg),default<O2>", target_machine=tm, options=opts)
func.optimize("mem2reg,instcombine,simplifycfg")
func.optimize("instcombine,simplifycfg", target_machine=tm, options=opts)
```
### Public API shape
```python
mod.optimize(
pipeline: str,
*,
target_machine: llvm.TargetMachine | None = None,
options: llvm.PassBuilderOptions | None = None,
) -> None
func.optimize(
pipeline: str,
*,
target_machine: llvm.TargetMachine | None = None,
options: llvm.PassBuilderOptions | None = None,
) -> None
```
### Behavior
- `Module.optimize` accepts module-level LLVM PassBuilder pipeline strings.
- `Function.optimize` accepts function-level LLVM PassBuilder pipeline strings.
- The method mutates the module or function in place.
- The method wraps the existing pass-running implementation.
- Existing lower-level pass APIs remain available for advanced users.
- Error messages should include the failed pipeline string when LLVM rejects it.
### Tests
- `default<O0>` succeeds on a simple module.
- `default<O2>` succeeds on a simple module.
- A custom function pipeline succeeds on one function without changing sibling functions.
- Invalid pipelines raise Python exceptions with the pipeline in the message.
- Passing a target machine works.
## Phase 3: Object and assembly emission convenience
### User problem
Emitting object code or assembly is possible, but the common path requires too much setup. Users should be able to emit from a module directly. Optimization should remain a separate explicit step.
### Target UX
```python
mod.optimize("default<O2>", target_machine=tm)
obj = mod.emit_object(target_machine=tm)
asm = mod.emit_assembly(target_machine=tm)
```
For the host target:
```python
tm = llvm.TargetMachine.host()
obj = mod.emit_object(target_machine=tm)
```
### Public API shape
```python
llvm.TargetMachine.host(
cpu: str = "",
features: str = "",
opt_level: llvm.CodeGenOptLevel = llvm.CodeGenOptLevel.Default,
reloc_mode: llvm.RelocMode = llvm.RelocMode.Default,
code_model: llvm.CodeModel = llvm.CodeModel.Default,
) -> llvm.TargetMachine
mod.emit_object(*, target_machine: llvm.TargetMachine | None = None) -> bytes
mod.emit_assembly(*, target_machine: llvm.TargetMachine | None = None) -> bytes
```
### Behavior
- If `target_machine` is provided, use it.
- If `target_machine` is omitted, create a host target machine internally.
- Do not run optimization passes inside emission methods.
- Return bytes, matching the existing memory-buffer emission API.
- Keep `TargetMachine.emit_to_memory_buffer` public for advanced users.
### Tests
- Emit object for a simple function.
- Emit assembly for a simple function.
- Emitted object can be opened by `BinaryManager`.
- Explicit `target_machine=` path works.
- Omitted `target_machine` path works when host target support is available.
- Missing target support raises a clear Python exception or skips in tests where appropriate.
## Phase 4: JIT through LLVM-C
### User problem
In-process JIT execution is not available. This blocks DSL/compiler workflows that need to compile IR and call generated code from Python.
### Constraint
Use LLVM-C APIs only. The implementation must not bind LLVM C++ ORC or ExecutionEngine classes directly.
The first implementation step is to inspect the LLVM-C headers available in this project build and choose the best supported C API:
- ORC/LLJIT C API if available and stable enough,
- otherwise MCJIT/ExecutionEngine C API if available.
### Target UX
```python
with llvm.JIT.host() as jit:
jit.add_module(mod)
addr = jit.lookup("add_i32")
```
ctypes convenience:
```python
with llvm.JIT.host() as jit:
jit.add_module(mod)
add_i32 = jit.ctypes_function(
"add_i32",
restype=ctypes.c_int32,
argtypes=[ctypes.c_int32, ctypes.c_int32],
)
assert add_i32(2, 3) == 5
```
Callback support if the chosen LLVM-C API exposes symbol registration cleanly:
```python
callback = ctypes.CFUNCTYPE(ctypes.c_int32, ctypes.c_int32)(py_func)
jit.add_symbol("py_func", callback)
```
### Ownership rules
- `jit.add_module(mod)` transfers module ownership to the JIT.
- The Python `Module` wrapper becomes invalid after transfer.
- `jit.lookup(name)` returns an integer address.
- `jit.ctypes_function(...)` wraps `lookup` and keeps required owner references alive.
- ctypes callback objects registered with the JIT must be pinned for at least as long as the JIT can call them.
### Tests
- JIT a pure integer function and call it through ctypes.
- Lookup of a missing symbol raises a clean exception.
- Module wrapper use after transfer raises a memory/lifetime error.
- Callback test if symbol registration is implemented.
- Unsupported platforms skip cleanly.
## Phase 5: Metadata/debug-info cleanup
### User problem
Common metadata operations should not require numeric metadata kind IDs or raw LLVM-C named-metadata handles.
### Target UX
```python
inst.metadata["llvm.loop"] = loop_md
md = inst.metadata.get("llvm.loop")
del inst.metadata["llvm.loop"]
mod.named_metadata["llvm.dbg.cu"].append(compile_unit)
for md in mod.named_metadata["llvm.dbg.cu"]:
...
mod.module_flags.add("Debug Info Version", llvm.ModuleFlagBehavior.Warning, value_md)
loc = ctx.debug_location(line=12, column=4, scope=subprogram)
with builder.debug_location(loc):
value = builder.add(a, b, "sum")
```
### Behavior
- Hide metadata kind IDs from normal use.
- Provide a bulk metadata copy path for transforms without exposing kind IDs.
- Expose named metadata as a mapping from name to appendable list.
- Expose module flags as a keyed view.
- Keep advanced DIBuilder creation methods that model useful debug-info concepts.
- Remove redundant raw APIs where the new views have parity.
### Tests
- Metadata set/get/delete by name.
- Detached instruction metadata.
- Metadata bulk copy.
- Named metadata append, iteration, key iteration.
- Module flags add/get/key iteration.
- Debug-location context manager.
- DIBuilder recipes for file, compile unit, function, and local variable.
- Public surface is reviewed during API changes.
## API audit process
After each phase:
1. Rebuild to regenerate `.pyi`.
2. Review new public symbols.
3. Confirm examples express user tasks directly.
4. Keep advanced lower-level APIs when they correspond to useful LLVM controls.
## Suggested implementation order
1. Add `Builder.intrinsic(...)`.
2. Add `Module.optimize(...)`.
3. Add `TargetMachine.host()`.
4. Add `Module.emit_object(...)` and `Module.emit_assembly(...)`.
5. Design and implement `llvm.JIT` using LLVM-C.
6. Add metadata/debug-info mapping views and DIBuilder recipes.
## Acceptance criteria
- Intrinsic examples use `builder.intrinsic(...)` for generic intrinsic calls.
- Optimization examples use `mod.optimize(<pass pipeline>)`.
- Object/assembly examples use `mod.emit_object()` / `mod.emit_assembly()`.
- JIT design and implementation use LLVM-C only.
- Metadata examples avoid raw metadata kind IDs.
- Low-level APIs remain available when they are useful advanced controls.
- Tests pass:
```bash
cmake --build build
uv run run_tests.py
uv run run_tests.py --regressions
uv run run_llvm_c_tests.py --use-python
uvx ty check
```
+124
View File
@@ -0,0 +1,124 @@
# API UX Cleanup Progress
## Status
Implemented and tested the active UX work:
1. generic intrinsic helper,
2. explicit module optimization helper,
3. object/assembly emission convenience,
4. JIT through LLVM-C ORC LLJIT,
5. metadata/debug-info cleanup.
## Implemented APIs
### Phase 1: Generic intrinsic helper
- [x] Added `Builder.intrinsic(name, args, *, overloaded_types=..., name_hint="")`.
- [x] Added no-overload form `Builder.intrinsic(name, args, *, name_hint="")`.
- [x] Internally looks up the intrinsic ID by name.
- [x] Uses `overloaded_types` as LLVM's overload-disambiguation type list.
- [x] Raises a clear error for unknown intrinsic names.
- [x] Raises a clear error when an overloaded intrinsic needs `overloaded_types`.
- [x] Keeps existing lower-level intrinsic APIs public.
- [x] Tests cover non-overloaded, overloaded, and memory intrinsics.
### Phase 2: Module/function optimization helpers
- [x] Added `Module.optimize(pipeline, *, target_machine=None, options=None)`.
- [x] Added `Function.optimize(pipeline, *, target_machine=None, options=None)` for function-level PassBuilder pipelines.
- [x] Uses LLVM PassBuilder pipeline strings directly.
- [x] Mutates the module or function in place.
- [x] Includes the failed pipeline string in error messages.
- [x] Keeps existing lower-level pass APIs public.
- [x] Fixed optional pass options by creating a default `PassBuilderOptions` internally when none is provided.
- [x] Tests cover success and failure paths.
### Phase 3: Object/assembly emission convenience
- [x] Added `TargetMachine.host(...)`.
- [x] Added `Module.emit_object(*, target_machine=None)`.
- [x] Added `Module.emit_assembly(*, target_machine=None)`.
- [x] If `target_machine` is omitted, creates a host target machine internally.
- [x] Emission methods do not run optimization passes.
- [x] Keeps `TargetMachine.emit_to_memory_buffer` public.
- [x] Tests cover object, assembly, host target, explicit target machine, and BinaryManager parsing.
### Phase 4: JIT through LLVM-C
- [x] Inspected available LLVM-C headers and chose ORC LLJIT C API.
- [x] Added `llvm.JIT.host()` context manager.
- [x] Implemented module ownership transfer for `jit.add_module(mod)`.
- [x] `jit.add_module(mod)` invalidates the Python `Module` wrapper on success.
- [x] Added `jit.lookup(name) -> int`.
- [x] Added `jit.ctypes_function(...)` callable wrapper.
- [x] `jit.ctypes_function(...)` keeps the JIT object alive while the returned callable wrapper is alive.
- [x] Added `jit.add_symbol(...)` for integer addresses and ctypes callbacks.
- [x] ctypes callback objects are pinned while the JIT is alive and released on dispose.
- [x] Unsupported host target/JIT setup raises `LLVMError`; tests skip target-dependent checks cleanly when unavailable.
### Phase 5: Metadata/debug-info cleanup
- [x] Added `Value.metadata` mapping view by metadata kind name.
- [x] Added `Metadata.kind`, `is_string`, `is_node`, `is_value`, `string`, `operands`, and `value` accessors.
- [x] Stubbed `Metadata.value` with `NotImplementedError` because LLVM-C cannot unwrap ValueAsMetadata to Value.
- [x] Added `MetadataMap.copy_to(target, include_debug_location=False)` so transforms can copy arbitrary attached metadata without exposing kind IDs.
- [x] Added support for detached instruction metadata by deriving the context from the value type.
- [x] Added `Module.named_metadata` mapping/list view.
- [x] Added `NamedMetadataMap.keys()` and iteration.
- [x] Added `Module.module_flags` view.
- [x] Added `Context.debug_location(...)`.
- [x] Added `Builder.debug_location(...)` context manager.
- [x] Added DIBuilder recipes: `file`, `compile_unit`, `function`, `local_variable`.
- [x] Removed redundant public low-level metadata APIs: raw metadata kind lookup, raw `Value.set_metadata`, public `ValueMetadataEntries`, public `NamedMDNode`, `Metadata.as_value`, raw named-metadata methods, and raw module-flag methods.
- [x] Removed redundant DIBuilder aliases covered by recipes: `create_file`, `create_compile_unit`.
- [x] Kept advanced DIBuilder `create_*` methods where the recipes do not provide full coverage.
## Tests added
- `tests/regressions/test_api_ux_cleanup.py`
- `tests/regressions/test_metadata_ux_cleanup.py`
Coverage:
- [x] non-overloaded intrinsic call,
- [x] overloaded floating-point intrinsic call,
- [x] memory intrinsic call,
- [x] unknown intrinsic error,
- [x] missing overload types error,
- [x] module optimization success and invalid-pipeline failure,
- [x] optimization with target machine,
- [x] object and assembly emission,
- [x] emitted object opens with `BinaryManager`,
- [x] JIT integer function lookup and ctypes call,
- [x] module invalidation after JIT transfer,
- [x] missing JIT symbol error,
- [x] JIT ctypes callable keeps the JIT alive,
- [x] JIT callback symbol registration and callback lifetime pinning.
## Documentation and examples updated
- [x] `README.md` current capabilities, known limitations, and example links.
- [x] `devdocs/api-reference.md` high-level UX helper examples.
- [x] `devdocs/api-ux-cleanup/plan.md` remains the task design reference.
- [x] `examples/intrinsic_memcpy.py` shows `Builder.intrinsic(...)`.
- [x] `examples/optimize_module.py` shows `Module.optimize(...)`.
- [x] `examples/optimize_function.py` shows `Function.optimize(...)`.
- [x] `examples/emit_object_assembly.py` shows `TargetMachine.host()`, `emit_object()`, and `emit_assembly()`.
- [x] `examples/jit_add.py` shows `JIT.host()`, `add_module()`, `lookup` via `ctypes_function()`, and `add_symbol()`.
## Validation performed
```bash
cmake --build build
uv run tests/regressions/test_api_ux_cleanup.py
uv run pytest tests/regressions/test_api_ux_cleanup.py -q
uv run tests/regressions/test_metadata_ux_cleanup.py
uv run pytest tests/regressions/test_metadata_ux_cleanup.py tests/test_examples.py -q
uv run run_tests.py --regressions
uv run run_tests.py
uv run run_llvm_c_tests.py --use-python
uvx ty check
```
All commands passed.
+61
View File
@@ -0,0 +1,61 @@
"""Emit object code and assembly from a module.
The module is optimized explicitly before emission. ``emit_object`` and
``emit_assembly`` perform code generation only.
Run from the repository root with:
uv run python examples/emit_object_assembly.py
"""
from __future__ import annotations
import llvm
def emit_for_host() -> tuple[str, int, str, list[str]]:
tm = llvm.TargetMachine.host()
with llvm.create_context() as ctx:
i32 = ctx.types.i32
with ctx.create_module("emit_example") as mod:
mod.target_triple = tm.triple
mod.data_layout = str(tm.create_data_layout())
fn = mod.add_function("get_answer", ctx.types.function(i32, []))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.ret(i32.constant(42))
assert mod.verify(), mod.verification_error
mod.optimize("default<O2>", target_machine=tm)
obj = mod.emit_object(target_machine=tm)
asm = mod.emit_assembly(target_machine=tm)
with llvm.BinaryManager.from_bytes(obj) as binary:
binary_type = binary.type.name
asm_lines = asm.decode("utf-8", errors="replace").splitlines()
preview = [line for line in asm_lines if line.strip()][:6]
return tm.triple, len(obj), binary_type, preview
def main() -> None:
try:
triple, obj_size, binary_type, preview = emit_for_host()
except llvm.LLVMError as exc:
print(f"skipped: host code generation is unavailable: {exc}")
return
print(f"target: {triple}")
print(f"object bytes: {obj_size}")
print(f"binary type: {binary_type}")
print("assembly preview:")
for line in preview:
print(f" {line}")
if __name__ == "__main__":
main()
+57
View File
@@ -0,0 +1,57 @@
"""Attach custom metadata to an instruction and print LLVM IR.
Run from the repository root with:
uv run python examples/instruction_metadata.py
"""
from __future__ import annotations
import llvm
def build_module() -> str:
with llvm.create_context() as ctx:
i32 = ctx.types.i32
fn_type = ctx.types.function(i32, [i32])
with ctx.create_module("instruction_metadata_example") as mod:
fn = mod.add_function("bump", fn_type)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
result = builder.add(fn.get_param(0), i32.constant(1), "result")
# Metadata is addressed by its kind name. No metadata kind ID is
# exposed in the Python API.
note_text = ctx.md_string("created by instruction_metadata.py")
note = ctx.md_node([note_text])
result.metadata["example.note"] = note
# Inspect the attached metadata through the same mapping view.
attached_note = result.metadata["example.note"]
has_note = "example.note" in result.metadata
round_trip_matches = result.metadata.get("example.note") == note
note_operand_count = len(attached_note.operands)
note_text_value = attached_note.operands[0].string
builder.ret(result)
assert mod.verify(), mod.verification_error
return (
"instruction metadata:\n"
f" has example.note: {has_note}\n"
f" round trip matches: {round_trip_matches}\n"
f" note operands: {note_operand_count}\n"
f" note text: {note_text_value}\n"
"\nIR:\n"
f"{mod}"
)
def main() -> None:
print(build_module(), end="")
if __name__ == "__main__":
main()
+52
View File
@@ -0,0 +1,52 @@
"""Build a call to an LLVM intrinsic by name.
This example uses ``Builder.intrinsic`` for ``llvm.memcpy``. The
``overloaded_types`` list selects the intrinsic declaration:
- destination pointer type,
- source pointer type,
- length integer type.
Run from the repository root with:
uv run python examples/intrinsic_memcpy.py
"""
from __future__ import annotations
import llvm
def build_module() -> str:
with llvm.create_context() as ctx:
void = ctx.types.void
ptr = ctx.types.ptr
i64 = ctx.types.i64
i1 = ctx.types.i1
with ctx.create_module("intrinsic_memcpy_example") as mod:
fn_ty = ctx.types.function(void, [ptr, ptr, i64])
fn = mod.add_function("copy_bytes", fn_ty)
dst = fn.get_param(0)
src = fn.get_param(1)
count = fn.get_param(2)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.intrinsic(
"llvm.memcpy",
[dst, src, count, i1.constant(False)],
overloaded_types=[ptr, ptr, i64],
)
builder.ret_void()
assert mod.verify(), mod.verification_error
return str(mod)
def main() -> None:
print(build_module(), end="")
if __name__ == "__main__":
main()
+83
View File
@@ -0,0 +1,83 @@
"""Compile LLVM IR in-process and call it from Python.
This example uses the LLVM-C ORC LLJIT wrapper. ``jit.add_module(mod)``
transfers the module into the JIT and invalidates the Python ``Module`` wrapper
on success.
Run from the repository root with:
uv run python examples/jit_add.py
"""
from __future__ import annotations
import ctypes
from typing import cast
import llvm
CALLBACK_TYPE = ctypes.CFUNCTYPE(ctypes.c_int32, ctypes.c_int32)
@CALLBACK_TYPE
def py_add_ten(value: int) -> int:
return value + 10
def add_functions_to_module(ctx: llvm.Context, mod: llvm.Module) -> None:
i32 = ctx.types.i32
add_fn = mod.add_function("add_i32", ctx.types.function(i32, [i32, i32]))
lhs = add_fn.get_param(0)
rhs = add_fn.get_param(1)
add_entry = add_fn.append_basic_block("entry")
with add_entry.create_builder() as builder:
builder.ret(builder.add(lhs, rhs, "sum"))
callback_decl = mod.add_function("py_add_ten", ctx.types.function(i32, [i32]))
call_python_fn = mod.add_function("call_python", ctx.types.function(i32, [i32]))
arg = call_python_fn.get_param(0)
callback_entry = call_python_fn.append_basic_block("entry")
with callback_entry.create_builder() as builder:
result = builder.call(callback_decl, [arg], "result")
builder.ret(result)
def run_jit() -> tuple[int, int]:
with llvm.JIT.host() as jit:
jit.add_symbol("py_add_ten", py_add_ten)
with llvm.create_context() as ctx:
with ctx.create_module("jit_example") as mod:
add_functions_to_module(ctx, mod)
assert mod.verify(), mod.verification_error
jit.add_module(mod)
add_i32 = jit.ctypes_function(
"add_i32",
restype=ctypes.c_int32,
argtypes=[ctypes.c_int32, ctypes.c_int32],
)
call_python = jit.ctypes_function(
"call_python",
restype=ctypes.c_int32,
argtypes=[ctypes.c_int32],
)
return cast(int, add_i32(40, 2)), cast(int, call_python(5))
def main() -> None:
try:
add_result, callback_result = run_jit()
except llvm.LLVMError as exc:
print(f"skipped: host JIT is unavailable: {exc}")
return
print(f"add_i32(40, 2) = {add_result}")
print(f"call_python(5) = {callback_result}")
if __name__ == "__main__":
main()
+77
View File
@@ -0,0 +1,77 @@
"""Use Pythonic metadata and debug-info helpers.
This example shows:
- instruction metadata by kind name,
- named metadata as a module mapping,
- module flags as a keyed view,
- DIBuilder convenience methods,
- builder debug-location scopes.
Run from the repository root with:
uv run python examples/metadata_debug_info.py
"""
from __future__ import annotations
import llvm
def build_module() -> str:
with llvm.create_context() as ctx:
with ctx.create_module("metadata_debug_info_example") as mod:
mod.is_new_dbg_info_format = True
i32 = ctx.types.i32
fn = mod.add_function("add", ctx.types.function(i32, [i32, i32]))
with mod.create_dibuilder() as dib:
file = dib.file("main.c", ".")
compile_unit = dib.compile_unit(
language=llvm.DwarfLanguage.C,
file=file,
producer="llvm-nanobind example",
)
subprogram = dib.function(
fn,
name="add",
file=file,
line=1,
return_type=i32,
param_types=[i32, i32],
)
_tmp_var = dib.local_variable(subprogram, "tmp", file, 2, i32)
# Named metadata is a mapping from name to appendable list.
mod.named_metadata["example.compile_units"].append(compile_unit)
# Module flags are keyed by their string name.
mod.module_flags.add(
"Example Metadata Version",
llvm.ModuleFlagBehavior.Warning,
i32.constant(1).as_metadata(),
)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
with builder.debug_location(line=2, column=5, scope=subprogram):
result = builder.add(fn.get_param(0), fn.get_param(1), "result")
# Metadata kind lookup is internal to the mapping view.
result.metadata["example.note"] = ctx.md_node(
[ctx.md_string("created by metadata_debug_info.py")]
)
builder.ret(result)
dib.finalize()
assert mod.verify(), mod.verification_error
return str(mod)
def main() -> None:
print(build_module(), end="")
if __name__ == "__main__":
main()
+49
View File
@@ -0,0 +1,49 @@
"""Create module named metadata and print LLVM IR.
Run from the repository root with:
uv run python examples/named_metadata.py
"""
from __future__ import annotations
import llvm
def build_module() -> str:
with llvm.create_context() as ctx:
with ctx.create_module("named_metadata_example") as mod:
tags = mod.named_metadata["example.tags"]
tags.append(ctx.md_node([ctx.md_string("frontend"), ctx.md_string("demo")]))
tags.append(ctx.md_node([ctx.md_string("purpose"), ctx.md_string("docs")]))
# Inspect named metadata through the module mapping view.
keys = list(mod.named_metadata)
operand_count = len(mod.named_metadata["example.tags"])
iterated_count = sum(1 for _ in mod.named_metadata["example.tags"])
first_tag = mod.named_metadata["example.tags"][0]
first_tag_strings = [operand.string for operand in first_tag.operands]
assert keys == ["example.tags"]
assert operand_count == 2
assert iterated_count == 2
assert first_tag_strings == ["frontend", "demo"]
assert mod.verify(), mod.verification_error
return (
"named metadata:\n"
f" keys: {', '.join(keys)}\n"
f" example.tags operands: {operand_count}\n"
f" iterated operands: {iterated_count}\n"
f" first tag strings: {', '.join(first_tag_strings)}\n"
"\nIR:\n"
f"{mod}"
)
def main() -> None:
print(build_module(), end="")
if __name__ == "__main__":
main()
+57
View File
@@ -0,0 +1,57 @@
"""Optimize one function with an LLVM PassBuilder function pipeline string.
``Function.optimize`` mutates only that function in place. The pipeline string is
a function-level PassBuilder pipeline, such as ``mem2reg,instcombine,simplifycfg``.
Run from the repository root with:
uv run python examples/optimize_function.py
"""
from __future__ import annotations
import textwrap
import llvm
INPUT_IR = """
define i32 @optimize_me(i32 %x) {
entry:
%tmp = alloca i32
%zero = add i32 %x, 0
store i32 %zero, ptr %tmp
%loaded = load i32, ptr %tmp
%result = mul i32 %loaded, 1
ret i32 %result
}
define i32 @leave_me_alone(i32 %x) {
entry:
%result = add i32 %x, 0
ret i32 %result
}
"""
def optimize_one_function(
ir_text: str,
function_name: str = "optimize_me",
pipeline: str = "mem2reg,instcombine,simplifycfg",
) -> str:
with llvm.create_context() as ctx:
with ctx.parse_ir(textwrap.dedent(ir_text).strip() + "\n") as mod:
assert mod.verify(), mod.verification_error
fn = mod.get_function(function_name)
assert fn is not None
fn.optimize(pipeline)
assert mod.verify(), mod.verification_error
return str(mod)
def main() -> None:
print(optimize_one_function(INPUT_IR), end="")
if __name__ == "__main__":
main()
+46
View File
@@ -0,0 +1,46 @@
"""Optimize a module with an LLVM PassBuilder pipeline string.
``Module.optimize`` mutates the module in place. The pipeline string is the same
string accepted by LLVM's PassBuilder, such as ``default<O0>`` or
``default<O2>``.
Run from the repository root with:
uv run python examples/optimize_module.py
"""
from __future__ import annotations
import textwrap
import llvm
INPUT_IR = """
define i32 @add_then_simplify(i32 %x) {
entry:
%tmp = alloca i32
%zero = add i32 %x, 0
store i32 %zero, ptr %tmp
%loaded = load i32, ptr %tmp
%result = mul i32 %loaded, 1
ret i32 %result
}
"""
def optimize_ir(ir_text: str, pipeline: str = "default<O2>") -> str:
with llvm.create_context() as ctx:
with ctx.parse_ir(textwrap.dedent(ir_text).strip() + "\n") as mod:
assert mod.verify(), mod.verification_error
mod.optimize(pipeline)
assert mod.verify(), mod.verification_error
return str(mod)
def main() -> None:
print(optimize_ir(INPUT_IR), end="")
if __name__ == "__main__":
main()
View File
-33
View File
@@ -1,33 +0,0 @@
(* Tiny unit test framework - really just to help find which line is busted *)
let exit_status = ref 0
let suite_name = ref ""
let group_name = ref ""
let case_num = ref 0
let print_checkpoints = false
let group name =
group_name := !suite_name ^ "/" ^ name;
case_num := 0;
if print_checkpoints then prerr_endline (" " ^ name ^ "...")
let insist ?(exit_on_fail = false) cond =
incr case_num;
if not cond then exit_status := 10;
( match (print_checkpoints, cond) with
| false, true -> ()
| false, false ->
prerr_endline
( "FAILED: " ^ !suite_name ^ "/" ^ !group_name ^ " #"
^ string_of_int !case_num )
| true, true -> prerr_endline (" " ^ string_of_int !case_num)
| true, false -> prerr_endline (" " ^ string_of_int !case_num ^ " FAIL") );
if exit_on_fail && not cond then exit !exit_status else ()
let suite name f =
suite_name := name;
if print_checkpoints then prerr_endline (name ^ ":");
f ()
-2
View File
@@ -1,2 +0,0 @@
# This is a directory for utility functions. No test here.
config.suffixes = [".dummy"]
-54
View File
@@ -1,54 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/analysis.ml
* RUN: %ocamlc -g -w +A -package llvm.analysis -linkpkg %t/analysis.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.analysis -linkpkg %t/analysis.ml -o %t/executable
* RUN: %t/executable
* XFAIL: vg_leak
*)
open Llvm
open Llvm_analysis
(* Note that this takes a moment to link, so it's best to keep the number of
individual tests low. *)
let context = global_context ()
let test x = if not x then exit 1 else ()
let bomb msg =
prerr_endline msg;
exit 2
let _ =
let fty = function_type (void_type context) [| |] in
let m = create_module context "valid_m" in
let fn = define_function "valid_fn" fty m in
let at_entry = builder_at_end context (entry_block fn) in
ignore (build_ret_void at_entry);
(* Test that valid constructs verify. *)
begin match verify_module m with
Some msg -> bomb "valid module failed verification!"
| None -> ()
end;
if not (verify_function fn) then bomb "valid function failed verification!";
(* Test that invalid constructs do not verify.
A basic block can contain only one terminator instruction. *)
ignore (build_ret_void at_entry);
begin match verify_module m with
Some msg -> ()
| None -> bomb "invalid module passed verification!"
end;
if verify_function fn then bomb "invalid function passed verification!";
dispose_module m
(* Don't bother to test assert_valid_{module,function}. *)
-83
View File
@@ -1,83 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/bitreader.ml
* RUN: %ocamlc -g -w +A -package llvm.bitreader -package llvm.bitwriter -linkpkg %t/bitreader.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc
* RUN: %ocamlopt -g -w +A -package llvm.bitreader -package llvm.bitwriter -linkpkg %t/bitreader.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc
* RUN: llvm-dis < %t/bitcode.bc
* XFAIL: vg_leak
*)
(* Note that this takes a moment to link, so it's best to keep the number of
individual tests low. *)
let context = Llvm.global_context ()
let diagnostic_handler _ = ()
let test x = if not x then exit 1 else ()
let _ =
Llvm.set_diagnostic_handler context (Some diagnostic_handler);
let fn = Sys.argv.(1) in
let m = Llvm.create_module context "ocaml_test_module" in
test (Llvm_bitwriter.write_bitcode_file m fn);
Llvm.dispose_module m;
(* parse_bitcode *)
begin
let mb = Llvm.MemoryBuffer.of_file fn in
begin try
let m = Llvm_bitreader.parse_bitcode context mb in
Llvm.dispose_module m
with x ->
Llvm.MemoryBuffer.dispose mb;
raise x
end
end;
(* MemoryBuffer.of_file *)
test begin try
let mb = Llvm.MemoryBuffer.of_file (fn ^ ".bogus") in
Llvm.MemoryBuffer.dispose mb;
false
with Llvm.IoError _ ->
true
end;
(* get_module *)
begin
let mb = Llvm.MemoryBuffer.of_file fn in
let m = begin try
Llvm_bitreader.get_module context mb
with x ->
Llvm.MemoryBuffer.dispose mb;
raise x
end in
Llvm.dispose_module m
end;
(* corrupt the bitcode *)
let fn = fn ^ ".txt" in
begin let oc = open_out fn in
output_string oc "not a bitcode file\n";
close_out oc
end;
(* test get_module exceptions *)
test begin
try
let mb = Llvm.MemoryBuffer.of_file fn in
let m = begin try
Llvm_bitreader.get_module context mb
with x ->
Llvm.MemoryBuffer.dispose mb;
raise x
end in
Llvm.dispose_module m;
false
with Llvm_bitreader.Error _ ->
true
end
-49
View File
@@ -1,49 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/bitwriter.ml
* RUN: %ocamlc -g -w -3 -w +A -package llvm.bitreader -package llvm.bitwriter -linkpkg %t/bitwriter.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc
* RUN: %ocamlopt -g -w -3 -w +A -package llvm.bitreader -package llvm.bitwriter -linkpkg %t/bitwriter.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc
* RUN: llvm-dis < %t/bitcode.bc
* XFAIL: vg_leak
*)
(* Note that this takes a moment to link, so it's best to keep the number of
individual tests low. *)
let context = Llvm.global_context ()
let test x = if not x then exit 1 else ()
let read_file name =
let ic = open_in_bin name in
let len = in_channel_length ic in
let buf = Bytes.create len in
test ((input ic buf 0 len) = len);
close_in ic;
buf
let temp_bitcode ?unbuffered m =
let temp_name, temp_oc = Filename.open_temp_file ~mode:[Open_binary] "" "" in
test (Llvm_bitwriter.output_bitcode ?unbuffered temp_oc m);
flush temp_oc;
let temp_buf = read_file temp_name in
close_out temp_oc;
temp_buf
let _ =
let m = Llvm.create_module context "ocaml_test_module" in
test (Llvm_bitwriter.write_bitcode_file m Sys.argv.(1));
let file_buf = read_file Sys.argv.(1) in
test (file_buf = temp_bitcode m);
test (file_buf = temp_bitcode ~unbuffered:false m);
test (file_buf = temp_bitcode ~unbuffered:true m);
test (file_buf = Bytes.of_string (Llvm.MemoryBuffer.as_string (Llvm_bitwriter.write_bitcode_to_memory_buffer m)))
-1498
View File
File diff suppressed because it is too large Load Diff
-471
View File
@@ -1,471 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/debuginfo.ml && cp %S/Utils/Testsuite.ml %t/Testsuite.ml
* RUN: %ocamlc -g -w +A -package llvm.all_backends -package llvm.target -package llvm.analysis -package llvm.debuginfo -I %t/ -linkpkg %t/Testsuite.ml %t/debuginfo.ml -o %t/executable
* RUN: %t/executable | FileCheck %s
* RUN: %ocamlopt -g -w +A -package llvm.all_backends -package llvm.target -package llvm.analysis -package llvm.debuginfo -I %t/ -linkpkg %t/Testsuite.ml %t/debuginfo.ml -o %t/executable
* RUN: %t/executable | FileCheck %s
* XFAIL: vg_leak
*)
open Testsuite
let context = Llvm.global_context ()
let filename = "di_test_file"
let directory = "di_test_dir"
let module_name = "di_test_module"
let null_metadata = Llvm_debuginfo.llmetadata_null ()
let string_of_metadata md =
Llvm.string_of_llvalue (Llvm.metadata_as_value context md)
let stdout_metadata md = Printf.printf "%s\n" (string_of_metadata md)
let prepare_target llmod =
Llvm_all_backends.initialize ();
let triple = Llvm_target.Target.default_triple () in
let lltarget = Llvm_target.Target.by_triple triple in
let llmachine = Llvm_target.TargetMachine.create ~triple lltarget in
let lldly =
Llvm_target.DataLayout.as_string
(Llvm_target.TargetMachine.data_layout llmachine)
in
let _ = Llvm.set_target_triple triple llmod in
let _ = Llvm.set_data_layout lldly llmod in
()
let new_module () =
let m = Llvm.create_module context module_name in
let () = prepare_target m in
let () = Llvm_debuginfo.set_is_new_dbg_info_format m true in
insist (Llvm_debuginfo.is_new_dbg_info_format m);
m
let test_get_module () =
group "module_level_tests";
let m = new_module () in
let cur_ver = Llvm_debuginfo.debug_metadata_version () in
insist (cur_ver > 0);
let m_ver = Llvm_debuginfo.get_module_debug_metadata_version m in
(* We haven't added any debug info to the module *)
insist (m_ver = 0);
let dibuilder = Llvm_debuginfo.dibuilder m in
let di_version_key = "Debug Info Version" in
let ver =
Llvm.value_as_metadata @@ Llvm.const_int (Llvm.i32_type context) cur_ver
in
let () =
Llvm.add_module_flag m Llvm.ModuleFlagBehavior.Warning di_version_key ver
in
let file_di =
Llvm_debuginfo.dibuild_create_file dibuilder ~filename ~directory
in
stdout_metadata file_di;
(* CHECK: [[FILE_PTR:<0x[0-9a-f]*>]] = !DIFile(filename: "di_test_file", directory: "di_test_dir")
*)
insist
( Llvm_debuginfo.di_file_get_filename ~file:file_di = filename
&& Llvm_debuginfo.di_file_get_directory ~file:file_di = directory );
insist
( Llvm_debuginfo.get_metadata_kind file_di
= Llvm_debuginfo.MetadataKind.DIFileMetadataKind );
let cu_di =
Llvm_debuginfo.dibuild_create_compile_unit dibuilder
Llvm_debuginfo.DWARFSourceLanguageKind.C89 ~file_ref:file_di
~producer:"TestGen" ~is_optimized:false ~flags:"" ~runtime_ver:0
~split_name:"" Llvm_debuginfo.DWARFEmissionKind.LineTablesOnly ~dwoid:0
~di_inlining:false ~di_profiling:false ~sys_root:"" ~sdk:""
in
stdout_metadata cu_di;
(* CHECK: [[CMPUNIT_PTR:<0x[0-9a-f]*>]] = distinct !DICompileUnit(language: DW_LANG_C89, file: [[FILE_PTR]], producer: "TestGen", isOptimized: false, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false)
*)
insist
( Llvm_debuginfo.get_metadata_kind cu_di
= Llvm_debuginfo.MetadataKind.DICompileUnitMetadataKind );
let m_di =
Llvm_debuginfo.dibuild_create_module dibuilder ~parent_ref:cu_di
~name:module_name ~config_macros:"" ~include_path:"" ~sys_root:""
in
insist
( Llvm_debuginfo.get_metadata_kind m_di
= Llvm_debuginfo.MetadataKind.DIModuleMetadataKind );
insist (Llvm_debuginfo.get_module_debug_metadata_version m = cur_ver);
stdout_metadata m_di;
(* CHECK: [[MODULE_PTR:<0x[0-9a-f]*>]] = !DIModule(scope: null, name: "di_test_module")
*)
(m, dibuilder, file_di, m_di)
let flags_zero = Llvm_debuginfo.diflags_get Llvm_debuginfo.DIFlag.Zero
let int_ty_di bits dibuilder =
Llvm_debuginfo.dibuild_create_basic_type dibuilder ~name:"int"
~size_in_bits:bits ~encoding:0x05
(* llvm::dwarf::DW_ATE_signed *) flags_zero
let test_get_function m dibuilder file_di m_di =
group "function_level_tests";
(* Create a function of type "void foo (int)". *)
let int_ty_di = int_ty_di 32 dibuilder in
stdout_metadata int_ty_di;
(* CHECK: [[INT32_PTR:<0x[0-9a-f]*>]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
*)
let int_ptr_ty_di =
Llvm_debuginfo.dibuild_create_pointer_type dibuilder
~pointee_ty:int_ty_di
~size_in_bits:32
~align_in_bits:32
~address_space:0
~name:"ptrint"
in
stdout_metadata int_ptr_ty_di;
(* CHECK: [[PTRINT32_PTR:<0x[0-9a-f]*>]] = !DIDerivedType(tag: DW_TAG_pointer_type, name: "ptrint", baseType: [[INT32_PTR]], size: 32, align: 32, dwarfAddressSpace: 0)
*)
let param_types = [| null_metadata; int_ty_di; int_ptr_ty_di |] in
let fty_di =
Llvm_debuginfo.dibuild_create_subroutine_type dibuilder ~file:file_di
~param_types flags_zero
in
insist
( Llvm_debuginfo.get_metadata_kind fty_di
= Llvm_debuginfo.MetadataKind.DISubroutineTypeMetadataKind );
(* To be able to print and verify the type array of the subroutine type,
* since we have no way to access it from fty_di, we build it again. *)
let fty_di_args =
Llvm_debuginfo.dibuild_get_or_create_type_array dibuilder ~data:param_types
in
stdout_metadata fty_di_args;
(* CHECK: [[FARGS_PTR:<0x[0-9a-f]*>]] = !{null, [[INT32_PTR]], [[PTRINT32_PTR]]}
*)
stdout_metadata fty_di;
(* CHECK: [[SBRTNTY_PTR:<0x[0-9a-f]*>]] = !DISubroutineType(types: [[FARGS_PTR]])
*)
(* Let's create the LLVM-IR function now. *)
let name = "tfun" in
let fty =
Llvm.function_type (Llvm.void_type context)
[| Llvm.i32_type context; Llvm.pointer_type context |]
in
let f = Llvm.define_function name fty m in
let f_di =
Llvm_debuginfo.dibuild_create_function dibuilder ~scope:m_di ~name
~linkage_name:name ~file:file_di ~line_no:10 ~ty:fty_di
~is_local_to_unit:false ~is_definition:true ~scope_line:10
~flags:flags_zero ~is_optimized:false
in
stdout_metadata f_di;
(* CHECK: [[SBPRG_PTR:<0x[0-9a-f]*>]] = distinct !DISubprogram(name: "tfun", linkageName: "tfun", scope: [[MODULE_PTR]], file: [[FILE_PTR]], line: 10, type: [[SBRTNTY_PTR]], scopeLine: 10, spFlags: DISPFlagDefinition, unit: [[CMPUNIT_PTR]])
*)
Llvm_debuginfo.set_subprogram f f_di;
( match Llvm_debuginfo.get_subprogram f with
| Some f_di' -> insist (f_di = f_di')
| None -> insist false );
insist
( Llvm_debuginfo.get_metadata_kind f_di
= Llvm_debuginfo.MetadataKind.DISubprogramMetadataKind );
insist (Llvm_debuginfo.di_subprogram_get_line f_di = 10);
(fty, f, f_di)
let test_bbinstr fty f f_di file_di dibuilder =
group "basic_block and instructions tests";
(* Create this pattern:
* if (arg0 != 0) {
* foo(arg0, arg1);
* }
* return;
*)
let arg0 = (Llvm.params f).(0) in
let arg1 = (Llvm.params f).(1) in
let builder = Llvm.builder_at_end context (Llvm.entry_block f) in
let zero = Llvm.const_int (Llvm.i32_type context) 0 in
let cmpi = Llvm.build_icmp Llvm.Icmp.Ne zero arg0 "cmpi" builder in
let truebb = Llvm.append_block context "truebb" f in
let falsebb = Llvm.append_block context "falsebb" f in
let _ = Llvm.build_cond_br cmpi truebb falsebb builder in
let foodecl = Llvm.declare_function "foo" fty (Llvm.global_parent f) in
let _ =
Llvm.position_at_end truebb builder;
let scope =
Llvm_debuginfo.dibuild_create_lexical_block dibuilder ~scope:f_di
~file:file_di ~line:9 ~column:4
in
let file_of_f_di = Llvm_debuginfo.di_scope_get_file ~scope:f_di in
let file_of_scope = Llvm_debuginfo.di_scope_get_file ~scope in
insist
( match (file_of_f_di, file_of_scope) with
| Some file_of_f_di', Some file_of_scope' ->
file_of_f_di' = file_di && file_of_scope' = file_di
| _ -> false );
let foocall = Llvm.build_call fty foodecl [| arg0; arg1 |] "" builder in
let foocall_loc =
Llvm_debuginfo.dibuild_create_debug_location context ~line:10 ~column:12
~scope
in
Llvm_debuginfo.instr_set_debug_loc foocall (Some foocall_loc);
insist
( match Llvm_debuginfo.instr_get_debug_loc foocall with
| Some foocall_loc' -> foocall_loc' = foocall_loc
| None -> false );
stdout_metadata scope;
(* CHECK: [[BLOCK_PTR:<0x[0-9a-f]*>]] = distinct !DILexicalBlock(scope: [[SBPRG_PTR]], file: [[FILE_PTR]], line: 9, column: 4)
*)
stdout_metadata foocall_loc;
(* CHECK: !DILocation(line: 10, column: 12, scope: [[BLOCK_PTR]])
*)
insist
( Llvm_debuginfo.di_location_get_scope ~location:foocall_loc = scope
&& Llvm_debuginfo.di_location_get_line ~location:foocall_loc = 10
&& Llvm_debuginfo.di_location_get_column ~location:foocall_loc = 12 );
insist
( Llvm_debuginfo.get_metadata_kind foocall_loc
= Llvm_debuginfo.MetadataKind.DILocationMetadataKind
&& Llvm_debuginfo.get_metadata_kind scope
= Llvm_debuginfo.MetadataKind.DILexicalBlockMetadataKind );
Llvm.build_br falsebb builder
in
let _ =
Llvm.position_at_end falsebb builder;
Llvm.build_ret_void builder
in
(* Printf.printf "%s\n" (Llvm.string_of_llmodule (Llvm.global_parent f)); *)
()
let test_global_variable_expression dibuilder f_di m_di =
group "global variable expression tests";
let cexpr_di =
Llvm_debuginfo.dibuild_create_constant_value_expression dibuilder 0
in
stdout_metadata cexpr_di;
(* CHECK: [[DICEXPR:!DIExpression\(DW_OP_constu, 0, DW_OP_stack_value\)]]
*)
insist
( Llvm_debuginfo.get_metadata_kind cexpr_di
= Llvm_debuginfo.MetadataKind.DIExpressionMetadataKind );
let ty = int_ty_di 64 dibuilder in
stdout_metadata ty;
(* CHECK: [[INT64TY_PTR:<0x[0-9a-f]*>]] = !DIBasicType(name: "int", size: 64, encoding: DW_ATE_signed)
*)
let gvexpr_di =
Llvm_debuginfo.dibuild_create_global_variable_expression dibuilder
~scope:m_di ~name:"my_global" ~linkage:"" ~file:f_di ~line:5 ~ty
~is_local_to_unit:true ~expr:cexpr_di ~decl:null_metadata ~align_in_bits:0
in
insist
( Llvm_debuginfo.get_metadata_kind gvexpr_di
= Llvm_debuginfo.MetadataKind.DIGlobalVariableExpressionMetadataKind );
( match
Llvm_debuginfo.di_global_variable_expression_get_variable gvexpr_di
with
| Some gvexpr_var_di ->
insist
( Llvm_debuginfo.get_metadata_kind gvexpr_var_di
= Llvm_debuginfo.MetadataKind.DIGlobalVariableMetadataKind );
stdout_metadata gvexpr_var_di
(* CHECK: [[GV_PTR:<0x[0-9a-f]*>]] = distinct !DIGlobalVariable(name: "my_global", scope: [[MODULE_PTR]], file: [[FILE_PTR]], line: 5, type: [[INT64TY_PTR]], isLocal: true, isDefinition: true)
*)
| None -> insist false );
stdout_metadata gvexpr_di;
(* CHECK: [[GVEXP_PTR:<0x[0-9a-f]*>]] = !DIGlobalVariableExpression(var: [[GV_PTR]], expr: [[DICEXPR]])
*)
()
let test_variables f dibuilder file_di fun_di =
let entry_term = Option.get @@ (Llvm.block_terminator (Llvm.entry_block f)) in
group "Local and parameter variable tests";
let ty = int_ty_di 64 dibuilder in
stdout_metadata ty;
(* CHECK: [[INT64TY_PTR:<0x[0-9a-f]*>]] = !DIBasicType(name: "int", size: 64, encoding: DW_ATE_signed)
*)
let auto_var =
Llvm_debuginfo.dibuild_create_auto_variable dibuilder ~scope:fun_di
~name:"my_local" ~file:file_di ~line:10 ~ty
~always_preserve:false flags_zero ~align_in_bits:0
in
stdout_metadata auto_var;
(* CHECK: [[LOCAL_VAR_PTR:<0x[0-9a-f]*>]] = !DILocalVariable(name: "my_local", scope: <{{0x[0-9a-f]*}}>, file: <{{0x[0-9a-f]*}}>, line: 10, type: [[INT64TY_PTR]])
*)
let builder = Llvm.builder_before context entry_term in
let all = Llvm.build_alloca (Llvm.i64_type context) "my_alloca" builder in
let scope =
Llvm_debuginfo.dibuild_create_lexical_block dibuilder ~scope:fun_di
~file:file_di ~line:9 ~column:4
in
let location =
Llvm_debuginfo.dibuild_create_debug_location
context ~line:10 ~column:12 ~scope
in
let vdi = Llvm_debuginfo.dibuild_insert_declare_before dibuilder ~storage:all
~var_info:auto_var ~expr:(Llvm_debuginfo.dibuild_expression dibuilder [||])
~location ~instr:entry_term
in
let () = Printf.printf "%s\n" (Llvm.string_of_lldbgrecord vdi) in
(* CHECK: dbg_declare(ptr %my_alloca, ![[#]], !DIExpression(), ![[#]])
*)
let arg1 = (Llvm.params f).(1) in
let arg_var = Llvm_debuginfo.dibuild_create_parameter_variable dibuilder ~scope:fun_di
~name:"my_arg" ~argno:1 ~file:file_di ~line:10 ~ty
~always_preserve:false flags_zero
in
let argdi = Llvm_debuginfo.dibuild_insert_declare_before dibuilder ~storage:arg1
~var_info:arg_var ~expr:(Llvm_debuginfo.dibuild_expression dibuilder [||])
~location ~instr:entry_term
in
let () = Printf.printf "%s\n" (Llvm.string_of_lldbgrecord argdi) in
(* CHECK: dbg_declare(ptr %1, ![[#]], !DIExpression(), ![[#]])
*)
()
let test_types dibuilder file_di m_di =
group "type tests";
let namespace_di =
Llvm_debuginfo.dibuild_create_namespace dibuilder ~parent_ref:m_di
~name:"NameSpace1" ~export_symbols:false
in
stdout_metadata namespace_di;
(* CHECK: [[NAMESPACE_PTR:<0x[0-9a-f]*>]] = !DINamespace(name: "NameSpace1", scope: [[MODULE_PTR]])
*)
let int64_ty_di = int_ty_di 64 dibuilder in
let structty_args = [| int64_ty_di; int64_ty_di; int64_ty_di |] in
let struct_ty_di =
Llvm_debuginfo.dibuild_create_struct_type dibuilder ~scope:namespace_di
~name:"StructType1" ~file:file_di ~line_number:20 ~size_in_bits:192
~align_in_bits:0 flags_zero ~derived_from:null_metadata
~elements:structty_args Llvm_debuginfo.DWARFSourceLanguageKind.C89
~vtable_holder:null_metadata ~unique_id:"StructType1"
in
(* Since there's no way to fetch the element types which is now
* a type array, we build that again for checking. *)
let structty_di_eltypes =
Llvm_debuginfo.dibuild_get_or_create_type_array dibuilder
~data:structty_args
in
stdout_metadata structty_di_eltypes;
(* CHECK: [[STRUCTELT_PTR:<0x[0-9a-f]*>]] = !{[[INT64TY_PTR]], [[INT64TY_PTR]], [[INT64TY_PTR]]}
*)
stdout_metadata struct_ty_di;
(* CHECK: [[STRUCT_PTR:<0x[0-9a-f]*>]] = !DICompositeType(tag: DW_TAG_structure_type, name: "StructType1", scope: [[NAMESPACE_PTR]], file: [[FILE_PTR]], line: 20, size: 192, elements: [[STRUCTELT_PTR]], identifier: "StructType1")
*)
insist
( Llvm_debuginfo.get_metadata_kind struct_ty_di
= Llvm_debuginfo.MetadataKind.DICompositeTypeMetadataKind );
let structptr_di =
Llvm_debuginfo.dibuild_create_pointer_type dibuilder
~pointee_ty:struct_ty_di ~size_in_bits:192 ~align_in_bits:0
~address_space:0 ~name:""
in
stdout_metadata structptr_di;
(* CHECK: [[STRUCTPTR_PTR:<0x[0-9a-f]*>]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[STRUCT_PTR]], size: 192, dwarfAddressSpace: 0)
*)
insist
( Llvm_debuginfo.get_metadata_kind structptr_di
= Llvm_debuginfo.MetadataKind.DIDerivedTypeMetadataKind );
let enumerator1 =
Llvm_debuginfo.dibuild_create_enumerator dibuilder ~name:"Test_A" ~value:0
~is_unsigned:true
in
stdout_metadata enumerator1;
(* CHECK: [[ENUMERATOR1_PTR:<0x[0-9a-f]*>]] = !DIEnumerator(name: "Test_A", value: 0, isUnsigned: true)
*)
let enumerator2 =
Llvm_debuginfo.dibuild_create_enumerator dibuilder ~name:"Test_B" ~value:1
~is_unsigned:true
in
stdout_metadata enumerator2;
(* CHECK: [[ENUMERATOR2_PTR:<0x[0-9a-f]*>]] = !DIEnumerator(name: "Test_B", value: 1, isUnsigned: true)
*)
let enumerator3 =
Llvm_debuginfo.dibuild_create_enumerator dibuilder ~name:"Test_C" ~value:2
~is_unsigned:true
in
insist
( Llvm_debuginfo.get_metadata_kind enumerator1
= Llvm_debuginfo.MetadataKind.DIEnumeratorMetadataKind
&& Llvm_debuginfo.get_metadata_kind enumerator2
= Llvm_debuginfo.MetadataKind.DIEnumeratorMetadataKind
&& Llvm_debuginfo.get_metadata_kind enumerator3
= Llvm_debuginfo.MetadataKind.DIEnumeratorMetadataKind );
stdout_metadata enumerator3;
(* CHECK: [[ENUMERATOR3_PTR:<0x[0-9a-f]*>]] = !DIEnumerator(name: "Test_C", value: 2, isUnsigned: true)
*)
let elements = [| enumerator1; enumerator2; enumerator3 |] in
let enumeration_ty_di =
Llvm_debuginfo.dibuild_create_enumeration_type dibuilder ~scope:namespace_di
~name:"EnumTest" ~file:file_di ~line_number:1 ~size_in_bits:64
~align_in_bits:0 ~elements ~class_ty:int64_ty_di
in
let elements_arr =
Llvm_debuginfo.dibuild_get_or_create_array dibuilder ~data:elements
in
stdout_metadata elements_arr;
(* CHECK: [[ELEMENTS_PTR:<0x[0-9a-f]*>]] = !{[[ENUMERATOR1_PTR]], [[ENUMERATOR2_PTR]], [[ENUMERATOR3_PTR]]}
*)
stdout_metadata enumeration_ty_di;
(* CHECK: [[ENUMERATION_PTR:<0x[0-9a-f]*>]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "EnumTest", scope: [[NAMESPACE_PTR]], file: [[FILE_PTR]], line: 1, baseType: [[INT64TY_PTR]], size: 64, elements: [[ELEMENTS_PTR]])
*)
insist
( Llvm_debuginfo.get_metadata_kind enumeration_ty_di
= Llvm_debuginfo.MetadataKind.DICompositeTypeMetadataKind );
let int32_ty_di = int_ty_di 32 dibuilder in
let class_mem1 =
Llvm_debuginfo.dibuild_create_member_type dibuilder ~scope:namespace_di
~name:"Field1" ~file:file_di ~line_number:3 ~size_in_bits:32
~align_in_bits:0 ~offset_in_bits:0 flags_zero ~ty:int32_ty_di
in
stdout_metadata class_mem1;
(* CHECK: [[MEMB1_PTR:<0x[0-9a-f]*>]] = !DIDerivedType(tag: DW_TAG_member, name: "Field1", scope: [[NAMESPACE_PTR]], file: [[FILE_PTR]], line: 3, baseType: [[INT32_PTR]], size: 32)
*)
insist (Llvm_debuginfo.di_type_get_name class_mem1 = "Field1");
insist (Llvm_debuginfo.di_type_get_line class_mem1 = 3);
let class_mem2 =
Llvm_debuginfo.dibuild_create_member_type dibuilder ~scope:namespace_di
~name:"Field2" ~file:file_di ~line_number:4 ~size_in_bits:64
~align_in_bits:8 ~offset_in_bits:32 flags_zero ~ty:int64_ty_di
in
stdout_metadata class_mem2;
(* CHECK: [[MEMB2_PTR:<0x[0-9a-f]*>]] = !DIDerivedType(tag: DW_TAG_member, name: "Field2", scope: [[NAMESPACE_PTR]], file: [[FILE_PTR]], line: 4, baseType: [[INT64TY_PTR]], size: 64, align: 8, offset: 32)
*)
insist (Llvm_debuginfo.di_type_get_offset_in_bits class_mem2 = 32);
insist (Llvm_debuginfo.di_type_get_size_in_bits class_mem2 = 64);
insist (Llvm_debuginfo.di_type_get_align_in_bits class_mem2 = 8);
let class_elements = [| class_mem1; class_mem2 |] in
insist
( Llvm_debuginfo.get_metadata_kind class_mem1
= Llvm_debuginfo.MetadataKind.DIDerivedTypeMetadataKind
&& Llvm_debuginfo.get_metadata_kind class_mem2
= Llvm_debuginfo.MetadataKind.DIDerivedTypeMetadataKind );
stdout_metadata
(Llvm_debuginfo.dibuild_get_or_create_type_array dibuilder
~data:class_elements);
(* CHECK: [[CLASSMEM_PTRS:<0x[0-9a-f]*>]] = !{[[MEMB1_PTR]], [[MEMB2_PTR]]}
*)
let classty_di =
Llvm_debuginfo.dibuild_create_class_type dibuilder ~scope:namespace_di
~name:"MyClass" ~file:file_di ~line_number:1 ~size_in_bits:96
~align_in_bits:0 ~offset_in_bits:0 flags_zero ~derived_from:null_metadata
~elements:class_elements ~vtable_holder:null_metadata
~template_params_node:null_metadata ~unique_identifier:"MyClass"
in
stdout_metadata classty_di;
(* [[CLASS_PTR:<0x[0-9a-f]*>]] = !DICompositeType(tag: DW_TAG_structure_type, name: "MyClass", scope: [[NAMESPACE_PTR]], file: [[FILE_PTR]], line: 1, size: 96, elements: [[CLASSMEM_PTRS]], identifier: "MyClass")
*)
insist
( Llvm_debuginfo.get_metadata_kind classty_di
= Llvm_debuginfo.MetadataKind.DICompositeTypeMetadataKind );
()
let () =
let m, dibuilder, file_di, m_di = test_get_module () in
let fty, f, fun_di = test_get_function m dibuilder file_di m_di in
let () = test_bbinstr fty f fun_di file_di dibuilder in
let () = test_global_variable_expression dibuilder file_di m_di in
let () = test_variables f dibuilder file_di fun_di in
let () = test_types dibuilder file_di m_di in
Llvm_debuginfo.dibuild_finalize dibuilder;
( match Llvm_analysis.verify_module m with
| Some err ->
prerr_endline ("Verification of module failed: " ^ err);
exit_status := 1
| None -> () );
exit !exit_status
-48
View File
@@ -1,48 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/diagnostic_handler.ml
* RUN: %ocamlc -g -w +A -package llvm.bitreader -linkpkg %t/diagnostic_handler.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc | FileCheck %s
* RUN: %ocamlopt -g -w +A -package llvm.bitreader -linkpkg %t/diagnostic_handler.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc | FileCheck %s
* XFAIL: vg_leak
*)
let context = Llvm.global_context ()
let diagnostic_handler d =
Printf.printf
"Diagnostic handler called: %s\n" (Llvm.Diagnostic.description d);
match Llvm.Diagnostic.severity d with
| Error -> Printf.printf "Diagnostic severity is Error\n"
| Warning -> Printf.printf "Diagnostic severity is Warning\n"
| Remark -> Printf.printf "Diagnostic severity is Remark\n"
| Note -> Printf.printf "Diagnostic severity is Note\n"
let test x = if not x then exit 1 else ()
let _ =
Llvm.set_diagnostic_handler context (Some diagnostic_handler);
(* corrupt the bitcode *)
let fn = Sys.argv.(1) ^ ".txt" in
begin let oc = open_out fn in
output_string oc "not a bitcode file\n";
close_out oc
end;
test begin
try
let mb = Llvm.MemoryBuffer.of_file fn in
let m = begin try
(* CHECK: Diagnostic handler called: Invalid bitcode signature
* CHECK: Diagnostic severity is Error
*)
Llvm_bitreader.get_module context mb
with x ->
Llvm.MemoryBuffer.dispose mb;
raise x
end in
Llvm.dispose_module m;
false
with Llvm_bitreader.Error _ ->
true
end
-113
View File
@@ -1,113 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/executionengine.ml
* RUN: %ocamlc -g -w +A -thread -package ctypes.foreign,llvm.executionengine -linkpkg %t/executionengine.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -thread -package ctypes.foreign,llvm.executionengine -linkpkg %t/executionengine.ml -o %t/executable
* RUN: %t/executable
* REQUIRES: native
* XFAIL: vg_leak
*)
open Llvm
open Llvm_executionengine
open Llvm_target
(* Note that this takes a moment to link, so it's best to keep the number of
individual tests low. *)
let context = global_context ()
let i8_type = Llvm.i8_type context
let i32_type = Llvm.i32_type context
let i64_type = Llvm.i64_type context
let double_type = Llvm.double_type context
let () =
assert (Llvm_executionengine.initialize ())
let bomb msg =
prerr_endline msg;
exit 2
let define_getglobal m pg =
let fty = function_type i32_type [||] in
let fn = define_function "getglobal" fty m in
let b = builder_at_end (global_context ()) (entry_block fn) in
let g = build_call fty pg [||] "" b in
ignore (build_ret g b);
fn
let define_plus m =
let fn = define_function "plus" (function_type i32_type [| i32_type;
i32_type |]) m in
let b = builder_at_end (global_context ()) (entry_block fn) in
let add = build_add (param fn 0) (param fn 1) "sum" b in
ignore (build_ret add b);
fn
let test_executionengine () =
let open Ctypes in
(* create *)
let m = create_module (global_context ()) "test_module" in
let ee = create m in
(* add plus *)
ignore (define_plus m);
(* declare global variable *)
ignore (define_global "globvar" (const_int i32_type 23) m);
(* add module *)
let m2 = create_module (global_context ()) "test_module2" in
add_module m2 ee;
(* add global mapping *)
(* BROKEN: see PR20656 *)
(* let g = declare_function "g" (function_type i32_type [||]) m2 in
let cg = coerce (Foreign.funptr (void @-> returning int32_t)) (ptr void)
(fun () -> 42l) in
add_global_mapping g cg ee;
(* check g *)
let cg' = get_pointer_to_global g (ptr void) ee in
if 0 <> ptr_compare cg cg' then bomb "int pointers to g differ";
(* add getglobal *)
let getglobal = define_getglobal m2 g in*)
(* run_static_ctors *)
run_static_ctors ee;
(* get a handle on globvar *)
let varh = get_global_value_address "globvar" int32_t ee in
if 23l <> varh then bomb "get_global_value_address didn't work";
(* call plus *)
let cplusty = Foreign.funptr (int32_t @-> int32_t @-> returning int32_t) in
let cplus = get_function_address "plus" cplusty ee in
if 4l <> cplus 2l 2l then bomb "plus didn't work";
(* call getglobal *)
(* let cgetglobalty = Foreign.funptr (void @-> returning int32_t) in
let cgetglobal = get_pointer_to_global getglobal cgetglobalty ee in
if 42l <> cgetglobal () then bomb "getglobal didn't work"; *)
(* remove_module *)
remove_module m2 ee;
dispose_module m2;
(* run_static_dtors *)
run_static_dtors ee;
(* Show that the data layout binding links and runs.*)
let dl = data_layout ee in
(* Demonstrate that a garbage pointer wasn't returned. *)
let ty = DataLayout.intptr_type context dl in
if ty != i32_type && ty != i64_type then bomb "target_data did not work";
(* dispose *)
dispose ee
let () =
test_executionengine ();
Gc.compact ()
-25
View File
@@ -1,25 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/ext_exc.ml
* RUN: %ocamlc -g -w +A -package llvm.bitreader -linkpkg %t/ext_exc.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.bitreader -linkpkg %t/ext_exc.ml -o %t/executable
* RUN: %t/executable
* XFAIL: vg_leak
*)
let context = Llvm.global_context ()
let diagnostic_handler _ = ()
(* This used to crash, we must not use 'external' in .mli files, but 'val' if we
* want the let _ bindings executed, see http://caml.inria.fr/mantis/view.php?id=4166 *)
let _ =
Llvm.set_diagnostic_handler context (Some diagnostic_handler);
try
ignore (Llvm_bitreader.get_module context (Llvm.MemoryBuffer.of_stdin ()))
with
Llvm_bitreader.Error _ -> ();;
let _ =
try
ignore (Llvm.MemoryBuffer.of_file "/path/to/nonexistent/file")
with
Llvm.IoError _ -> ();;
-59
View File
@@ -1,59 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/irreader.ml
* RUN: %ocamlc -g -w +A -package llvm.irreader -linkpkg %t/irreader.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.irreader -linkpkg %t/irreader.ml -o %t/executable
* RUN: %t/executable
* XFAIL: vg_leak
*)
(* Note: It takes several seconds for ocamlopt to link an executable with
libLLVMCore.a, so it's better to write a big test than a bunch of
little ones. *)
open Llvm
open Llvm_irreader
let context = global_context ()
(* Tiny unit test framework - really just to help find which line is busted *)
let print_checkpoints = false
let suite name f =
if print_checkpoints then
prerr_endline (name ^ ":");
f ()
let _ =
Printexc.record_backtrace true
let insist cond =
if not cond then failwith "insist"
(*===-- IR Reader ---------------------------------------------------------===*)
let test_irreader () =
begin
let buf = MemoryBuffer.of_string "@foo = global i32 42" in
let m = parse_ir context buf in
match lookup_global "foo" m with
| Some foo ->
insist ((global_initializer foo) = (Some (const_int (i32_type context) 42)))
| None ->
failwith "global"
end;
begin
let buf = MemoryBuffer.of_string "@foo = global garble" in
try
ignore (parse_ir context buf);
failwith "parsed"
with Llvm_irreader.Error _ ->
()
end
(*===-- Driver ------------------------------------------------------------===*)
let _ =
suite "irreader" test_irreader
-65
View File
@@ -1,65 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/linker.ml
* RUN: %ocamlc -g -w +A -package llvm.linker -linkpkg %t/linker.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.linker -linkpkg %t/linker.ml -o %t/executable
* RUN: %t/executable
* XFAIL: vg_leak
*)
(* Note: It takes several seconds for ocamlopt to link an executable with
libLLVMCore.a, so it's better to write a big test than a bunch of
little ones. *)
open Llvm
open Llvm_linker
let context = global_context ()
let void_type = Llvm.void_type context
let diagnostic_handler _ = ()
(* Tiny unit test framework - really just to help find which line is busted *)
let print_checkpoints = false
let suite name f =
if print_checkpoints then
prerr_endline (name ^ ":");
f ()
(*===-- Linker -----------------------------------------------------------===*)
let test_linker () =
set_diagnostic_handler context (Some diagnostic_handler);
let fty = function_type void_type [| |] in
let make_module name =
let m = create_module context name in
let fn = define_function ("fn_" ^ name) fty m in
ignore (build_ret_void (builder_at_end context (entry_block fn)));
m
in
let m1 = make_module "one"
and m2 = make_module "two" in
link_modules m1 m2;
dispose_module m1;
let m1 = make_module "one"
and m2 = make_module "two" in
link_modules m1 m2;
dispose_module m1;
let m1 = make_module "one"
and m2 = make_module "one" in
try
link_modules m1 m2;
failwith "must raise"
with Error _ ->
dispose_module m1
(*===-- Driver ------------------------------------------------------------===*)
let _ =
suite "linker" test_linker
-4
View File
@@ -1,4 +0,0 @@
config.suffixes = [".ml"]
if not "ocaml" in config.root.llvm_bindings:
config.unsupported = True
-74
View File
@@ -1,74 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/passbuilder.ml
* RUN: %ocamlc -g -w +A -package llvm.passbuilder -package llvm.all_backends -linkpkg %t/passbuilder.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.passbuilder -package llvm.all_backends -linkpkg %t/passbuilder.ml -o %t/executable
* RUN: %t/executable
* XFAIL: vg_leak
*)
let () = Llvm_all_backends.initialize ()
(*===-- Fixture -----------------------------------------------------------===*)
let context = Llvm.global_context ()
let m = Llvm.create_module context "mymodule"
let () =
let ty =
Llvm.function_type (Llvm.void_type context)
[| Llvm.i1_type context;
Llvm.pointer_type context;
Llvm.pointer_type context |]
in
let foo = Llvm.define_function "foo" ty m in
let entry = Llvm.entry_block foo in
let builder = Llvm.builder_at_end context entry in
ignore
(Llvm.build_store
(Llvm.const_int (Llvm.i8_type context) 42) (Llvm.param foo 1) builder);
let loop = Llvm.append_block context "loop" foo in
Llvm.position_at_end loop builder;
ignore
(Llvm.build_load (Llvm.i8_type context) (Llvm.param foo 2) "tmp1" builder);
ignore (Llvm.build_br loop builder);
let exit = Llvm.append_block context "exit" foo in
Llvm.position_at_end exit builder;
ignore (Llvm.build_ret_void builder);
Llvm.position_at_end entry builder;
ignore (Llvm.build_cond_br (Llvm.param foo 0) loop exit builder)
let target =
Llvm_target.Target.by_triple (Llvm_target.Target.default_triple ())
let machine =
Llvm_target.TargetMachine.create
~triple:(Llvm_target.Target.default_triple ()) target
let options = Llvm_passbuilder.create_passbuilder_options ()
(*===-- PassBuilder -------------------------------------------------------===*)
let () =
Llvm_passbuilder.passbuilder_options_set_verify_each options true;
Llvm_passbuilder.passbuilder_options_set_debug_logging options true;
Llvm_passbuilder.passbuilder_options_set_loop_interleaving options true;
Llvm_passbuilder.passbuilder_options_set_loop_vectorization options true;
Llvm_passbuilder.passbuilder_options_set_slp_vectorization options true;
Llvm_passbuilder.passbuilder_options_set_loop_unrolling options true;
Llvm_passbuilder.passbuilder_options_set_forget_all_scev_in_loop_unroll
options true;
Llvm_passbuilder.passbuilder_options_set_licm_mssa_opt_cap options 2;
Llvm_passbuilder.passbuilder_options_set_licm_mssa_no_acc_for_promotion_cap
options 2;
Llvm_passbuilder.passbuilder_options_set_call_graph_profile options true;
Llvm_passbuilder.passbuilder_options_set_merge_functions options true;
Llvm_passbuilder.passbuilder_options_set_inliner_threshold options 2;
match Llvm_passbuilder.run_passes m "no-op-module" machine options with
| Error e ->
prerr_endline e;
assert false
| Ok () -> ()
let () =
Llvm_passbuilder.dispose_passbuilder_options options;
Llvm.dispose_module m
-111
View File
@@ -1,111 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/target.ml
* RUN: %ocamlc -g -w +A -package llvm.target -package llvm.all_backends -linkpkg %t/target.ml -o %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.target -package llvm.all_backends -linkpkg %t/target.ml -o %t/executable
* RUN: %t/executable %t/bitcode.bc
* XFAIL: vg_leak
*)
(* Note: It takes several seconds for ocamlopt to link an executable with
libLLVMCore.a, so it's better to write a big test than a bunch of
little ones. *)
open Llvm
open Llvm_target
let () = Llvm_all_backends.initialize ()
let context = global_context ()
let i32_type = Llvm.i32_type context
let i64_type = Llvm.i64_type context
(* Tiny unit test framework - really just to help find which line is busted *)
let print_checkpoints = false
let _ =
Printexc.record_backtrace true
let assert_equal a b =
if a <> b then failwith "assert_equal"
(*===-- Fixture -----------------------------------------------------------===*)
let filename = Sys.argv.(1)
let m = create_module context filename
let target = Target.by_triple (Target.default_triple ())
let machine = TargetMachine.create (Target.default_triple ()) target
(*===-- Data Layout -------------------------------------------------------===*)
let test_target_data () =
let module DL = DataLayout in
let layout = "e-p:32:32-f64:32:64-v64:32:64-v128:32:128-n32-S32" in
let dl = DL.of_string layout in
let sty = struct_type context [| i32_type; i64_type |] in
assert_equal (DL.as_string dl) layout;
assert_equal (DL.byte_order dl) Endian.Little;
assert_equal (DL.pointer_size dl) 4;
assert_equal (DL.intptr_type context dl) i32_type;
assert_equal (DL.qualified_pointer_size 0 dl) 4;
assert_equal (DL.qualified_intptr_type context 0 dl) i32_type;
assert_equal (DL.size_in_bits sty dl) (Int64.of_int 96);
assert_equal (DL.store_size sty dl) (Int64.of_int 12);
assert_equal (DL.abi_size sty dl) (Int64.of_int 12);
assert_equal (DL.stack_align sty dl) 4;
assert_equal (DL.preferred_align sty dl) 8;
assert_equal (DL.preferred_align_of_global (declare_global sty "g" m) dl) 8;
assert_equal (DL.element_at_offset sty (Int64.of_int 1) dl) 0;
assert_equal (DL.offset_of_element sty 1 dl) (Int64.of_int 4)
(*===-- Target ------------------------------------------------------------===*)
let test_target () =
let module T = Target in
ignore (T.succ target);
ignore (T.name target);
ignore (T.description target);
ignore (T.has_jit target);
ignore (T.has_target_machine target);
ignore (T.has_asm_backend target)
(*===-- Target Machine ----------------------------------------------------===*)
let test_target_machine () =
let module TM = TargetMachine in
assert_equal (TM.target machine) target;
assert_equal (TM.triple machine) (Target.default_triple ());
assert_equal (TM.cpu machine) "";
assert_equal (TM.features machine) "";
ignore (TM.data_layout machine);
TM.set_verbose_asm true machine
(*===-- Code Emission -----------------------------------------------------===*)
let test_code_emission () =
TargetMachine.emit_to_file m CodeGenFileType.ObjectFile filename machine;
try
TargetMachine.emit_to_file m CodeGenFileType.ObjectFile
"/nonexistent/file" machine;
failwith "must raise"
with Llvm_target.Error _ ->
();
let buf = TargetMachine.emit_to_memory_buffer m CodeGenFileType.ObjectFile
machine in
Llvm.MemoryBuffer.dispose buf
(*===-- Driver ------------------------------------------------------------===*)
let _ =
test_target_data ();
test_target ();
test_target_machine ();
test_code_emission ();
dispose_module m
-21
View File
@@ -1,21 +0,0 @@
(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/transform_utils.ml
* RUN: %ocamlc -g -w +A -package llvm.transform_utils -linkpkg %t/transform_utils.ml -o %t/executable
* RUN: %t/executable
* RUN: %ocamlopt -g -w +A -package llvm.transform_utils -linkpkg %t/transform_utils.ml -o %t/executable
* RUN: %t/executable
* XFAIL: vg_leak
*)
open Llvm
open Llvm_transform_utils
let context = global_context ()
let test_clone_module () =
let m = create_module context "mod" in
let m' = clone_module m in
if m == m' then failwith "m == m'";
if string_of_llmodule m <> string_of_llmodule m' then failwith "string_of m <> m'"
let () =
test_clone_module ()
+27 -27
View File
@@ -28,7 +28,7 @@ def get_di_tag():
# Create DIBuilder
with mod.create_dibuilder() as dib:
# Create file
file_md = dib.create_file("metadata.c", ".")
file_md = dib.file("metadata.c", ".")
# Create struct type (DW_TAG_structure_type = 0x13)
struct_md = dib.create_struct_type(
@@ -60,7 +60,7 @@ def di_type_get_name():
# Create DIBuilder
with mod.create_dibuilder() as dib:
# Create file
file_md = dib.create_file("metadata.c", ".")
file_md = dib.file("metadata.c", ".")
# Create struct type with name
name = "TestClass"
@@ -157,23 +157,23 @@ def test_dibuilder():
# Create DIBuilder
with mod.create_dibuilder() as dib:
# Create file
file_md = dib.create_file(filename, ".")
file_md = dib.file(filename, ".")
# Create compile unit
compile_unit = dib.create_compile_unit(
llvm.DWARFSourceLanguageC,
file_md,
"llvm-c-test",
False,
"",
0,
"",
llvm.DWARFEmissionFull,
0,
False,
False,
"/",
"",
compile_unit = dib.compile_unit(
language=llvm.DwarfLanguage.C,
file=file_md,
producer="llvm-c-test",
is_optimized=False,
flags="",
runtime_ver=0,
split_name="",
kind=llvm.DwarfEmissionKind.Full,
dwo_id=0,
split_debug_inlining=False,
debug_info_for_profiling=False,
sys_root="/",
sdk="",
)
# Create module
@@ -265,7 +265,7 @@ def test_dibuilder():
struct_dbg_ptr_ty = dib.create_pointer_type(struct_dbg_ty, 192, 0, 0, "")
# Add to named metadata
mod.add_named_metadata_operand("FooType", struct_dbg_ptr_ty)
mod.named_metadata["FooType"].append(struct_dbg_ptr_ty)
# Create function
i64_type = ctx.types.i64
@@ -289,7 +289,7 @@ def test_dibuilder():
)
# Create debug location for parameters
foo_param_location = ctx.create_debug_location(
foo_param_location = ctx.debug_location(
42, 0, replaceable_function_md, None
)
@@ -354,7 +354,7 @@ def test_dibuilder():
# Create another basic block for variables
foo_var_block = foo_function.append_basic_block("vars")
foo_vars_location = ctx.create_debug_location(
foo_vars_location = ctx.debug_location(
43, 0, function_md, None
)
@@ -404,7 +404,7 @@ def test_dibuilder():
enum_test = dib.create_enumeration_type(
namespace, "EnumTest", file_md, 0, 64, 0, enumerators_test, int64_ty
)
mod.add_named_metadata_operand("EnumTest", enum_test)
mod.named_metadata["EnumTest"].append(enum_test)
# Create UInt128 type and large enumerators
uint128_ty = dib.create_basic_type("UInt128", 128, 0, llvm.DIFlagZero)
@@ -429,7 +429,7 @@ def test_dibuilder():
large_enumerators,
uint128_ty,
)
mod.add_named_metadata_operand("LargeEnumTest", large_enum_test)
mod.named_metadata["LargeEnumTest"].append(large_enum_test)
# Create subrange type with metadata bounds
foo_val3 = i64_type.constant(8)
@@ -442,7 +442,7 @@ def test_dibuilder():
subrange_md_ty = dib.create_subrange_type(
file_md, "foo", 42, file_md, 64, 0, 0, int64_ty, lo, hi, strd, bias
)
mod.add_named_metadata_operand("SubrangeType", subrange_md_ty)
mod.named_metadata["SubrangeType"].append(subrange_md_ty)
# Create set types
set_md_ty1 = dib.create_set_type(
@@ -451,8 +451,8 @@ def test_dibuilder():
set_md_ty2 = dib.create_set_type(
file_md, "subrangeset", file_md, 42, 64, 0, subrange_md_ty
)
mod.add_named_metadata_operand("SetType1", set_md_ty1)
mod.add_named_metadata_operand("SetType2", set_md_ty2)
mod.named_metadata["SetType1"].append(set_md_ty1)
mod.named_metadata["SetType2"].append(set_md_ty2)
# Create dynamic array type
dyn_subscripts = [dib.get_or_create_subrange(0, 10)]
@@ -473,7 +473,7 @@ def test_dibuilder():
rank_expr,
None,
)
mod.add_named_metadata_operand("DynType", dynamic_array_md_ty)
mod.named_metadata["DynType"].append(dynamic_array_md_ty)
# Create forward declaration
struct_p_ty = dib.create_forward_decl(
@@ -485,7 +485,7 @@ def test_dibuilder():
struct_elts_array = [int64_ty, int64_ty, int32_ty]
class_arr = dib.get_or_create_array(struct_elts_array)
dib.replace_arrays([struct_p_ty], [class_arr])
mod.add_named_metadata_operand("ClassType", struct_p_ty)
mod.named_metadata["ClassType"].append(struct_p_ty)
# Build IR instructions
with foo_entry_block.create_builder() as builder:
-5
View File
@@ -104,11 +104,6 @@ def disassemble() -> int:
Returns:
Exit code (0 for success)
"""
# Initialize all targets
llvm.initialize_all_target_infos()
llvm.initialize_all_target_mcs()
llvm.initialize_all_disassemblers()
# Process stdin
tokenize_stdin(handle_line)
+18 -76
View File
@@ -1029,13 +1029,8 @@ class FunCloner:
if src.can_use_fast_math_flags:
dst.fast_math_flags = src.fast_math_flags
# Copy instruction metadata
ctx = self.module.context
all_metadata = src.instruction_get_all_metadata_other_than_debug_loc()
for i in range(len(all_metadata)):
kind = all_metadata.get_kind(i)
md = all_metadata.get_metadata(i)
dst.set_metadata(kind, md, ctx)
# Copy instruction metadata.
src.metadata.copy_to(dst)
check_value_kind(dst, llvm.ValueKind.Instruction)
self.vmap[src] = dst
@@ -1239,30 +1234,11 @@ def declare_symbols(src: llvm.Module, m: llvm.Module) -> None:
cur = next_i
# Declare named metadata
cur_md = src.first_named_metadata
end_md = src.last_named_metadata
if cur_md is None:
if end_md is not None:
raise RuntimeError("Range has an end but no beginning")
else:
while True:
name = cur_md.name
if m.get_named_metadata(name):
raise RuntimeError("Named Metadata Node already cloned")
m.add_named_metadata(name)
next_md = cur_md.next
if next_md is None:
if cur_md != end_md:
raise RuntimeError("")
break
prev_md = next_md.prev
if prev_md != cur_md:
raise RuntimeError("Next.Previous global is not Current")
cur_md = next_md
# Declare named metadata.
for name in src.named_metadata:
if m.named_metadata.get(name) is not None:
raise RuntimeError("Named Metadata Node already cloned")
_ = m.named_metadata[name]
def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
@@ -1284,13 +1260,8 @@ def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
if init:
g.initializer = clone_constant(init, m)
# Copy global metadata
ctx = m.context
all_metadata = cur.global_copy_all_metadata()
for i in range(len(all_metadata)):
kind = all_metadata.get_kind(i)
md = all_metadata.get_metadata(i)
g.set_metadata(kind, md, ctx)
# Copy global metadata.
cur.metadata.copy_to(g)
g.is_global_constant = cur.is_global_constant
g.is_thread_local = cur.is_thread_local
@@ -1335,13 +1306,8 @@ def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
raise RuntimeError("Could not find personality function")
fun.personality_fn = p
# Copy function metadata
ctx = m.context
all_metadata = cur.global_copy_all_metadata()
for i in range(len(all_metadata)):
kind = all_metadata.get_kind(i)
md = all_metadata.get_metadata(i)
fun.set_metadata(kind, md, ctx)
# Copy function metadata.
cur.metadata.copy_to(fun)
# Copy prefix and prologue data
if cur.has_prefix_data:
@@ -1433,37 +1399,13 @@ def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
cur = next_i
# Clone named metadata contents
cur_md = src.first_named_metadata
end_md = src.last_named_metadata
if cur_md is None:
if end_md is not None:
raise RuntimeError("Range has an end but no beginning")
else:
while True:
name = cur_md.name
named_md = m.get_named_metadata(name)
if not named_md:
raise RuntimeError("Named MD Node must have been declared already")
operand_count = src.get_named_metadata_num_operands(name)
operands = src.get_named_metadata_operands(name)
for op in operands:
# Convert value to metadata before adding
md = op.as_metadata()
m.add_named_metadata_operand(name, md)
next_md = cur_md.next
if next_md is None:
if cur_md != end_md:
raise RuntimeError("Last Named MD Node does not match End")
break
prev_md = next_md.prev
if prev_md != cur_md:
raise RuntimeError("Next.Previous Named MD Node is not Current")
cur_md = next_md
# Clone named metadata contents.
for name in src.named_metadata:
named_md = m.named_metadata.get(name)
if named_md is None:
raise RuntimeError("Named MD Node must have been declared already")
for md in src.named_metadata[name]:
named_md.append(md)
def echo() -> int:
+11 -18
View File
@@ -26,7 +26,7 @@ def add_named_metadata_operand():
# First convert value to metadata, then create node
md = val.as_metadata()
md_node = ctx.md_node([md])
mod.add_named_metadata_operand("name", md_node)
mod.named_metadata["name"].append(md_node)
return 0
except Exception as e:
@@ -62,8 +62,7 @@ def set_metadata():
md = val.as_metadata()
md_node = ctx.md_node([md])
kind_id = llvm.get_md_kind_id("kind")
ret_inst.set_metadata(kind_id, md_node, ctx)
ret_inst.metadata["kind"] = md_node
# Delete the instruction
ret_inst.delete_instruction()
@@ -79,18 +78,14 @@ def replace_md_operand():
try:
with llvm.create_context() as ctx:
with ctx.create_module("Mod"):
# Build MDNode("foo"), then replace operand 0 with MDString("bar").
string1_md = ctx.md_string("foo")
node_md = ctx.md_node([string1_md])
value = node_md.as_value(ctx)
# Build MDNode("foo") and inspect operand 0.
string_md = ctx.md_string("foo")
node_md = ctx.md_node([string_md])
string2_md = ctx.md_string("bar")
value.replace_md_node_operand_with(0, string2_md)
# Verify replacement took effect.
operand = value.get_operand(0)
if operand.as_metadata() != string2_md:
raise AssertionError("Metadata operand replacement did not apply")
# The public Python API keeps Metadata as Metadata; MDNode
# children are inspected through Metadata.operands.
if node_md.operands[0] != string_md:
raise AssertionError("Metadata operand access did not apply")
return 0
except Exception as e:
@@ -107,17 +102,15 @@ def is_a_value_as_metadata():
i32 = ctx.types.i32
val = i32.constant(0, False).as_metadata()
md_node = ctx.md_node([val])
md_val = md_node.as_value(ctx)
if not md_val.is_value_as_metadata:
if not md_node.operands[0].is_value:
raise AssertionError("Expected ValueAsMetadata for value-backed MD")
# MDNode built from MDString should NOT be ValueAsMetadata.
string_md = ctx.md_string("foo")
string_node = ctx.md_node([string_md])
string_val = string_node.as_value(ctx)
if string_val.is_value_as_metadata:
if string_node.operands[0].is_value:
raise AssertionError("Expected non-ValueAsMetadata for string-backed MD")
return 0
-4
View File
@@ -9,10 +9,6 @@ import llvm
def targets_list():
"""List all registered LLVM targets."""
# Initialize all target info and targets
llvm.initialize_all_target_infos()
llvm.initialize_all_targets()
# Iterate through targets
target = llvm.get_first_target()
while target is not None:
+2355 -408
View File
File diff suppressed because it is too large Load Diff
@@ -1,300 +0,0 @@
"""
Regression coverage for API surface cleanup helpers.
Covers:
- Module add_* reuse_existing=True defaults and explicit raw insertion escape hatch
- Named struct reuse_existing=True defaults and explicit raw insertion escape hatch
- New member/factory APIs that replace module-level helpers
- Builder parent navigation helpers and Module.create_builder()
"""
import llvm
def expect_raises(fn, message_part: str) -> None:
try:
fn()
except Exception as exc:
assert message_part in str(exc), str(exc)
else:
raise AssertionError("expected exception")
def test_module_add_reuse_existing_semantics() -> None:
with llvm.create_context() as ctx:
i32 = ctx.types.i32
i64 = ctx.types.i64
void = ctx.types.void
fn_ty = ctx.types.function(void, [])
other_fn_ty = ctx.types.function(i32, [])
resolver_ty = ctx.types.function(i32, [])
with ctx.create_module("reuse_existing") as mod:
fn = mod.add_function("f", fn_ty)
assert mod.add_function("f", fn_ty) == fn
assert len([f for f in mod.functions if f.name.startswith("f")]) == 1
expect_raises(lambda: mod.add_function("f", other_fn_ty), "different type")
raw_fn = mod.add_function("f", fn_ty, reuse_existing=False)
assert raw_fn.name != "f"
g = mod.add_global(i32, "g")
assert mod.add_global(i32, "g") == g
expect_raises(lambda: mod.add_global(i64, "g"), "different type")
expect_raises(
lambda: mod.add_global_in_address_space(i32, "g", 1),
"address space",
)
raw_g = mod.add_global(i32, "g", reuse_existing=False)
assert raw_g.name != "g"
expect_raises(lambda: mod.add_function("g", fn_ty), "global variable")
expect_raises(lambda: mod.add_global(i32, "f"), "function")
alias = mod.add_alias(i32, 0, g, "alias")
assert mod.add_alias(i32, 0, g, "alias") == alias
expect_raises(lambda: mod.add_alias(i64, 0, g, "alias"), "different type")
raw_alias = mod.add_alias(i32, 0, g, "alias", reuse_existing=False)
assert raw_alias.name != "alias"
resolver = mod.add_function("resolver", resolver_ty)
other_resolver = mod.add_function("other_resolver", resolver_ty)
ifunc = mod.add_global_ifunc("ifunc", resolver_ty, 0, resolver)
assert mod.add_global_ifunc("ifunc", resolver_ty, 0, resolver) == ifunc
expect_raises(
lambda: mod.add_global_ifunc("ifunc", resolver_ty, 1, resolver),
"address space",
)
expect_raises(
lambda: mod.add_global_ifunc("ifunc", resolver_ty, 0, other_resolver),
"different resolver",
)
raw_ifunc = mod.add_global_ifunc(
"ifunc", resolver_ty, 0, resolver, reuse_existing=False
)
assert raw_ifunc.name != "ifunc"
def test_named_struct_reuse_existing_semantics() -> None:
with llvm.create_context() as ctx:
i32 = ctx.types.i32
i64 = ctx.types.i64
pair = ctx.types.struct("Pair", [i32, i64], packed=False)
assert pair.struct_name == "Pair"
assert not pair.is_opaque_struct
assert ctx.types.struct("Pair", [i32, i64], packed=False) == pair
expect_raises(
lambda: ctx.types.struct("Pair", [i32], packed=False),
"different body",
)
expect_raises(
lambda: ctx.types.struct("Pair", [i32, i64], packed=True),
"different packing",
)
raw_pair = ctx.types.struct(
"Pair", [i32, i64], packed=False, reuse_existing=False
)
assert raw_pair != pair
assert raw_pair.struct_name != "Pair"
forward = ctx.types.opaque_struct("Forward")
assert forward.is_opaque_struct
assert ctx.types.opaque_struct("Forward") == forward
completed = ctx.types.struct("Forward", [i32])
assert completed == forward
assert not completed.is_opaque_struct
raw_forward = ctx.types.opaque_struct("Forward", reuse_existing=False)
assert raw_forward != forward
assert raw_forward.struct_name != "Forward"
def test_member_factories_and_builder_navigation() -> None:
llvm.initialize_all_target_infos()
llvm.initialize_all_targets()
llvm.initialize_all_target_mcs()
llvm.initialize_all_asm_printers()
llvm.initialize_all_asm_parsers()
removed_globals = [
"AttributeFunctionIndex",
"AttributeReturnIndex",
"last_enum_attribute_kind",
"block_address",
"const_data_array",
"const_gep_with_no_wrap_flags",
"const_int_of_arbitrary_precision",
"const_ptr_auth",
"const_string",
"const_struct",
"const_vector",
"create_binary_from_bytes",
"create_binary_from_file",
"create_disasm_cpu_features",
"create_target_data",
"create_target_machine",
"di_file_get_directory",
"di_file_get_filename",
"di_file_get_source",
"di_global_variable_expression_get_expression",
"di_global_variable_expression_get_variable",
"di_location_get_column",
"di_location_get_inlined_at",
"di_location_get_line",
"di_location_get_scope",
"di_scope_get_file",
"di_subprogram_get_line",
"di_subprogram_replace_type",
"di_type_get_name",
"di_variable_get_file",
"di_variable_get_line",
"di_variable_get_scope",
"dibuilder_create_debug_location",
"get_cast_opcode",
"get_debug_metadata_version",
"get_default_target_triple",
"get_first_dbg_record",
"get_host_cpu_features",
"get_host_cpu_name",
"get_inline_asm",
"get_di_node_tag",
"get_last_dbg_record",
"get_last_enum_attribute_kind",
"get_module_debug_metadata_version",
"get_next_dbg_record",
"get_previous_dbg_record",
"get_target_from_name",
"get_target_from_triple",
"intrinsic_get_type",
"lookup_enum_attribute_kind",
"replace_md_node_operand_with",
"run_passes",
"strip_module_debug_info",
]
for name in removed_globals:
assert not hasattr(llvm, name), name
removed_class_methods = {
llvm.Context: [
"get_diagnostics",
"create_enum_attribute",
"create_string_attribute",
"create_type_attribute",
],
llvm.Module: ["get_verification_error"],
llvm.Function: [
"get_personality_fn",
"set_personality_fn",
"get_gc",
"set_gc",
"get_attribute_count",
"get_enum_attribute",
"add_attribute",
"get_attributes",
"get_string_attribute",
"remove_enum_attribute",
"remove_string_attribute",
],
llvm.Attribute: ["kind"],
llvm.AttributeAccessor: ["get_enum", "remove_enum"],
llvm.Value: [
"set_constant",
"set_comdat",
"set_thread_local",
"set_externally_initialized",
"set_volatile",
"set_inst_alignment",
"set_global_ifunc_resolver",
"set_personality_fn",
"set_prefix_data",
"set_prologue_data",
"set_nsw",
"set_nuw",
"set_exact",
"set_nneg",
"set_is_disjoint",
"set_icmp_same_sign",
"set_ordering",
"set_atomic_sync_scope_id",
"set_weak",
"set_tail_call_kind",
"set_called_operand",
"set_cleanup",
"set_fast_math_flags",
"get_callsite_attribute_count",
"get_callsite_enum_attribute",
"add_callsite_attribute",
],
}
for cls, names in removed_class_methods.items():
for name in names:
assert not hasattr(cls, name), f"{cls.__name__}.{name}"
assert isinstance(llvm.default_target_triple, str)
assert isinstance(llvm.host_cpu_name, str)
assert isinstance(llvm.host_cpu_features, str)
assert isinstance(llvm.debug_metadata_version, int)
with llvm.create_context() as ctx:
i32 = ctx.types.i32
i64 = ctx.types.i64
ptr = ctx.types.ptr
fn_ty = ctx.types.function(i32, [])
assert i64.constant([1]).const_zext_value == 1
assert i32.vector_const([i32.constant(1), i32.constant(2)]).is_constant
assert i32.constant(1).get_cast_opcode(False, i64, False) == llvm.Opcode.ZExt
assert ctx.get_intrinsic_type(llvm.lookup_intrinsic_id("llvm.memcpy"), [ptr, ptr, i64]).kind == llvm.TypeKind.Function
asm_ty = ctx.types.function(ctx.types.void, [])
assert asm_ty.inline_asm("nop", "", False, False, llvm.InlineAsmDialect.ATT, False).is_inline_asm
with ctx.create_module("surface") as mod:
fn = mod.add_function("f", fn_ty)
bb = fn.append_basic_block("entry")
assert bb.block_address().is_constant
with mod.create_builder(bb) as builder:
assert builder.function == fn
assert builder.module == mod
assert builder.context is not None
ret = builder.ret(i32.constant(0))
expect_raises(lambda: ret.has_dbg_records, "LLVM-C has no safe")
with mod.create_builder(ret) as before_ret:
assert before_ret.insert_block == bb
assert not hasattr(mod, "get_or_insert_named_metadata")
named_md = mod.add_named_metadata("llvm.nanobind.surface")
assert mod.add_named_metadata("llvm.nanobind.surface") == named_md
assert not hasattr(mod, "get_or_insert_comdat")
comdat = mod.add_comdat("surface_comdat")
comdat.selection_kind = llvm.ComdatSelectionKind.ExactMatch
assert (
mod.add_comdat("surface_comdat").selection_kind
== llvm.ComdatSelectionKind.ExactMatch
)
opts = llvm.PassBuilderOptions()
mod.run_passes("default<O0>", options=opts)
bitcode = mod.write_bitcode_to_memory_buffer()
with llvm.BinaryManager.from_bytes(bitcode) as binary:
assert binary.copy_to_memory_buffer()
target = llvm.Target.from_triple(llvm.default_target_triple)
assert target is not None
tm = llvm.TargetMachine.create(target, llvm.default_target_triple)
td = tm.create_data_layout()
assert str(llvm.TargetData.create(str(td))) == str(td)
dis = llvm.Disasm.create(llvm.default_target_triple)
assert hasattr(dis, "is_valid")
if __name__ == "__main__":
test_module_add_reuse_existing_semantics()
test_named_struct_reuse_existing_semantics()
test_member_factories_and_builder_navigation()
print("test_api_surface_cleanup: PASSED")
+397
View File
@@ -0,0 +1,397 @@
"""
Regression tests for the API UX cleanup helpers.
These tests cover the high-level APIs from devdocs/api-ux-cleanup:
- Builder.intrinsic(...)
- Module.optimize(...)
- Function.optimize(...)
- TargetMachine.host()
- Module.emit_object()/emit_assembly()
- JIT through LLVM-C ORC LLJIT
"""
import ctypes
import llvm
def _simple_i32_module(name: str = "simple"):
ctx_mgr = llvm.create_context()
ctx = ctx_mgr.__enter__()
mod_mgr = ctx.create_module(name)
mod = mod_mgr.__enter__()
i32 = ctx.types.i32
fn = mod.add_function("answer", ctx.types.function(i32, []))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.ret(builder.add(i32.constant(40), i32.constant(2), "sum"))
return ctx_mgr, mod_mgr, ctx, mod
def _close_module(ctx_mgr, mod_mgr):
try:
mod_mgr.__exit__(None, None, None)
finally:
ctx_mgr.__exit__(None, None, None)
def _host_target_machine_or_skip():
try:
return llvm.TargetMachine.host()
except llvm.LLVMError as exc:
print(f"host target unavailable, skipping target-dependent check: {exc}")
return None
def _host_jit_or_skip():
try:
return llvm.JIT.host()
except llvm.LLVMError as exc:
print(f"host JIT unavailable, skipping JIT check: {exc}")
return None
def test_builder_intrinsic_non_overloaded():
with llvm.create_context() as ctx:
with ctx.create_module("intrinsic_trap") as mod:
fn = mod.add_function("f", ctx.types.function(ctx.types.void, []))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.intrinsic("llvm.trap", [])
builder.unreachable()
ir = str(mod)
assert "call void @llvm.trap()" in ir
assert mod.verify(), mod.verification_error
def test_builder_intrinsic_overloaded_float():
with llvm.create_context() as ctx:
with ctx.create_module("intrinsic_sqrt") as mod:
f64 = ctx.types.f64
fn = mod.add_function("mysqrt", ctx.types.function(f64, [f64]))
x = fn.get_param(0)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
y = builder.intrinsic(
"llvm.sqrt", [x], overloaded_types=[f64], name_hint="y"
)
builder.ret(y)
ir = str(mod)
assert "@llvm.sqrt.f64" in ir
assert mod.verify(), mod.verification_error
def test_builder_intrinsic_memcpy():
with llvm.create_context() as ctx:
with ctx.create_module("intrinsic_memcpy") as mod:
void = ctx.types.void
ptr = ctx.types.ptr
i64 = ctx.types.i64
i1 = ctx.types.i1
fn = mod.add_function("copy", ctx.types.function(void, [ptr, ptr, i64]))
dst = fn.get_param(0)
src = fn.get_param(1)
n = fn.get_param(2)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.intrinsic(
"llvm.memcpy",
[dst, src, n, i1.constant(False)],
overloaded_types=[ptr, ptr, i64],
)
builder.ret_void()
ir = str(mod)
assert "@llvm.memcpy" in ir
assert mod.verify(), mod.verification_error
def test_builder_intrinsic_errors_are_clear():
with llvm.create_context() as ctx:
with ctx.create_module("intrinsic_errors") as mod:
f64 = ctx.types.f64
fn = mod.add_function("f", ctx.types.function(f64, [f64]))
x = fn.get_param(0)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
try:
builder.intrinsic("llvm.not_a_real_intrinsic", [])
except llvm.LLVMAssertionError as exc:
assert "Unknown LLVM intrinsic" in str(exc)
else:
raise AssertionError("expected unknown intrinsic error")
try:
builder.intrinsic("llvm.sqrt", [x])
except llvm.LLVMAssertionError as exc:
assert "overloaded_types" in str(exc)
else:
raise AssertionError("expected overloaded_types error")
builder.ret(x)
def test_module_optimize_success_and_failure():
ctx_mgr, mod_mgr, _ctx, mod = _simple_i32_module("optimize_helper")
try:
assert mod.verify(), mod.verification_error
mod.optimize("default<O0>")
assert mod.verify(), mod.verification_error
try:
mod.optimize("not-a-real-pass")
except llvm.LLVMError as exc:
message = str(exc)
assert "not-a-real-pass" in message
assert "Failed to optimize" in message
else:
raise AssertionError("expected invalid pipeline error")
finally:
_close_module(ctx_mgr, mod_mgr)
def test_function_optimize_success_and_failure():
with llvm.create_context() as ctx:
with ctx.create_module("function_optimize_helper") as mod:
i32 = ctx.types.i32
fn = mod.add_function("answer", ctx.types.function(i32, [i32]))
x = fn.get_param(0)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
result = builder.add(x, i32.constant(0), "result")
builder.ret(result)
untouched = mod.add_function("untouched", ctx.types.function(i32, [i32]))
y = untouched.get_param(0)
untouched_entry = untouched.append_basic_block("entry")
with untouched_entry.create_builder() as builder:
result = builder.add(y, i32.constant(0), "result")
builder.ret(result)
decl = mod.add_function("decl", ctx.types.function(i32, []))
assert mod.verify(), mod.verification_error
fn.optimize("instcombine,simplifycfg")
assert mod.verify(), mod.verification_error
ir = str(mod)
optimized_body = ir.split("define i32 @answer", 1)[1].split(
"define i32 @untouched", 1
)[0]
untouched_body = ir.split("define i32 @untouched", 1)[1].split(
"declare i32 @decl", 1
)[0]
assert "ret i32 %0" in optimized_body
assert " add i32 " not in optimized_body
assert " add i32 " in untouched_body
try:
fn.optimize("not-a-real-function-pass")
except llvm.LLVMError as exc:
message = str(exc)
assert "not-a-real-function-pass" in message
assert "Failed to optimize function" in message
else:
raise AssertionError("expected invalid function pipeline error")
try:
decl.optimize("instcombine")
except llvm.LLVMAssertionError as exc:
assert "function definition" in str(exc)
else:
raise AssertionError("expected declaration optimization error")
def test_module_optimize_with_target_machine():
tm = _host_target_machine_or_skip()
if tm is None:
return
ctx_mgr, mod_mgr, _ctx, mod = _simple_i32_module("optimize_with_tm")
try:
mod.optimize("default<O0>", target_machine=tm)
assert mod.verify(), mod.verification_error
finally:
_close_module(ctx_mgr, mod_mgr)
def test_emit_object_and_assembly_convenience():
tm = _host_target_machine_or_skip()
if tm is None:
return
ctx_mgr, mod_mgr, _ctx, mod = _simple_i32_module("emit_helper")
try:
mod.optimize("default<O0>", target_machine=tm)
obj = mod.emit_object(target_machine=tm)
asm = mod.emit_assembly(target_machine=tm)
obj_host = mod.emit_object()
asm_host = mod.emit_assembly()
assert isinstance(obj, bytes) and len(obj) > 0
assert isinstance(asm, bytes) and len(asm) > 0
assert isinstance(obj_host, bytes) and len(obj_host) > 0
assert isinstance(asm_host, bytes) and len(asm_host) > 0
with llvm.BinaryManager.from_bytes(obj) as binary:
assert binary.type in {
llvm.BinaryType.COFF,
llvm.BinaryType.ELF32L,
llvm.BinaryType.ELF32B,
llvm.BinaryType.ELF64L,
llvm.BinaryType.ELF64B,
llvm.BinaryType.MachO32L,
llvm.BinaryType.MachO32B,
llvm.BinaryType.MachO64L,
llvm.BinaryType.MachO64B,
llvm.BinaryType.Wasm,
}
finally:
_close_module(ctx_mgr, mod_mgr)
def test_jit_integer_function_and_module_transfer():
jit = _host_jit_or_skip()
if jit is None:
return
with jit:
with llvm.create_context() as ctx:
with ctx.create_module("jit_add") as mod:
i32 = ctx.types.i32
fn = mod.add_function("add_i32", ctx.types.function(i32, [i32, i32]))
a = fn.get_param(0)
b = fn.get_param(1)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.ret(builder.add(a, b, "sum"))
assert mod.verify(), mod.verification_error
jit.add_module(mod)
try:
_ = mod.name
except llvm.LLVMMemoryError as exc:
assert "disposed" in str(exc)
else:
raise AssertionError("expected module to be invalid after JIT transfer")
address = jit.lookup("add_i32")
assert isinstance(address, int) and address != 0
add_i32 = jit.ctypes_function(
"add_i32", ctypes.c_int32, [ctypes.c_int32, ctypes.c_int32]
)
assert add_i32(2, 3) == 5
def test_jit_missing_symbol_error():
jit = _host_jit_or_skip()
if jit is None:
return
with jit:
try:
jit.lookup("missing_symbol")
except llvm.LLVMError as exc:
assert "missing_symbol" in str(exc)
else:
raise AssertionError("expected missing symbol lookup to fail")
def test_jit_ctypes_function_keeps_jit_alive():
jit = _host_jit_or_skip()
if jit is None:
return
with llvm.create_context() as ctx:
with ctx.create_module("jit_keepalive") as mod:
i32 = ctx.types.i32
fn = mod.add_function("add_i32_keepalive", ctx.types.function(i32, [i32, i32]))
a = fn.get_param(0)
b = fn.get_param(1)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.ret(builder.add(a, b, "sum"))
jit.add_module(mod)
add_i32 = jit.ctypes_function(
"add_i32_keepalive", ctypes.c_int32, [ctypes.c_int32, ctypes.c_int32]
)
del jit
assert add_i32(4, 6) == 10
def test_jit_callback_symbol():
jit = _host_jit_or_skip()
if jit is None:
return
callback_type = ctypes.CFUNCTYPE(ctypes.c_int32, ctypes.c_int32)
@callback_type
def py_inc(x):
return x + 10
with jit:
jit.add_symbol("py_inc", py_inc)
with llvm.create_context() as ctx:
with ctx.create_module("jit_callback") as mod:
i32 = ctx.types.i32
callee = mod.add_function("py_inc", ctx.types.function(i32, [i32]))
fn = mod.add_function("call_py_inc", ctx.types.function(i32, [i32]))
x = fn.get_param(0)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
result = builder.call(callee, [x], "result")
builder.ret(result)
assert mod.verify(), mod.verification_error
jit.add_module(mod)
call_py_inc = jit.ctypes_function(
"call_py_inc", ctypes.c_int32, [ctypes.c_int32]
)
assert call_py_inc(5) == 15
if __name__ == "__main__":
test_builder_intrinsic_non_overloaded()
print("test_builder_intrinsic_non_overloaded: PASSED")
test_builder_intrinsic_overloaded_float()
print("test_builder_intrinsic_overloaded_float: PASSED")
test_builder_intrinsic_memcpy()
print("test_builder_intrinsic_memcpy: PASSED")
test_builder_intrinsic_errors_are_clear()
print("test_builder_intrinsic_errors_are_clear: PASSED")
test_module_optimize_success_and_failure()
print("test_module_optimize_success_and_failure: PASSED")
test_module_optimize_with_target_machine()
print("test_module_optimize_with_target_machine: PASSED")
test_emit_object_and_assembly_convenience()
print("test_emit_object_and_assembly_convenience: PASSED")
test_jit_integer_function_and_module_transfer()
print("test_jit_integer_function_and_module_transfer: PASSED")
test_jit_missing_symbol_error()
print("test_jit_missing_symbol_error: PASSED")
test_jit_ctypes_function_keeps_jit_alive()
print("test_jit_ctypes_function_keeps_jit_alive: PASSED")
test_jit_callback_symbol()
print("test_jit_callback_symbol: PASSED")
@@ -74,6 +74,59 @@ def test_attribute_factories_and_function_accessors():
assert mod.verify(), mod.verification_error
def test_memory_attribute_helpers():
with llvm.create_context() as ctx:
with ctx.create_module("memory_attrs") as mod:
void = ctx.types.void
fn_ty = ctx.types.function(void, [])
fn = mod.add_function("f", fn_ty)
memory_none = llvm.Attribute.memory(ctx, "none")
assert memory_none.value == 0
fn.attributes.add(memory_none)
memory_attr = fn.attributes.get("memory")
assert memory_attr is not None
assert memory_attr.value == 0
assert "memory(none)" in str(mod)
fn.attributes.remove("memory")
fn.attributes.add_memory("read")
memory_attr = fn.attributes.get("memory")
assert memory_attr is not None
assert memory_attr.value == llvm.Attribute.memory(ctx, "read").value
assert "memory(read)" in str(mod)
fn.attributes.remove("memory")
fn.attributes.add_memory("argmem: read")
memory_attr = fn.attributes.get("memory")
assert memory_attr is not None
assert memory_attr.value == 1
assert "memory(argmem: read)" in str(mod)
fn.attributes.remove("memory")
complex_effects = (
"memory(argmem: read, inaccessiblemem: write, other: readwrite)"
)
fn.attributes.add_memory(complex_effects)
memory_attr = fn.attributes.get("memory")
assert memory_attr is not None
assert (
memory_attr.value == llvm.Attribute.memory(ctx, complex_effects).value
)
ir = str(mod)
assert "argmem: read" in ir
assert "inaccessiblemem: write" in ir
try:
fn.attributes.add_memory("argmem: frobnicate")
except llvm.LLVMAssertionError as exc:
assert "Unknown memory access effect" in str(exc)
else:
raise AssertionError("expected invalid memory access error")
assert mod.verify(), mod.verification_error
def test_callsite_attribute_accessors():
with llvm.create_context() as ctx:
with ctx.create_module("callsite_attr_accessors") as mod:
@@ -123,6 +176,9 @@ if __name__ == "__main__":
test_attribute_factories_and_function_accessors()
print("test_attribute_factories_and_function_accessors: PASSED")
test_memory_attribute_helpers()
print("test_memory_attribute_helpers: PASSED")
test_callsite_attribute_accessors()
print("test_callsite_attribute_accessors: PASSED")
@@ -5,6 +5,7 @@ Accessing properties on end iterators used to hard-abort in LLVM APIs.
Bindings should raise LLVMAssertionError instead.
"""
import gc
from pathlib import Path
import llvm
@@ -61,6 +62,23 @@ def test_section_symbol_and_relocation_end_guards() -> None:
)
def test_relocation_iterator_keeps_temporary_section_alive() -> None:
if not OBJECT_PATH.exists():
print(f"SKIP: object file not found: {OBJECT_PATH.resolve()}")
return
with llvm.BinaryManager.from_file(OBJECT_PATH) as binary:
probe = binary.sections
if probe.is_at_end():
print("SKIP: object file has no sections")
return
reloc = binary.sections.relocations
gc.collect()
assert isinstance(reloc.is_at_end(), bool)
def test_contains_symbol_and_move_to_containing_section_require_non_end_symbol() -> None:
if not OBJECT_PATH.exists():
print(f"SKIP: object file not found: {OBJECT_PATH.resolve()}")
@@ -85,5 +103,6 @@ def test_contains_symbol_and_move_to_containing_section_require_non_end_symbol()
if __name__ == "__main__":
test_section_symbol_and_relocation_end_guards()
test_relocation_iterator_keeps_temporary_section_alive()
test_contains_symbol_and_move_to_containing_section_require_non_end_symbol()
print("test_binary_iterator_end_guards: PASSED")
@@ -29,6 +29,22 @@ def _manual_symbol_names(binary: llvm.Binary) -> list[str]:
return names
def _assert_exhausted_next_is_stable(iterator) -> None:
while True:
try:
next(iterator)
except StopIteration:
break
for _ in range(2):
try:
next(iterator)
except StopIteration:
pass
else:
raise AssertionError("expected exhausted iterator to stay exhausted")
def test_binary_iterators_match_manual_iteration():
obj = Path("llvm-c/llvm-c-test/inputs/simple.o")
if not obj.exists():
@@ -52,6 +68,23 @@ def test_binary_iterators_match_manual_iteration():
)
def test_binary_iterators_stay_stopped_after_exhaustion():
obj = Path("llvm-c/llvm-c-test/inputs/simple.o")
if not obj.exists():
print(f"SKIP: object file not found: {obj.resolve()}")
return
with llvm.BinaryManager.from_file(obj) as binary:
_assert_exhausted_next_is_stable(iter(binary.sections))
_assert_exhausted_next_is_stable(iter(binary.symbols))
sections = binary.sections
if not sections.is_at_end():
_assert_exhausted_next_is_stable(iter(sections.relocations))
if __name__ == "__main__":
test_binary_iterators_match_manual_iteration()
print("test_binary_iterators_match_manual_iteration: PASSED")
test_binary_iterators_stay_stopped_after_exhaustion()
print("test_binary_iterators_stay_stopped_after_exhaustion: PASSED")
@@ -4,8 +4,8 @@ Full-tree regression matrix for remaining wrapper guard paths.
This complements value/type/non-value matrices with:
- helper API assertions (phi/switch/indirectbr, call builders, vector_const)
- manager state-machine guards (Context/Module/Builder/DIBuilder/Binary)
- wrapper lifetime guards (TypeFactory/Attribute/Comdat/NamedMDNode/Use/
ValueMetadataEntries)
- wrapper lifetime guards (TypeFactory/Attribute/Comdat/NamedMetadataList/Use/
MetadataMap)
- attribute/operand-bundle/indexed metadata guards
- disassembler invalid-context guards
"""
@@ -166,15 +166,16 @@ def test_attribute_operand_bundle_and_entries_guards():
add_inst = b.add(fn.get_param(0), i32.constant(1, False), "sum")
b.ret(add_inst)
md = ctx.md_string("meta")
kind = ctx.get_md_kind_id("llvm_nanobind.meta")
add_inst.set_metadata(kind, md, ctx)
entries = add_inst.instruction_get_all_metadata_other_than_debug_loc()
assert len(entries) >= 1
_ = entries.get_kind(0)
_ = entries.get_metadata(0)
assert_out_of_range(lambda: entries.get_kind(len(entries)))
assert_out_of_range(lambda: entries.get_metadata(len(entries)))
md = ctx.md_node([ctx.md_string("meta")])
add_inst.metadata["llvm_nanobind.meta"] = md
assert "llvm_nanobind.meta" in add_inst.metadata
assert add_inst.metadata["llvm_nanobind.meta"] == md
assert add_inst.metadata.get("missing.kind") is None
try:
_ = add_inst.metadata["missing.kind"]
assert False, "Expected KeyError for missing metadata"
except KeyError:
pass
def test_manager_state_machine_guards():
@@ -223,7 +224,7 @@ def test_manager_state_machine_guards():
dm2 = mod.create_dibuilder()
dib = dm2.__enter__()
_ = dib.create_file("x.c", ".")
_ = dib.file("x.c", ".")
assert_memory_error(
lambda: dm2.dispose(),
"cannot call dispose() after __enter__",
@@ -283,7 +284,7 @@ def test_lifetime_guards_for_remaining_wrappers():
escaped["types"] = ctx.types
escaped["attr"] = llvm.Attribute.string(ctx, "k", "v")
escaped["comdat"] = mod.add_comdat("C")
escaped["named_md"] = mod.add_named_metadata("llvm.nanobind.named")
escaped["named_md"] = mod.named_metadata["llvm.nanobind.named"]
fn_ty = ctx.types.function(i32, [i32], False)
fn = mod.add_function("f", fn_ty)
@@ -296,20 +297,19 @@ def test_lifetime_guards_for_remaining_wrappers():
assert len(uses) >= 1
escaped["use"] = uses[0]
md = ctx.md_string("meta")
kind = ctx.get_md_kind_id("llvm_nanobind.lifetime")
add_inst.set_metadata(kind, md, ctx)
escaped["entries"] = add_inst.instruction_get_all_metadata_other_than_debug_loc()
assert len(escaped["entries"]) >= 1
md = ctx.md_node([ctx.md_string("meta")])
add_inst.metadata["llvm_nanobind.lifetime"] = md
escaped["metadata"] = add_inst.metadata
assert escaped["metadata"].get("llvm_nanobind.lifetime") == md
assert_memory_error(lambda: escaped["types"].i32, "typefactory used after context")
assert_memory_error(lambda: escaped["attr"].string_kind, "attribute used after context")
assert_memory_error(lambda: escaped["comdat"].selection_kind, "comdat used after module")
assert_memory_error(lambda: escaped["named_md"].name, "namedmdnode used after context")
assert_memory_error(lambda: len(escaped["named_md"]), "named metadata view used after context")
assert_memory_error(lambda: escaped["use"].user, "use used after context")
assert_memory_error(
lambda: escaped["entries"].get_kind(0),
"valuemetadataentries used after context",
lambda: escaped["metadata"].get("llvm_nanobind.lifetime"),
"metadata view used after context",
)
@@ -0,0 +1,65 @@
"""Regression coverage for GitHub issues 11, 12, and 13."""
import llvm
def test_issue_11_types_aliases_share_context_type_factory() -> None:
with llvm.create_context() as ctx:
with ctx.create_module("types_aliases") as mod:
i32 = ctx.types.i32
fn = mod.add_function("f", ctx.types.function(i32, [i32]))
bb = fn.append_basic_block("entry")
with bb.create_builder() as b:
value = b.add(fn.get_param(0), i32.constant(1), "value")
b.ret(value)
assert ctx.types == mod.types
assert ctx.types == fn.types
assert ctx.types == bb.types
assert ctx.types == value.types
assert mod.types.i64 == ctx.types.i64
assert fn.types.function(i32, [i32]) == ctx.types.function(i32, [i32])
def test_issue_12_function_create_builder_creates_entry_block() -> None:
with llvm.create_context() as ctx:
with ctx.create_module("function_create_builder") as mod:
i64 = mod.types.i64
fn = mod.add_function("lift2", mod.types.function(i64, [i64, i64]))
with fn.create_builder() as ir:
ir.ret(i64.constant(0))
assert fn.basic_block_count == 1
assert fn.entry_block.name == "entry"
assert mod.verify(), mod.verification_error
assert "define i64 @lift2" in str(mod)
assert "ret i64 0" in str(mod)
def test_issue_13_alloca_count_overload_replaces_array_alloca() -> None:
with llvm.create_context() as ctx:
with ctx.create_module("alloca_count_overload") as mod:
void = mod.types.void
i32 = mod.types.i32
fn = mod.add_function("f", mod.types.function(void, []))
with fn.create_builder() as b:
scalar = b.alloca(i32, "scalar")
array = b.alloca(i32, i32.constant(4), "array")
b.ret_void()
assert not hasattr(llvm.Builder, "array_alloca")
assert not scalar.is_array_allocation
assert array.is_array_allocation
ir = str(mod)
assert "%scalar = alloca i32" in ir
assert "%array = alloca i32, i32 4" in ir
assert mod.verify(), mod.verification_error
if __name__ == "__main__":
test_issue_11_types_aliases_share_context_type_factory()
test_issue_12_function_create_builder_creates_entry_block()
test_issue_13_alloca_count_overload_replaces_array_alloca()
print("test_github_issues_11_12_13: PASSED")
@@ -0,0 +1,168 @@
"""
Regression tests for module-owned wrapper lifetime guards.
Root cause: many wrappers kept only an LLVM context token while holding raw
references to values, blocks, uses, operand bundles, metadata views, or borrowed
modules owned by an LLVMModuleRef. Disposing the module while the context stayed
alive left those raw references dangling. Each wrapper must reject use through a
Python LLVMMemoryError before calling LLVM-C on freed module-owned storage.
"""
from pathlib import Path
import llvm
def expect_memory_error(action, expected: str):
try:
action()
except llvm.LLVMMemoryError as exc:
assert expected.lower() in str(exc).lower(), str(exc)
else:
raise AssertionError("expected LLVMMemoryError")
def expect_assertion(action, expected: str):
try:
action()
except llvm.LLVMAssertionError as exc:
assert expected.lower() in str(exc).lower(), str(exc)
else:
raise AssertionError("expected LLVMAssertionError")
def test_module_owned_wrappers_reject_use_after_module_disposed():
ctx_mgr = llvm.create_context()
ctx = ctx_mgr.__enter__()
try:
mod_mgr = ctx.create_module("module_lifetimes")
mod = mod_mgr.__enter__()
i32 = ctx.types.i32
fn = mod.add_function("f", ctx.types.function(i32, [i32]))
borrowed_mod = fn.module
param = fn.get_param(0)
attr_view = fn.attributes
bb = fn.append_basic_block("entry")
bb_value = bb.as_value()
builder_manager = bb.create_builder()
operand_bundle = llvm.create_operand_bundle("deopt", [param], ctx)
with bb.create_builder() as builder:
inst = builder.add(param, i32.constant(1), "sum")
builder.ret(inst)
uses = param.uses
assert uses
use = uses[0]
metadata_view = inst.metadata
value_metadata = param.as_metadata()
with mod.create_dibuilder() as dib:
di_file = dib.file("x.c", ".")
mod_mgr.__exit__(None, None, None)
expect_memory_error(lambda: fn.name, "module was disposed")
expect_memory_error(lambda: param.name, "module was disposed")
expect_memory_error(lambda: bb.name, "module was disposed")
expect_memory_error(lambda: bb_value.name, "module was disposed")
expect_memory_error(lambda: inst.name, "module was disposed")
expect_memory_error(lambda: use.user, "module was disposed")
expect_memory_error(lambda: attr_view.get("noreturn"), "module was disposed")
expect_memory_error(lambda: borrowed_mod.name, "module has been disposed")
expect_memory_error(lambda: builder_manager.__enter__(), "module has been disposed")
expect_memory_error(lambda: operand_bundle.num_args, "module was disposed")
expect_memory_error(lambda: metadata_view.get("dbg"), "module was disposed")
expect_memory_error(lambda: value_metadata.kind, "module was disposed")
expect_memory_error(lambda: di_file.kind, "module was disposed")
finally:
ctx_mgr.__exit__(None, None, None)
def test_context_manager_results_reject_use_after_exit():
ctx_mgr = llvm.create_context()
ctx = ctx_mgr.__enter__()
ctx_mgr.__exit__(None, None, None)
expect_memory_error(lambda: ctx.types.i32, "context")
with llvm.create_context() as live_ctx:
mod_mgr = live_ctx.create_module("escaped_module")
mod = mod_mgr.__enter__()
mod_mgr.__exit__(None, None, None)
expect_memory_error(lambda: mod.name, "module")
mod_mgr = live_ctx.create_module("escaped_builder")
mod = mod_mgr.__enter__()
fn = mod.add_function("f", live_ctx.types.function(live_ctx.types.void, []))
bb = fn.append_basic_block("entry")
builder_mgr = bb.create_builder()
builder = builder_mgr.__enter__()
builder_mgr.__exit__(None, None, None)
expect_memory_error(lambda: builder.ret_void(), "builder")
dib_mgr = mod.create_dibuilder()
dib = dib_mgr.__enter__()
dib_mgr.__exit__(None, None, None)
expect_memory_error(lambda: dib.file("x.c", "."), "dibuilder")
mod_mgr.__exit__(None, None, None)
def test_binary_manager_result_rejects_use_after_exit():
bitcode_path = Path(__file__).parent / "factorial.bc"
binary_mgr = llvm.BinaryManager.from_bytes(bitcode_path.read_bytes())
binary = binary_mgr.__enter__()
binary_mgr.__exit__(None, None, None)
expect_memory_error(lambda: binary.type, "binary")
def test_builder_debug_location_rejects_cross_context_metadata():
with llvm.create_context() as ctx1:
with llvm.create_context() as ctx2:
with ctx1.create_module("debug_location_context_mismatch") as mod:
fn = mod.add_function("f", ctx1.types.function(ctx1.types.void, []))
bb = fn.append_basic_block("entry")
local_scope = ctx1.md_node([])
foreign_scope = ctx2.md_node([])
foreign_inlined_at = ctx2.md_node([])
with bb.create_builder() as builder:
expect_assertion(
lambda: builder.debug_location(
line=1, column=1, scope=foreign_scope
),
"same context",
)
expect_assertion(
lambda: builder.debug_location(
line=1,
column=1,
scope=local_scope,
inlined_at=foreign_inlined_at,
),
"same context",
)
expect_assertion(
lambda: builder.debug_location(foreign_scope),
"same context",
)
expect_assertion(
lambda: ctx1.debug_location(
line=1, column=1, scope=foreign_scope
),
"same context",
)
builder.ret_void()
if __name__ == "__main__":
test_module_owned_wrappers_reject_use_after_module_disposed()
print("test_module_owned_wrappers_reject_use_after_module_disposed: PASSED")
test_context_manager_results_reject_use_after_exit()
print("test_context_manager_results_reject_use_after_exit: PASSED")
test_binary_manager_result_rejects_use_after_exit()
print("test_binary_manager_result_rejects_use_after_exit: PASSED")
test_builder_debug_location_rejects_cross_context_metadata()
print("test_builder_debug_location_rejects_cross_context_metadata: PASSED")
@@ -0,0 +1,351 @@
"""
Regression tests for the Pythonic metadata/debug-info API.
These cover:
- Value.metadata mapping view
- Module.named_metadata mapping/list view
- Module.module_flags view
- Builder.debug_location(...) context manager
- DIBuilder convenience recipes for files, compile units, functions, and locals
"""
import llvm
def test_value_metadata_mapping_set_get_delete():
with llvm.create_context() as ctx:
with ctx.create_module("metadata_mapping") as mod:
i32 = ctx.types.i32
fn = mod.add_function("f", ctx.types.function(i32, [i32]))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
inst = builder.add(fn.get_param(0), i32.constant(1), "sum")
marker = ctx.md_node([ctx.md_string("example.metadata")])
inst.metadata["example.kind"] = marker
assert "example.kind" in inst.metadata
assert inst.metadata.get("example.kind") == marker
assert inst.metadata["example.kind"] == marker
try:
_ = inst.metadata["missing.kind"]
except KeyError:
pass
else:
raise AssertionError("expected KeyError for missing metadata")
builder.ret(inst)
ir_with_metadata = str(mod)
assert "!example.kind" in ir_with_metadata
del inst.metadata["example.kind"]
assert inst.metadata.get("example.kind") is None
assert "!example.kind" not in str(mod)
assert mod.verify(), mod.verification_error
def test_named_metadata_view_append_iterate_and_get():
with llvm.create_context() as ctx:
with ctx.create_module("named_metadata_view") as mod:
node = ctx.md_node([ctx.md_string("named")])
assert "custom.named" not in mod.named_metadata
assert mod.named_metadata.get("custom.named") is None
mod.named_metadata["custom.named"].append(node)
assert "custom.named" in mod.named_metadata
named = mod.named_metadata["custom.named"]
assert len(named) == 1
assert named[0] == node
assert list(named) == [node]
assert mod.named_metadata.get("custom.named") is not None
assert "custom.named" in mod.named_metadata.keys()
assert "custom.named" in list(mod.named_metadata)
assert "!custom.named = !{" in str(mod)
def test_md_string_value_round_trips_to_python_string():
with llvm.create_context() as ctx:
md_string = ctx.md_string("hello metadata")
assert isinstance(md_string.kind, int)
assert md_string.is_string
assert not md_string.is_node
assert not md_string.is_value
assert md_string.string == "hello metadata"
md_node = ctx.md_node([md_string])
assert isinstance(md_node.kind, int)
assert not md_node.is_string
assert md_node.is_node
assert not md_node.is_value
assert len(md_node.operands) == 1
assert md_node.operands[0].string == "hello metadata"
try:
_ = md_node.string
except llvm.LLVMAssertionError as exc:
assert "not an MDString" in str(exc)
else:
raise AssertionError("expected non-MDString string to fail")
try:
_ = md_string.operands
except llvm.LLVMAssertionError as exc:
assert "not an MDNode" in str(exc)
else:
raise AssertionError("expected non-MDNode operands to fail")
value_md = ctx.types.i32.constant(7).as_metadata()
assert value_md.is_value
try:
_ = value_md.value
except NotImplementedError as exc:
assert "ValueAsMetadata" in str(exc)
else:
raise AssertionError("expected Metadata.value to be unimplemented")
def test_metadata_attachment_rejects_cross_context_metadata():
with llvm.create_context() as ctx1:
with llvm.create_context() as ctx2:
with ctx1.create_module("metadata_context_mismatch") as mod:
i32 = ctx1.types.i32
fn = mod.add_function("f", ctx1.types.function(i32, [i32]))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
inst = builder.add(fn.get_param(0), i32.constant(1), "sum")
foreign = ctx2.md_node([ctx2.md_string("foreign")])
try:
inst.metadata["example.foreign"] = foreign
except llvm.LLVMAssertionError as exc:
assert "same context" in str(exc)
else:
raise AssertionError("expected cross-context metadata error")
builder.ret(inst)
assert "example.foreign" not in str(mod)
assert mod.verify(), mod.verification_error
def test_metadata_map_rejects_use_after_module_disposed():
ctx_mgr = llvm.create_context()
ctx = ctx_mgr.__enter__()
try:
mod_mgr = ctx.create_module("metadata_map_lifetime")
mod = mod_mgr.__enter__()
fn = mod.add_function("f", ctx.types.function(ctx.types.void, []))
metadata_view = fn.metadata
mod_mgr.__exit__(None, None, None)
try:
metadata_view.get("example.kind")
except llvm.LLVMMemoryError as exc:
assert "module was disposed" in str(exc)
else:
raise AssertionError("expected disposed-module metadata view error")
finally:
ctx_mgr.__exit__(None, None, None)
def test_metadata_copy_to_replaces_raw_kind_id_copying():
with llvm.create_context() as ctx:
with ctx.create_module("metadata_copy") as mod:
i32 = ctx.types.i32
fn = mod.add_function("f", ctx.types.function(i32, [i32]))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
first = builder.add(fn.get_param(0), i32.constant(1), "first")
second = builder.add(first, i32.constant(2), "second")
marker = ctx.md_node([ctx.md_string("copied")])
first.metadata["example.copy"] = marker
first.metadata.copy_to(second)
builder.ret(second)
assert second.metadata.get("example.copy") == marker
assert str(mod).count("!example.copy") == 2
assert mod.verify(), mod.verification_error
def test_metadata_mapping_works_on_detached_instruction():
with llvm.create_context() as ctx:
with ctx.create_module("detached_metadata") as mod:
fn = mod.add_function("f", ctx.types.function(ctx.types.void, []))
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
ret = builder.ret_void()
ret.remove_from_parent()
marker = ctx.md_node([ctx.md_string("detached")])
ret.metadata["example.detached"] = marker
assert ret.metadata["example.detached"] == marker
ret.delete_instruction()
def test_module_flags_view_add_get_and_iterate_keys():
with llvm.create_context() as ctx:
with ctx.create_module("module_flags_view") as mod:
version = ctx.types.i32.constant(3).as_metadata()
mod.module_flags.add(
"Debug Info Version", llvm.ModuleFlagBehavior.Warning, version
)
assert "Debug Info Version" in mod.module_flags
assert mod.module_flags.get("Debug Info Version") == version
assert mod.module_flags["Debug Info Version"] == version
assert "Debug Info Version" in mod.module_flags.keys()
assert "Debug Info Version" in list(mod.module_flags)
assert "!llvm.module.flags" in str(mod)
def test_debug_location_manager_rejects_disposed_builder():
with llvm.create_context() as ctx:
with ctx.create_module("debug_location_lifetime") as mod:
i32 = ctx.types.i32
fn = mod.add_function("f", ctx.types.function(i32, []))
entry = fn.append_basic_block("entry")
loc = ctx.md_node([])
with entry.create_builder() as builder:
manager = builder.debug_location(loc)
builder.ret(i32.constant(0))
try:
manager.__enter__()
except llvm.LLVMMemoryError as exc:
assert "builder was disposed" in str(exc)
else:
raise AssertionError("expected disposed-builder debug location error")
def test_debug_location_context_manager_and_dibuilder_recipes():
with llvm.create_context() as ctx:
with ctx.create_module("debug_location_context") as mod:
mod.is_new_dbg_info_format = True
i32 = ctx.types.i32
fn = mod.add_function("add", ctx.types.function(i32, [i32, i32]))
with mod.create_dibuilder() as dib:
file = dib.file("main.c", ".")
compile_unit = dib.compile_unit(
language=llvm.DwarfLanguage.C,
file=file,
producer="llvm-nanobind-test",
)
subprogram = dib.function(
fn,
name="add",
file=file,
line=1,
return_type=i32,
param_types=[i32, i32],
)
local = dib.local_variable(subprogram, "tmp", file, 2, i32)
assert compile_unit is not None
assert subprogram is not None
assert local is not None
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
with builder.debug_location(line=2, column=5, scope=subprogram):
sum_inst = builder.add(fn.get_param(0), fn.get_param(1), "sum")
loc = ctx.debug_location(line=3, column=7, scope=subprogram)
with builder.debug_location(loc):
result = builder.add(sum_inst, i32.constant(0), "result")
builder.ret(result)
dib.finalize()
ir = str(mod)
assert "define i32 @add" in ir
assert "!dbg" in ir
assert "line: 2, column: 5" in ir
assert "line: 3, column: 7" in ir
assert "!llvm.dbg.cu" in ir
assert mod.verify(), mod.verification_error
def test_redundant_low_level_metadata_apis_are_removed():
assert not hasattr(llvm, "get_md_kind_id")
assert not hasattr(llvm, "ValueMetadataEntries")
assert not hasattr(llvm, "NamedMDNode")
assert not hasattr(llvm.Context, "get_md_kind_id")
assert not hasattr(llvm.Context, "create_debug_location")
assert not hasattr(llvm.Value, "set_metadata")
assert not hasattr(llvm.Value, "global_copy_all_metadata")
assert not hasattr(llvm.Value, "instruction_get_all_metadata_other_than_debug_loc")
assert not hasattr(llvm.Metadata, "as_value")
assert not hasattr(llvm.Metadata, "string_value")
assert not hasattr(llvm.Metadata, "__len__")
assert not hasattr(llvm.Metadata, "__getitem__")
assert not hasattr(llvm.Metadata, "__iter__")
assert not hasattr(llvm.Module, "first_named_metadata")
assert not hasattr(llvm.Module, "last_named_metadata")
assert not hasattr(llvm.Module, "get_named_metadata")
assert not hasattr(llvm.Module, "add_named_metadata")
assert not hasattr(llvm.Module, "get_named_metadata_num_operands")
assert not hasattr(llvm.Module, "get_named_metadata_operands")
assert not hasattr(llvm.Module, "add_named_metadata_operand")
assert not hasattr(llvm.Module, "add_module_flag")
assert not hasattr(llvm.Module, "get_module_flag")
assert not hasattr(llvm.DIBuilder, "create_file")
assert not hasattr(llvm.DIBuilder, "create_compile_unit")
# Advanced DIBuilder methods remain public because the convenience recipes do
# not cover custom debug types, manual subroutine types, or record insertion.
assert hasattr(llvm.DIBuilder, "create_function")
assert hasattr(llvm.DIBuilder, "create_auto_variable")
assert hasattr(llvm.DIBuilder, "create_struct_type")
if __name__ == "__main__":
test_value_metadata_mapping_set_get_delete()
print("test_value_metadata_mapping_set_get_delete: PASSED")
test_named_metadata_view_append_iterate_and_get()
print("test_named_metadata_view_append_iterate_and_get: PASSED")
test_md_string_value_round_trips_to_python_string()
print("test_md_string_value_round_trips_to_python_string: PASSED")
test_metadata_attachment_rejects_cross_context_metadata()
print("test_metadata_attachment_rejects_cross_context_metadata: PASSED")
test_metadata_map_rejects_use_after_module_disposed()
print("test_metadata_map_rejects_use_after_module_disposed: PASSED")
test_metadata_copy_to_replaces_raw_kind_id_copying()
print("test_metadata_copy_to_replaces_raw_kind_id_copying: PASSED")
test_metadata_mapping_works_on_detached_instruction()
print("test_metadata_mapping_works_on_detached_instruction: PASSED")
test_module_flags_view_add_get_and_iterate_keys()
print("test_module_flags_view_add_get_and_iterate_keys: PASSED")
test_debug_location_manager_rejects_disposed_builder()
print("test_debug_location_manager_rejects_disposed_builder: PASSED")
test_debug_location_context_manager_and_dibuilder_recipes()
print("test_debug_location_context_manager_and_dibuilder_recipes: PASSED")
test_redundant_low_level_metadata_apis_are_removed()
print("test_redundant_low_level_metadata_apis_are_removed: PASSED")
@@ -28,12 +28,6 @@ FIXTURES_DIR = Path("tests/golden/ollvm_obf")
@lru_cache(maxsize=1)
def target_machine() -> llvm.TargetMachine:
llvm.initialize_all_target_infos()
llvm.initialize_all_targets()
llvm.initialize_all_target_mcs()
llvm.initialize_all_asm_printers()
llvm.initialize_all_asm_parsers()
triple = llvm.default_target_triple
target = llvm.Target.from_triple(triple)
assert target is not None
@@ -12,7 +12,7 @@ def test_alloca_array_size_and_is_array_allocation() -> None:
with entry.create_builder() as b:
scalar = b.alloca(i32, "scalar")
array = b.array_alloca(i32, i32.constant(4, False), "array")
array = b.alloca(i32, i32.constant(4, False), "array")
b.ret_void()
assert scalar.allocated_type == i32
+2 -12
View File
@@ -423,11 +423,6 @@ def test_value_accessor_guard_matrix_negative():
lambda: setattr(bad, "unnamed_address", g0.unnamed_address),
"global value",
),
(
"global_copy_all_metadata",
lambda: bad.global_copy_all_metadata(),
"global value",
),
(
"has_personality_fn",
lambda: bad.has_personality_fn,
@@ -457,11 +452,6 @@ def test_value_accessor_guard_matrix_negative():
lambda: setattr(bad, "prologue_data", ctx.types.ptr.null()),
"function value",
),
(
"instruction_get_all_metadata_other_than_debug_loc",
lambda: bad.instruction_get_all_metadata_other_than_debug_loc(),
"instruction value",
),
("opcode", lambda: bad.opcode, "instruction value"),
("opcode_name", lambda: bad.opcode_name, "instruction value"),
("next_instruction", lambda: bad.next_instruction, "instruction value"),
@@ -905,7 +895,7 @@ def test_value_accessor_guard_matrix_positive():
assert g0.global_value_type.kind == llvm.TypeKind.Integer
ua = g0.unnamed_address
g0.unnamed_address = ua
assert len(g0.global_copy_all_metadata()) >= 0
g0.metadata.copy_to(g0)
# Function value.
assert not f.has_personality_fn
@@ -920,7 +910,7 @@ def test_value_accessor_guard_matrix_positive():
assert f.prologue_data is not None
# Instruction value.
assert len(add_inst.instruction_get_all_metadata_other_than_debug_loc()) >= 0
add_inst.metadata.copy_to(add_inst)
assert add_inst.opcode == llvm.Opcode.Add
assert add_inst.opcode_name == "add"
next_inst = add_inst.next_instruction
+1 -1
View File
@@ -39,7 +39,7 @@ def main():
# Array alloca (dynamic size)
array_size = i32.constant(5)
array_alloca = builder.array_alloca(i32, array_size, "dynamic_array")
array_alloca = builder.alloca(i32, array_size, "dynamic_array")
# Static array alloca
static_array = builder.alloca(arr_ty, "static_array")
+80
View File
@@ -88,3 +88,83 @@ def test_transform_replace_add_leaves_no_wrap_adds_alone() -> None:
"""
)
assert "%sum = add nsw i32 %x, %y" in output
def test_intrinsic_memcpy_example() -> None:
output = run_example("examples/intrinsic_memcpy.py")
assert "define void @copy_bytes" in output
assert "call void @llvm.memcpy.p0.p0.i64" in output
def test_optimize_module_example() -> None:
output = run_example("examples/optimize_module.py")
assert "define i32 @add_then_simplify" in output
assert "ret i32 %x" in output
assert "alloca" not in output
def test_optimize_function_example() -> None:
output = run_example("examples/optimize_function.py")
assert "define i32 @optimize_me" in output
assert "define i32 @leave_me_alone" in output
assert "ret i32 %x" in output
assert "%result = add i32 %x, 0" in output
optimized = output.split("define i32 @optimize_me", 1)[1].split(
"define i32 @leave_me_alone", 1
)[0]
assert "alloca" not in optimized
assert " add i32 " not in optimized
def test_emit_object_assembly_example() -> None:
output = run_example("examples/emit_object_assembly.py")
if output.startswith("skipped:"):
assert "host code generation is unavailable" in output
return
assert "target:" in output
assert "object bytes:" in output
assert "binary type:" in output
assert "assembly preview:" in output
def test_jit_add_example() -> None:
output = run_example("examples/jit_add.py")
if output.startswith("skipped:"):
assert "host JIT is unavailable" in output
return
assert "add_i32(40, 2) = 42" in output
assert "call_python(5) = 15" in output
def test_instruction_metadata_example() -> None:
output = run_example("examples/instruction_metadata.py")
assert "instruction metadata:" in output
assert "has example.note: True" in output
assert "round trip matches: True" in output
assert "note operands: 1" in output
assert "note text: created by instruction_metadata.py" in output
assert "define i32 @bump" in output
assert "%result = add i32" in output
assert "!example.note" in output
assert "created by instruction_metadata.py" in output
def test_named_metadata_example() -> None:
output = run_example("examples/named_metadata.py")
assert "named metadata:" in output
assert "keys: example.tags" in output
assert "example.tags operands: 2" in output
assert "iterated operands: 2" in output
assert "first tag strings: frontend, demo" in output
assert "!example.tags = !{" in output
assert '"frontend"' in output
assert '"purpose"' in output
def test_metadata_debug_info_example() -> None:
output = run_example("examples/metadata_debug_info.py")
assert "define i32 @add" in output
assert "!example.note" in output
assert "!example.compile_units" in output
assert "!llvm.module.flags" in output
assert "!dbg" in output
+31 -36
View File
@@ -66,22 +66,19 @@ def test_core_intrinsic_get_type():
print("[PASS] intrinsic_get_type works correctly")
def test_core_replace_md_node_operand():
"""Test LLVMReplaceMDNodeOperandWith → Value.replace_md_node_operand_with()"""
def test_core_metadata_node_operands():
"""Test MDNode operand inspection through Metadata.operands."""
with llvm.create_context() as ctx:
# Create metadata nodes
md1 = ctx.md_string("original")
md2 = ctx.md_string("replacement")
md_node = ctx.md_node([md1])
md_node = ctx.md_node([md1, md2])
# Convert to value to use with replace function
md_val = md_node.as_value(ctx)
replacement_val = md2.as_value(ctx)
assert [operand.string for operand in md_node.operands] == [
"original",
"replacement",
]
# Replace operand
md_val.replace_md_node_operand_with(0, replacement_val.as_metadata())
print("[PASS] replace_md_node_operand_with works correctly")
print("[PASS] metadata node operands work correctly")
def test_debuginfo_global_variable_expression():
@@ -90,16 +87,16 @@ def test_debuginfo_global_variable_expression():
with ctx.create_module("test") as mod:
with mod.create_dibuilder() as dib:
# Create necessary debug info
file = dib.create_file("test.c", "/path")
cu = dib.create_compile_unit(
lang=DWARF_LANG_C,
file = dib.file("test.c", "/path")
cu = dib.compile_unit(
language=llvm.DwarfLanguage.C,
file=file,
producer="test",
is_optimized=False,
flags="",
runtime_ver=0,
split_name="",
kind=llvm.DWARFEmissionFull,
kind=llvm.DwarfEmissionKind.Full,
dwo_id=0,
split_debug_inlining=True,
debug_info_for_profiling=False,
@@ -110,6 +107,12 @@ def test_debuginfo_global_variable_expression():
i32_ty = dib.create_basic_type(
"int", 32, DWARF_ATE_SIGNED, DI_FLAGS_ZERO
)
assert i32_ty.di_type_name == "int"
assert i32_ty.di_type_size_in_bits == 32
assert i32_ty.di_type_align_in_bits == 0
assert i32_ty.di_type_offset_in_bits == 0
assert i32_ty.di_type_line == 0
assert i32_ty.di_type_flags == DI_FLAGS_ZERO
# Create a global variable expression
gve = dib.create_global_variable_expression(
@@ -144,16 +147,16 @@ def test_debuginfo_class_type():
with llvm.create_context() as ctx:
with ctx.create_module("test") as mod:
with mod.create_dibuilder() as dib:
file = dib.create_file("test.cpp", "/path")
cu = dib.create_compile_unit(
lang=DWARF_LANG_C_PLUS_PLUS,
file = dib.file("test.cpp", "/path")
cu = dib.compile_unit(
language=llvm.DwarfLanguage.CPlusPlus,
file=file,
producer="test",
is_optimized=False,
flags="",
runtime_ver=0,
split_name="",
kind=llvm.DWARFEmissionFull,
kind=llvm.DwarfEmissionKind.Full,
dwo_id=0,
split_debug_inlining=True,
debug_info_for_profiling=False,
@@ -189,16 +192,16 @@ def test_debuginfo_static_member_type():
with llvm.create_context() as ctx:
with ctx.create_module("test") as mod:
with mod.create_dibuilder() as dib:
file = dib.create_file("test.cpp", "/path")
cu = dib.create_compile_unit(
lang=DWARF_LANG_C_PLUS_PLUS,
file = dib.file("test.cpp", "/path")
cu = dib.compile_unit(
language=llvm.DwarfLanguage.CPlusPlus,
file=file,
producer="test",
is_optimized=False,
flags="",
runtime_ver=0,
split_name="",
kind=llvm.DWARFEmissionFull,
kind=llvm.DwarfEmissionKind.Full,
dwo_id=0,
split_debug_inlining=True,
debug_info_for_profiling=False,
@@ -237,16 +240,16 @@ def test_debuginfo_member_pointer_type():
with llvm.create_context() as ctx:
with ctx.create_module("test") as mod:
with mod.create_dibuilder() as dib:
file = dib.create_file("test.cpp", "/path")
cu = dib.create_compile_unit(
lang=DWARF_LANG_C_PLUS_PLUS,
file = dib.file("test.cpp", "/path")
cu = dib.compile_unit(
language=llvm.DwarfLanguage.CPlusPlus,
file=file,
producer="test",
is_optimized=False,
flags="",
runtime_ver=0,
split_name="",
kind=llvm.DWARFEmissionFull,
kind=llvm.DwarfEmissionKind.Full,
dwo_id=0,
split_debug_inlining=True,
debug_info_for_profiling=False,
@@ -292,10 +295,6 @@ def test_debuginfo_member_pointer_type():
def test_object_binary_copy_to_memory_buffer():
"""Test LLVMBinaryCopyMemoryBuffer → binary.copy_to_memory_buffer()"""
# Initialize targets first (before creating context)
llvm.initialize_native_target()
llvm.initialize_native_asm_printer()
with llvm.create_context() as ctx:
with ctx.create_module("test") as mod:
# Create a simple function
@@ -335,10 +334,6 @@ def test_object_binary_copy_to_memory_buffer():
def test_object_section_contains_symbol():
"""Test LLVMGetSectionContainsSymbol → section.contains_symbol()"""
# Initialize targets first
llvm.initialize_native_target()
llvm.initialize_native_asm_printer()
with llvm.create_context() as ctx:
with ctx.create_module("test") as mod:
# Create a function
@@ -418,7 +413,7 @@ def test_all():
# Core.h
test_core_get_cast_opcode()
test_core_intrinsic_get_type()
test_core_replace_md_node_operand()
test_core_metadata_node_operands()
# DebugInfo.h
test_debuginfo_global_variable_expression()
-7
View File
@@ -98,13 +98,6 @@ def count_functions(mod):
def main():
# Initialize all targets (needed for pass manager)
llvm.initialize_all_target_infos()
llvm.initialize_all_targets()
llvm.initialize_all_target_mcs()
llvm.initialize_all_asm_printers()
llvm.initialize_all_asm_parsers()
# Get default target for running passes
triple = llvm.default_target_triple
target = llvm.Target.from_triple(triple)
-10
View File
@@ -7,9 +7,6 @@ This is the Python equivalent of tests/test_target_codegen.cpp.
Output should match the C++ golden master test.
LLVM Python APIs tested:
- llvm.initialize_all_targets(), llvm.initialize_all_target_mcs()
- llvm.initialize_all_asm_printers(), llvm.initialize_all_asm_parsers()
- llvm.initialize_native_target(), llvm.initialize_native_asm_printer()
- llvm.default_target_triple, llvm.normalize_target_triple()
- llvm.host_cpu_name, llvm.host_cpu_features
- llvm.Target.from_triple(), llvm.Target.from_name()
@@ -26,13 +23,6 @@ import llvm
def main():
# Initialize all targets
llvm.initialize_all_target_infos()
llvm.initialize_all_targets()
llvm.initialize_all_target_mcs()
llvm.initialize_all_asm_printers()
llvm.initialize_all_asm_parsers()
print("; Test: test_target_codegen")
print("; Tests target initialization, queries, and code generation")
print(";")