mirror of
https://github.com/LLVMParty/llvm-nanobind
synced 2026-06-21 13:43:38 +00:00
Merge pull request #9 from LLVMParty/api-ux
Add JIT/optimize/intrinsic/object support
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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 ()
|
||||
@@ -1,2 +0,0 @@
|
||||
# This is a directory for utility functions. No test here.
|
||||
config.suffixes = [".dummy"]
|
||||
@@ -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}. *)
|
||||
@@ -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
|
||||
@@ -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)))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ()
|
||||
@@ -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 _ -> ();;
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
config.suffixes = [".ml"]
|
||||
|
||||
if not "ocaml" in config.root.llvm_bindings:
|
||||
config.unsupported = True
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
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")
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(";")
|
||||
|
||||
Reference in New Issue
Block a user