diff --git a/README.md b/README.md index 52de7df..23aedb5 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/devdocs/api-design-philosophy.md b/devdocs/api-design-philosophy.md index cb26da0..8fda1c7 100644 --- a/devdocs/api-design-philosophy.md +++ b/devdocs/api-design-philosophy.md @@ -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 `C API: ...` 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 `C API limitation`. + +--- + +### 9. Method Chaining Potential **Principle**: Methods returning values enable fluent APIs. diff --git a/devdocs/api-reference.md b/devdocs/api-reference.md index 9e8ae4f..ac814e5 100644 --- a/devdocs/api-reference.md +++ b/devdocs/api-reference.md @@ -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", 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. diff --git a/devdocs/api-ux-cleanup/plan.md b/devdocs/api-ux-cleanup/plan.md new file mode 100644 index 0000000..b416ee7 --- /dev/null +++ b/devdocs/api-ux-cleanup/plan.md @@ -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") +mod.optimize("default", target_machine=tm) +mod.optimize("function(mem2reg),default", 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` succeeds on a simple module. +- `default` 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", 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()`. +- 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 +``` diff --git a/devdocs/api-ux-cleanup/progress.md b/devdocs/api-ux-cleanup/progress.md new file mode 100644 index 0000000..8ba5c8a --- /dev/null +++ b/devdocs/api-ux-cleanup/progress.md @@ -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. diff --git a/examples/emit_object_assembly.py b/examples/emit_object_assembly.py new file mode 100644 index 0000000..b6ae51c --- /dev/null +++ b/examples/emit_object_assembly.py @@ -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", 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() diff --git a/examples/instruction_metadata.py b/examples/instruction_metadata.py new file mode 100644 index 0000000..6340a3e --- /dev/null +++ b/examples/instruction_metadata.py @@ -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() diff --git a/examples/intrinsic_memcpy.py b/examples/intrinsic_memcpy.py new file mode 100644 index 0000000..26b687e --- /dev/null +++ b/examples/intrinsic_memcpy.py @@ -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() diff --git a/examples/jit_add.py b/examples/jit_add.py new file mode 100644 index 0000000..99f9d51 --- /dev/null +++ b/examples/jit_add.py @@ -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() diff --git a/examples/metadata_debug_info.py b/examples/metadata_debug_info.py new file mode 100644 index 0000000..9b3bf1a --- /dev/null +++ b/examples/metadata_debug_info.py @@ -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() diff --git a/examples/named_metadata.py b/examples/named_metadata.py new file mode 100644 index 0000000..1d4e4ef --- /dev/null +++ b/examples/named_metadata.py @@ -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() diff --git a/examples/optimize_function.py b/examples/optimize_function.py new file mode 100644 index 0000000..f103c8b --- /dev/null +++ b/examples/optimize_function.py @@ -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() diff --git a/examples/optimize_module.py b/examples/optimize_module.py new file mode 100644 index 0000000..d6a95f5 --- /dev/null +++ b/examples/optimize_module.py @@ -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`` or +``default``. + +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") -> 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() diff --git a/llvm-c/OCaml/.ocamlformat b/llvm-c/OCaml/.ocamlformat deleted file mode 100644 index e69de29..0000000 diff --git a/llvm-c/OCaml/Utils/Testsuite.ml b/llvm-c/OCaml/Utils/Testsuite.ml deleted file mode 100644 index 7a8955a..0000000 --- a/llvm-c/OCaml/Utils/Testsuite.ml +++ /dev/null @@ -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 () diff --git a/llvm-c/OCaml/Utils/lit.local.cfg b/llvm-c/OCaml/Utils/lit.local.cfg deleted file mode 100644 index a8c015b..0000000 --- a/llvm-c/OCaml/Utils/lit.local.cfg +++ /dev/null @@ -1,2 +0,0 @@ -# This is a directory for utility functions. No test here. -config.suffixes = [".dummy"] diff --git a/llvm-c/OCaml/analysis.ml b/llvm-c/OCaml/analysis.ml deleted file mode 100644 index da3e662..0000000 --- a/llvm-c/OCaml/analysis.ml +++ /dev/null @@ -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}. *) diff --git a/llvm-c/OCaml/bitreader.ml b/llvm-c/OCaml/bitreader.ml deleted file mode 100644 index 2638ca9..0000000 --- a/llvm-c/OCaml/bitreader.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/bitwriter.ml b/llvm-c/OCaml/bitwriter.ml deleted file mode 100644 index 17111bd..0000000 --- a/llvm-c/OCaml/bitwriter.ml +++ /dev/null @@ -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))) diff --git a/llvm-c/OCaml/core.ml b/llvm-c/OCaml/core.ml deleted file mode 100644 index 9f999be..0000000 --- a/llvm-c/OCaml/core.ml +++ /dev/null @@ -1,1498 +0,0 @@ -(* RUN: rm -rf %t && mkdir -p %t && cp %s %t/core.ml && cp %S/Utils/Testsuite.ml %t/Testsuite.ml - * RUN: %ocamlc -g -w +A -package llvm.analysis -package llvm.bitwriter -I %t/ -linkpkg %t/Testsuite.ml %t/core.ml -o %t/executable - * RUN: %t/executable %t/bitcode.bc - * RUN: %ocamlopt -g -w +A -package llvm.analysis -package llvm.bitwriter -I %t/ -linkpkg %t/Testsuite.ml %t/core.ml -o %t/executable - * RUN: %t/executable %t/bitcode.bc - * RUN: llvm-dis < %t/bitcode.bc > %t/dis.ll - * RUN: FileCheck %s < %t/dis.ll - * Do a second pass for things that shouldn't be anywhere. - * RUN: FileCheck -check-prefix=CHECK-NOWHERE %s < %t/dis.ll - * 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_bitwriter - -open Testsuite -let context = global_context () -let i1_type = Llvm.i1_type context -let i8_type = Llvm.i8_type context -let i16_type = Llvm.i16_type context -let i32_type = Llvm.i32_type context -let i64_type = Llvm.i64_type context -let void_type = Llvm.void_type context -let float_type = Llvm.float_type context -let double_type = Llvm.double_type context -let fp128_type = Llvm.fp128_type context - -(*===-- Fixture -----------------------------------------------------------===*) - -let filename = Sys.argv.(1) -let m = create_module context filename - -(*===-- Modules ----------------------------------------------------------===*) - -let test_modules () = - insist (module_context m = context) - -(*===-- Contained types --------------------------------------------------===*) - -let test_contained_types () = - let ar = struct_type context [| i32_type; i8_type |] in - insist (i32_type = (Array.get (subtypes ar)) 0); - insist (i8_type = (Array.get (subtypes ar)) 1); - insist ([| i32_type; i8_type |] = struct_element_types ar) - -(*===-- Pointer types ----------------------------------------------------===*) - -let test_pointer_types () = - insist (TypeKind.Pointer = classify_type (pointer_type context)); - insist (0 = address_space (pointer_type context)); - insist (0 = address_space (qualified_pointer_type context 0)); - insist (1 = address_space (qualified_pointer_type context 1)) - -(*===-- Other types ------------------------------------------------------===*) - -let test_other_types () = - insist (TypeKind.Void = classify_type void_type); - insist (TypeKind.Label = classify_type (label_type context)); - insist (TypeKind.X86_amx = classify_type (x86_amx_type context)); - insist (TypeKind.Token = classify_type (token_type context)); - insist (TypeKind.Metadata = classify_type (metadata_type context)) - -(*===-- Conversion --------------------------------------------------------===*) - -let test_conversion () = - insist ("i32" = (string_of_lltype i32_type)); - let c = const_int i32_type 42 in - insist ("i32 42" = (string_of_llvalue c)) - - -(*===-- Target ------------------------------------------------------------===*) - -let test_target () = - begin group "triple"; - let trip = "i686-apple-darwin8" in - set_target_triple trip m; - insist (trip = target_triple m) - end; - - begin group "layout"; - let layout = "e-m:o-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:128-n8:16:32-S128" in - set_data_layout layout m; - insist (layout = data_layout m) - end - (* CHECK: target datalayout = "e-m:o-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:128-n8:16:32-S128" - * CHECK: target triple = "i686-apple-darwin8" - *) - - -(*===-- Constants ---------------------------------------------------------===*) - -let test_constants () = - (* CHECK: const_int{{.*}}i32{{.*}}-1 - *) - group "int"; - let c = const_int i32_type (-1) in - ignore (define_global "const_int" c m); - insist (i32_type = type_of c); - insist (is_constant c); - insist (Some (-1L) = int64_of_const c); - - (* CHECK: const_sext_int{{.*}}i64{{.*}}-1 - *) - group "sext int"; - let c = const_int i64_type (-1) in - ignore (define_global "const_sext_int" c m); - insist (i64_type = type_of c); - insist (Some (-1L) = int64_of_const c); - - (* CHECK: const_zext_int64{{.*}}i64{{.*}}4294967295 - *) - group "zext int64"; - let c = const_of_int64 i64_type (Int64.of_string "4294967295") false in - ignore (define_global "const_zext_int64" c m); - insist (i64_type = type_of c); - insist (Some 4294967295L = int64_of_const c); - - (* CHECK: const_int_string{{.*}}i32{{.*}}-1 - *) - group "int string"; - let c = const_int_of_string i32_type "-1" 10 in - ignore (define_global "const_int_string" c m); - insist (i32_type = type_of c); - insist (None = (string_of_const c)); - insist (None = float_of_const c); - insist (Some (-1L) = int64_of_const c); - - (* CHECK: const_int64{{.*}}i64{{.*}}9223372036854775807 - *) - group "max int64"; - let c = const_of_int64 i64_type 9223372036854775807L true in - ignore (define_global "const_int64" c m) ; - insist (i64_type = type_of c); - insist (Some 9223372036854775807L = int64_of_const c); - - if Sys.word_size = 64; then begin - group "long int"; - let c = const_int i64_type (1 lsl 61) in - insist (c = const_of_int64 i64_type (Int64.of_int (1 lsl 61)) false) - end; - - (* CHECK: @const_string = global {{.*}}c"cruel\00world" - *) - group "string"; - let c = const_string context "cruel\000world" in - ignore (define_global "const_string" c m); - insist ((array_type i8_type 11) = type_of c); - insist ((Some "cruel\000world") = (string_of_const c)); - - (* CHECK: const_stringz{{.*}}"hi\00again\00" - *) - group "stringz"; - let c = const_stringz context "hi\000again" in - ignore (define_global "const_stringz" c m); - insist ((array_type i8_type 9) = type_of c); - - (* CHECK: const_single{{.*}}2.75 - * CHECK: const_double{{.*}}3.1459 - * CHECK: const_double_string{{.*}}2 - * CHECK: const_fake_fp128{{.*}}0xL00000000000000004000000000000000 - * CHECK: const_fp128_string{{.*}}0xLF3CB1CCF26FBC178452FB4EC7F91973F - *) - begin group "real"; - let cs = const_float float_type 2.75 in - ignore (define_global "const_single" cs m); - insist (float_type = type_of cs); - insist (float_of_const cs = Some 2.75); - - let cd = const_float double_type 3.1459 in - ignore (define_global "const_double" cd m); - insist (double_type = type_of cd); - insist (float_of_const cd = Some 3.1459); - - let cd = const_float_of_string double_type "2" in - ignore (define_global "const_double_string" cd m); - insist (double_type = type_of cd); - insist (float_of_const cd = Some 2.); - - let cd = const_float fp128_type 2. in - ignore (define_global "const_fake_fp128" cd m); - insist (fp128_type = type_of cd); - insist (float_of_const cd = Some 2.); - - let cd = const_float_of_string fp128_type "1e400" in - ignore (define_global "const_fp128_string" cd m); - insist (fp128_type = type_of cd); - insist (float_of_const cd = None); - end; - - let one = const_int i16_type 1 in - let two = const_int i16_type 2 in - let three = const_int i32_type 3 in - let four = const_int i32_type 4 in - - (* CHECK: const_array{{.*}}[i32 3, i32 4] - *) - group "array"; - let c = const_array i32_type [| three; four |] in - ignore (define_global "const_array" c m); - insist ((array_type i32_type 2) = (type_of c)); - insist (element_type (type_of c) = i32_type); - insist (Some three = (aggregate_element c 0)); - insist (Some four = (aggregate_element c 1)); - insist (None = (aggregate_element c 2)); - - (* CHECK: const_vector{{.*}} - *) - group "vector"; - let c = const_vector [| one; two; one; two; - one; two; one; two |] in - ignore (define_global "const_vector" c m); - insist ((vector_type i16_type 8) = (type_of c)); - insist (element_type (type_of c) = i16_type); - - (* CHECK: const_structure{{.*.}}i16 1, i16 2, i32 3, i32 4 - *) - group "structure"; - let c = const_struct context [| one; two; three; four |] in - ignore (define_global "const_structure" c m); - insist ((struct_type context [| i16_type; i16_type; i32_type; i32_type |]) - = (type_of c)); - - (* CHECK: const_null{{.*}}zeroinit - *) - group "null"; - let c = const_null (packed_struct_type context [| i1_type; i8_type; i64_type; - double_type |]) in - ignore (define_global "const_null" c m); - - (* CHECK: const_all_ones{{.*}}-1 - *) - group "all ones"; - let c = const_all_ones i64_type in - ignore (define_global "const_all_ones" c m); - - group "pointer null"; begin - (* CHECK: const_pointer_null = global ptr null - *) - let c = const_pointer_null (pointer_type context) in - ignore (define_global "const_pointer_null" c m); - end; - - (* CHECK: const_undef{{.*}}undef - *) - group "undef"; - let c = undef i1_type in - ignore (define_global "const_undef" c m); - insist (i1_type = type_of c); - insist (is_undef c); - - (* CHECK: const_poison{{.*}}poison - *) - group "poison"; - let c = poison i1_type in - ignore (define_global "const_poison" c m); - insist (i1_type = type_of c); - insist (is_poison c); - - group "constant arithmetic"; - (* CHECK: @const_neg = global i64 sub - * CHECK: @const_nsw_neg = global i64 sub nsw - * CHECK: @const_nuw_neg = global i64 sub - * CHECK: @const_not = global i64 xor - * CHECK: @const_add = global i64 add - * CHECK: @const_nsw_add = global i64 add nsw - * CHECK: @const_nuw_add = global i64 add nuw - * CHECK: @const_sub = global i64 sub - * CHECK: @const_nsw_sub = global i64 sub nsw - * CHECK: @const_nuw_sub = global i64 sub nuw - * CHECK: @const_xor = global i64 xor - *) - let void_ptr = pointer_type context in - let five = const_int i64_type 5 in - let foldbomb_gv = define_global "FoldBomb" (const_null i8_type) m in - let foldbomb = const_ptrtoint foldbomb_gv i64_type in - ignore (define_global "const_neg" (const_neg foldbomb) m); - ignore (define_global "const_nsw_neg" (const_nsw_neg foldbomb) m); - ignore (define_global "const_nuw_neg" (const_nuw_neg foldbomb) m); - ignore (define_global "const_not" (const_not foldbomb) m); - ignore (define_global "const_add" (const_add foldbomb five) m); - ignore (define_global "const_nsw_add" (const_nsw_add foldbomb five) m); - ignore (define_global "const_nuw_add" (const_nuw_add foldbomb five) m); - ignore (define_global "const_sub" (const_sub foldbomb five) m); - ignore (define_global "const_nsw_sub" (const_nsw_sub foldbomb five) m); - ignore (define_global "const_nuw_sub" (const_nuw_sub foldbomb five) m); - ignore (define_global "const_xor" (const_xor foldbomb five) m); - - group "constant casts"; - (* CHECK: const_trunc{{.*}}trunc - * CHECK: const_ptrtoint{{.*}}ptrtoint - * CHECK: const_inttoptr{{.*}}inttoptr - * CHECK: const_bitcast{{.*}}bitcast - *) - let i128_type = integer_type context 128 in - ignore (define_global "const_trunc" (const_trunc (const_add foldbomb five) - i8_type) m); - ignore (define_global "const_ptrtoint" (const_ptrtoint - (const_gep i8_type (const_null (pointer_type context)) - [| const_int i32_type 1 |]) - i32_type) m); - ignore (define_global "const_inttoptr" (const_inttoptr (const_add foldbomb five) - void_ptr) m); - ignore (define_global "const_bitcast" (const_bitcast foldbomb double_type) m); - - group "misc constants"; - (* CHECK: const_size_of{{.*}}getelementptr{{.*}}null - * CHECK: const_gep{{.*}}getelementptr - * CHECK: const_extractelement{{.*}}extractelement - * CHECK: const_insertelement{{.*}}insertelement - * CHECK: const_shufflevector = global <4 x i32> - *) - ignore (define_global "const_size_of" (size_of (pointer_type context)) m); - ignore (define_global "const_gep" (const_gep i8_type foldbomb_gv [| five |]) - m); - let zero = const_int i32_type 0 in - let one = const_int i32_type 1 in - ignore (define_global "const_extractelement" (const_extractelement - (const_vector [| zero; one; zero; one |]) - (const_trunc foldbomb i32_type)) m); - ignore (define_global "const_insertelement" (const_insertelement - (const_vector [| zero; one; zero; one |]) - zero (const_trunc foldbomb i32_type)) m); - ignore (define_global "const_shufflevector" (const_shufflevector - (const_vector [| zero; one |]) - (const_vector [| one; zero |]) - (const_vector [| const_int i32_type 0; const_int i32_type 1; - const_int i32_type 2; const_int i32_type 3 |])) m); - - group "asm"; begin - let ft = function_type void_type [| i32_type; i32_type; i32_type |] in - ignore (const_inline_asm - ft - "" - "{cx},{ax},{di},~{dirflag},~{fpsr},~{flags},~{edi},~{ecx}" - true - false) - end; - - group "recursive struct"; begin - let nsty = named_struct_type context "rec" in - let pty = pointer_type context in - struct_set_body nsty [| i32_type; pty |] false; - let elts = [| const_int i32_type 4; const_pointer_null pty |] in - let grec_init = const_named_struct nsty elts in - ignore (define_global "grec" grec_init m); - ignore (string_of_lltype nsty); - end - - -(*===-- Attributes --------------------------------------------------------===*) - -let test_attributes () = - group "enum attrs"; - let nonnull_kind = enum_attr_kind "nonnull" in - let dereferenceable_kind = enum_attr_kind "dereferenceable" in - insist (nonnull_kind = (enum_attr_kind "nonnull")); - insist (nonnull_kind <> dereferenceable_kind); - - let nonnull = - create_enum_attr context "nonnull" 0L in - let dereferenceable_4 = - create_enum_attr context "dereferenceable" 4L in - let dereferenceable_8 = - create_enum_attr context "dereferenceable" 8L in - insist (nonnull <> dereferenceable_4); - insist (dereferenceable_4 <> dereferenceable_8); - insist (nonnull = (create_enum_attr context "nonnull" 0L)); - insist ((repr_of_attr nonnull) = - AttrRepr.Enum(nonnull_kind, 0L)); - insist ((repr_of_attr dereferenceable_4) = - AttrRepr.Enum(dereferenceable_kind, 4L)); - insist ((attr_of_repr context (repr_of_attr nonnull)) = - nonnull); - insist ((attr_of_repr context (repr_of_attr dereferenceable_4)) = - dereferenceable_4); - - group "string attrs"; - let foo_bar = create_string_attr context "foo" "bar" in - let foo_baz = create_string_attr context "foo" "baz" in - insist (foo_bar <> foo_baz); - insist (foo_bar = (create_string_attr context "foo" "bar")); - insist ((repr_of_attr foo_bar) = AttrRepr.String("foo", "bar")); - insist ((attr_of_repr context (repr_of_attr foo_bar)) = foo_bar); - () - -(*===-- Global Values -----------------------------------------------------===*) - -let test_global_values () = - let (++) x f = f x; x in - let zero32 = const_null i32_type in - - (* CHECK: GVal01 - *) - group "naming"; - let g = define_global "TEMPORARY" zero32 m in - insist ("TEMPORARY" = value_name g); - set_value_name "GVal01" g; - insist ("GVal01" = value_name g); - - (* CHECK: GVal02{{.*}}linkonce - *) - group "linkage"; - let g = define_global "GVal02" zero32 m ++ - set_linkage Linkage.Link_once in - insist (Linkage.Link_once = linkage g); - - (* CHECK: GVal03{{.*}}Hanalei - *) - group "section"; - let g = define_global "GVal03" zero32 m ++ - set_section "Hanalei" in - insist ("Hanalei" = section g); - - (* CHECK: GVal04{{.*}}hidden - *) - group "visibility"; - let g = define_global "GVal04" zero32 m ++ - set_visibility Visibility.Hidden in - insist (Visibility.Hidden = visibility g); - - (* CHECK: GVal05{{.*}}align 128 - *) - group "alignment"; - let g = define_global "GVal05" zero32 m ++ - set_alignment 128 in - insist (128 = alignment g); - - (* CHECK: GVal06{{.*}}dllexport - *) - group "dll_storage_class"; - let g = define_global "GVal06" zero32 m ++ - set_dll_storage_class DLLStorageClass.DLLExport in - insist (DLLStorageClass.DLLExport = dll_storage_class g); - - (* CHECK: GVal07{{.*}}!test !0 - * See metadata check at the end of the file. - *) - group "metadata"; - let g = define_global "GVal07" zero32 m in - let md_string = mdstring context "global test metadata" in - let md_node = mdnode context [| zero32; md_string |] |> value_as_metadata in - let mdkind_test = mdkind_id context "test" in - global_set_metadata g mdkind_test md_node; - let md' = global_copy_all_metadata g in - insist (md' = [| mdkind_test, md_node |]) - -(*===-- Global Variables --------------------------------------------------===*) - -let test_global_variables () = - let (++) x f = f x; x in - let forty_two32 = const_int i32_type 42 in - - group "declarations"; begin - (* CHECK: @GVar01 = external global i32 - * CHECK: @QGVar01 = external addrspace(3) global i32 - *) - insist (None == lookup_global "GVar01" m); - let g = declare_global i32_type "GVar01" m in - insist (is_declaration g); - insist (pointer_type context == - type_of (declare_global float_type "GVar01" m)); - insist (g == declare_global i32_type "GVar01" m); - insist (match lookup_global "GVar01" m with Some x -> x = g - | None -> false); - - insist (None == lookup_global "QGVar01" m); - let g = declare_qualified_global i32_type "QGVar01" 3 m in - insist (is_declaration g); - insist (qualified_pointer_type context 3 == - type_of (declare_qualified_global float_type "QGVar01" 3 m)); - insist (g == declare_qualified_global i32_type "QGVar01" 3 m); - insist (match lookup_global "QGVar01" m with Some x -> x = g - | None -> false); - end; - - group "definitions"; begin - (* CHECK: @GVar02 = global i32 42 - * CHECK: @GVar03 = global i32 42 - * CHECK: @QGVar02 = addrspace(3) global i32 42 - * CHECK: @QGVar03 = addrspace(3) global i32 42 - *) - let g = define_global "GVar02" forty_two32 m in - let g2 = declare_global i32_type "GVar03" m ++ - set_initializer forty_two32 in - insist (not (is_declaration g)); - insist (not (is_declaration g2)); - insist ((global_initializer g) = (global_initializer g2)); - - let g = define_qualified_global "QGVar02" forty_two32 3 m in - let g2 = declare_qualified_global i32_type "QGVar03" 3 m ++ - set_initializer forty_two32 in - insist (not (is_declaration g)); - insist (not (is_declaration g2)); - insist ((global_initializer g) = (global_initializer g2)); - end; - - (* CHECK: GVar04{{.*}}thread_local - *) - group "threadlocal"; - let g = define_global "GVar04" forty_two32 m ++ - set_thread_local true in - insist (is_thread_local g); - - (* CHECK: GVar05{{.*}}thread_local(initialexec) - *) - group "threadlocal_mode"; - let g = define_global "GVar05" forty_two32 m ++ - set_thread_local_mode ThreadLocalMode.InitialExec in - insist ((thread_local_mode g) = ThreadLocalMode.InitialExec); - - (* CHECK: GVar06{{.*}}externally_initialized - *) - group "externally_initialized"; - let g = define_global "GVar06" forty_two32 m ++ - set_externally_initialized true in - insist (is_externally_initialized g); - - (* CHECK-NOWHERE-NOT: GVar07 - *) - group "delete"; - let g = define_global "GVar07" forty_two32 m in - delete_global g; - - (* CHECK: ConstGlobalVar{{.*}}constant - *) - group "constant"; - let g = define_global "ConstGlobalVar" forty_two32 m in - insist (not (is_global_constant g)); - set_global_constant true g; - insist (is_global_constant g); - - begin group "iteration"; - let m = create_module context "temp" in - - insist (get_module_identifier m = "temp"); - set_module_identifer m "temp2"; - insist (get_module_identifier m = "temp2"); - - insist (At_end m = global_begin m); - insist (At_start m = global_end m); - - let g1 = declare_global i32_type "One" m in - let g2 = declare_global i32_type "Two" m in - - insist (Before g1 = global_begin m); - insist (Before g2 = global_succ g1); - insist (At_end m = global_succ g2); - - insist (After g2 = global_end m); - insist (After g1 = global_pred g2); - insist (At_start m = global_pred g1); - - let lf s x = s ^ "->" ^ value_name x in - insist ("->One->Two" = fold_left_globals lf "" m); - - let rf x s = value_name x ^ "<-" ^ s in - insist ("One<-Two<-" = fold_right_globals rf m ""); - - dispose_module m - end - -(* String globals built below are emitted here. - * CHECK: build_global_string{{.*}}stringval - *) - - -(*===-- Uses --------------------------------------------------------------===*) - -let test_uses () = - let ty = function_type i32_type [| i32_type; i32_type |] in - let fn = define_function "use_function" ty m in - let b = builder_at_end context (entry_block fn) in - - let p1 = param fn 0 in - let p2 = param fn 1 in - let v1 = build_add p1 p2 "v1" b in - let v2 = build_add p1 v1 "v2" b in - let _ = build_add v1 v2 "v3" b in - - let lf s u = value_name (user u) ^ "->" ^ s in - insist ("v2->v3->" = fold_left_uses lf "" v1); - let rf u s = value_name (user u) ^ "<-" ^ s in - insist ("v3<-v2<-" = fold_right_uses rf v1 ""); - - let lf s u = value_name (used_value u) ^ "->" ^ s in - insist ("v1->v1->" = fold_left_uses lf "" v1); - - let rf u s = value_name (used_value u) ^ "<-" ^ s in - insist ("v1<-v1<-" = fold_right_uses rf v1 ""); - - ignore (build_unreachable b) - - -(*===-- Users -------------------------------------------------------------===*) - -let test_users () = - let ty = function_type i32_type [| i32_type; i32_type |] in - let fn = define_function "user_function" ty m in - let b = builder_at_end context (entry_block fn) in - - let p1 = param fn 0 in - let p2 = param fn 1 in - let a3 = build_alloca i32_type "user_alloca" b in - let p3 = build_load i32_type a3 "user_load" b in - let i = build_add p1 p2 "sum" b in - - insist ((num_operands i) = 2); - insist ((operand i 0) = p1); - insist ((operand i 1) = p2); - - set_operand i 1 p3; - insist ((operand i 1) != p2); - insist ((operand i 1) = p3); - - ignore (build_unreachable b) - - -(*===-- Aliases -----------------------------------------------------------===*) - -let test_aliases () = - (* CHECK: @alias = alias i32, ptr @aliasee - *) - let forty_two32 = const_int i32_type 42 in - let v = define_global "aliasee" forty_two32 m in - ignore (add_alias m i32_type 0 v "alias") - - -(*===-- Functions ---------------------------------------------------------===*) - -let test_functions () = - let ty = function_type i32_type [| i32_type; i64_type |] in - let ty2 = function_type i8_type [| i8_type; i64_type |] in - - (* CHECK: declare i32 @Fn1(i32, i64) - *) - begin group "declare"; - insist (None = lookup_function "Fn1" m); - let fn = declare_function "Fn1" ty m in - insist (pointer_type context = type_of fn); - insist (is_declaration fn); - insist (0 = Array.length (basic_blocks fn)); - insist (pointer_type context == type_of (declare_function "Fn1" ty2 m)); - insist (fn == declare_function "Fn1" ty m); - insist (None <> lookup_function "Fn1" m); - insist (match lookup_function "Fn1" m with Some x -> x = fn - | None -> false); - insist (m == global_parent fn) - end; - - (* CHECK-NOWHERE-NOT: Fn2 - *) - group "delete"; - let fn = declare_function "Fn2" ty m in - delete_function fn; - - (* CHECK: define{{.*}}Fn3 - *) - group "define"; - let fn = define_function "Fn3" ty m in - insist (not (is_declaration fn)); - insist (1 = Array.length (basic_blocks fn)); - ignore (build_unreachable (builder_at_end context (entry_block fn))); - - (* CHECK: define{{.*}}Fn4{{.*}}Param1{{.*}}Param2 - *) - group "params"; - let fn = define_function "Fn4" ty m in - let params = params fn in - insist (2 = Array.length params); - insist (params.(0) = param fn 0); - insist (params.(1) = param fn 1); - insist (i32_type = type_of params.(0)); - insist (i64_type = type_of params.(1)); - set_value_name "Param1" params.(0); - set_value_name "Param2" params.(1); - ignore (build_unreachable (builder_at_end context (entry_block fn))); - - (* CHECK: fastcc{{.*}}Fn5 - *) - group "callconv"; - let fn = define_function "Fn5" ty m in - insist (CallConv.c = function_call_conv fn); - set_function_call_conv CallConv.fast fn; - insist (CallConv.fast = function_call_conv fn); - ignore (build_unreachable (builder_at_end context (entry_block fn))); - - begin group "gc"; - (* CHECK: Fn6{{.*}}gc{{.*}}shadowstack - *) - let fn = define_function "Fn6" ty m in - insist (None = gc fn); - set_gc (Some "ocaml") fn; - insist (Some "ocaml" = gc fn); - set_gc None fn; - insist (None = gc fn); - set_gc (Some "shadowstack") fn; - ignore (build_unreachable (builder_at_end context (entry_block fn))); - end; - - begin group "iteration"; - let m = create_module context "temp" in - - insist (At_end m = function_begin m); - insist (At_start m = function_end m); - - let f1 = define_function "One" ty m in - let f2 = define_function "Two" ty m in - - insist (Before f1 = function_begin m); - insist (Before f2 = function_succ f1); - insist (At_end m = function_succ f2); - - insist (After f2 = function_end m); - insist (After f1 = function_pred f2); - insist (At_start m = function_pred f1); - - let lf s x = s ^ "->" ^ value_name x in - insist ("->One->Two" = fold_left_functions lf "" m); - - let rf x s = value_name x ^ "<-" ^ s in - insist ("One<-Two<-" = fold_right_functions rf m ""); - - dispose_module m - end - - -(*===-- Params ------------------------------------------------------------===*) - -let test_params () = - begin group "iteration"; - let m = create_module context "temp" in - - let vf = define_function "void" (function_type void_type [| |]) m in - - insist (At_end vf = param_begin vf); - insist (At_start vf = param_end vf); - - let ty = function_type void_type [| i32_type; i32_type |] in - let f = define_function "f" ty m in - let p1 = param f 0 in - let p2 = param f 1 in - set_value_name "One" p1; - set_value_name "Two" p2; - - insist (Before p1 = param_begin f); - insist (Before p2 = param_succ p1); - insist (At_end f = param_succ p2); - - insist (After p2 = param_end f); - insist (After p1 = param_pred p2); - insist (At_start f = param_pred p1); - - let lf s x = s ^ "->" ^ value_name x in - insist ("->One->Two" = fold_left_params lf "" f); - - let rf x s = value_name x ^ "<-" ^ s in - insist ("One<-Two<-" = fold_right_params rf f ""); - - dispose_module m - end - - -(*===-- Basic Blocks ------------------------------------------------------===*) - -let test_basic_blocks () = - let ty = function_type void_type [| |] in - - (* CHECK: Bb1 - *) - group "entry"; - let fn = declare_function "X" ty m in - let bb = append_block context "Bb1" fn in - insist (bb = entry_block fn); - ignore (build_unreachable (builder_at_end context bb)); - - (* CHECK-NOWHERE-NOT: Bb2 - *) - group "delete"; - let fn = declare_function "X2" ty m in - let bb = append_block context "Bb2" fn in - delete_block bb; - - group "insert"; - let fn = declare_function "X3" ty m in - let bbb = append_block context "b" fn in - let bba = insert_block context "a" bbb in - insist ([| bba; bbb |] = basic_blocks fn); - ignore (build_unreachable (builder_at_end context bba)); - ignore (build_unreachable (builder_at_end context bbb)); - - (* CHECK: Bb3 - *) - group "name/value"; - let fn = define_function "X4" ty m in - let bb = entry_block fn in - ignore (build_unreachable (builder_at_end context bb)); - let bbv = value_of_block bb in - set_value_name "Bb3" bbv; - insist ("Bb3" = value_name bbv); - - group "casts"; - let fn = define_function "X5" ty m in - let bb = entry_block fn in - ignore (build_unreachable (builder_at_end context bb)); - insist (bb = block_of_value (value_of_block bb)); - insist (value_is_block (value_of_block bb)); - insist (not (value_is_block (const_null i32_type))); - - begin group "iteration"; - let m = create_module context "temp" in - let f = declare_function "Temp" (function_type i32_type [| |]) m in - - insist (At_end f = block_begin f); - insist (At_start f = block_end f); - - let b1 = append_block context "One" f in - let b2 = append_block context "Two" f in - - insist (Before b1 = block_begin f); - insist (Before b2 = block_succ b1); - insist (At_end f = block_succ b2); - - insist (After b2 = block_end f); - insist (After b1 = block_pred b2); - insist (At_start f = block_pred b1); - - let lf s x = s ^ "->" ^ value_name (value_of_block x) in - insist ("->One->Two" = fold_left_blocks lf "" f); - - let rf x s = value_name (value_of_block x) ^ "<-" ^ s in - insist ("One<-Two<-" = fold_right_blocks rf f ""); - - dispose_module m - end - - -(*===-- Instructions ------------------------------------------------------===*) - -let test_instructions () = - begin group "iteration"; - let m = create_module context "temp" in - let fty = function_type void_type [| i32_type; i32_type |] in - let f = define_function "f" fty m in - let bb = entry_block f in - let b = builder_at context (At_end bb) in - - insist (At_end bb = instr_begin bb); - insist (At_start bb = instr_end bb); - - let i1 = build_add (param f 0) (param f 1) "One" b in - let i2 = build_sub (param f 0) (param f 1) "Two" b in - - insist (Before i1 = instr_begin bb); - insist (Before i2 = instr_succ i1); - insist (At_end bb = instr_succ i2); - - insist (After i2 = instr_end bb); - insist (After i1 = instr_pred i2); - insist (At_start bb = instr_pred i1); - - let lf s x = s ^ "->" ^ value_name x in - insist ("->One->Two" = fold_left_instrs lf "" bb); - - let rf x s = value_name x ^ "<-" ^ s in - insist ("One<-Two<-" = fold_right_instrs rf bb ""); - - dispose_module m - end; - - group "clone instr"; - begin - (* CHECK: %clone = add i32 %0, 2 - *) - let fty = function_type void_type [| i32_type |] in - let fn = define_function "BuilderParent" fty m in - let bb = entry_block fn in - let b = builder_at_end context bb in - let p = param fn 0 in - let sum = build_add p p "sum" b in - let y = const_int i32_type 2 in - let clone = instr_clone sum in - set_operand clone 0 p; - set_operand clone 1 y; - insert_into_builder clone "clone" b; - ignore (build_ret_void b) - end - - -(*===-- Builder -----------------------------------------------------------===*) - -let test_builder () = - let (++) x f = f x; x in - - begin group "parent"; - insist (try - ignore (insertion_block (builder context)); - false - with Not_found -> - true); - - let fty = function_type void_type [| i32_type |] in - let fn = define_function "BuilderParent" fty m in - let bb = entry_block fn in - let b = builder_at_end context bb in - let p = param fn 0 in - let sum = build_add p p "sum" b in - ignore (build_ret_void b); - - insist (fn = block_parent bb); - insist (fn = param_parent p); - insist (bb = instr_parent sum); - insist (bb = insertion_block b) - end; - - group "ret void"; - begin - (* CHECK: ret void - *) - let fty = function_type void_type [| |] in - let fn = declare_function "X6" fty m in - let b = builder_at_end context (append_block context "Bb01" fn) in - ignore (build_ret_void b) - end; - - group "ret aggregate"; - begin - (* CHECK: ret { i8, i64 } { i8 4, i64 5 } - *) - let sty = struct_type context [| i8_type; i64_type |] in - let fty = function_type sty [| |] in - let fn = declare_function "XA6" fty m in - let b = builder_at_end context (append_block context "Bb01" fn) in - let agg = [| const_int i8_type 4; const_int i64_type 5 |] in - ignore (build_aggregate_ret agg b) - end; - - (* The rest of the tests will use one big function. *) - let fty = function_type i32_type [| i32_type; i32_type |] in - let fn = define_function "X7" fty m in - let atentry = builder_at_end context (entry_block fn) in - let p1 = param fn 0 ++ set_value_name "P1" in - let p2 = param fn 1 ++ set_value_name "P2" in - let f1 = build_uitofp p1 float_type "F1" atentry in - let f2 = build_uitofp p2 float_type "F2" atentry in - - let bb00 = append_block context "Bb00" fn in - ignore (build_unreachable (builder_at_end context bb00)); - - group "function attribute"; - begin - let signext = create_enum_attr context "signext" 0L in - let zeroext = create_enum_attr context "zeroext" 0L in - let noalias = create_enum_attr context "noalias" 0L in - let nounwind = create_enum_attr context "nounwind" 0L in - let no_sse = create_string_attr context "no-sse" "" in - - add_function_attr fn signext (AttrIndex.Param 0); - add_function_attr fn noalias (AttrIndex.Param 1); - insist ((function_attrs fn (AttrIndex.Param 1)) = [|noalias|]); - remove_enum_function_attr fn (enum_attr_kind "noalias") (AttrIndex.Param 1); - add_function_attr fn no_sse (AttrIndex.Param 1); - insist ((function_attrs fn (AttrIndex.Param 1)) = [|no_sse|]); - remove_string_function_attr fn "no-sse" (AttrIndex.Param 1); - insist ((function_attrs fn (AttrIndex.Param 1)) = [||]); - add_function_attr fn nounwind AttrIndex.Function; - add_function_attr fn zeroext AttrIndex.Return; - - (* CHECK: define zeroext i32 @X7(i32 signext %P1, i32 %P2) - *) - end; - - group "casts"; begin - let void_ptr = pointer_type context in - - (* CHECK-DAG: %build_trunc = trunc i32 %P1 to i8 - * CHECK-DAG: %build_trunc2 = trunc i32 %P1 to i8 - * CHECK-DAG: %build_trunc3 = trunc i32 %P1 to i8 - * CHECK-DAG: %build_zext = zext i8 %build_trunc to i32 - * CHECK-DAG: %build_zext2 = zext i8 %build_trunc to i32 - * CHECK-DAG: %build_sext = sext i32 %build_zext to i64 - * CHECK-DAG: %build_sext2 = sext i32 %build_zext to i64 - * CHECK-DAG: %build_sext3 = sext i32 %build_zext to i64 - * CHECK-DAG: %build_uitofp = uitofp i64 %build_sext to float - * CHECK-DAG: %build_sitofp = sitofp i32 %build_zext to double - * CHECK-DAG: %build_fptoui = fptoui float %build_uitofp to i32 - * CHECK-DAG: %build_fptosi = fptosi double %build_sitofp to i64 - * CHECK-DAG: %build_fptrunc = fptrunc double %build_sitofp to float - * CHECK-DAG: %build_fptrunc2 = fptrunc double %build_sitofp to float - * CHECK-DAG: %build_fpext = fpext float %build_fptrunc to double - * CHECK-DAG: %build_fpext2 = fpext float %build_fptrunc to double - * CHECK-DAG: %build_inttoptr = inttoptr i32 %P1 to ptr - * CHECK-DAG: %build_ptrtoint = ptrtoint ptr %build_inttoptr to i64 - * CHECK-DAG: %build_ptrtoint2 = ptrtoint ptr %build_inttoptr to i64 - * CHECK-DAG: %build_bitcast = bitcast i64 %build_ptrtoint to double - * CHECK-DAG: %build_bitcast2 = bitcast i64 %build_ptrtoint to double - * CHECK-DAG: %build_bitcast3 = bitcast i64 %build_ptrtoint to double - * CHECK-DAG: %build_bitcast4 = bitcast i64 %build_ptrtoint to double - *) - let inst28 = build_trunc p1 i8_type "build_trunc" atentry in - let inst29 = build_zext inst28 i32_type "build_zext" atentry in - let inst30 = build_sext inst29 i64_type "build_sext" atentry in - let inst31 = build_uitofp inst30 float_type "build_uitofp" atentry in - let inst32 = build_sitofp inst29 double_type "build_sitofp" atentry in - ignore(build_fptoui inst31 i32_type "build_fptoui" atentry); - ignore(build_fptosi inst32 i64_type "build_fptosi" atentry); - let inst35 = build_fptrunc inst32 float_type "build_fptrunc" atentry in - ignore(build_fpext inst35 double_type "build_fpext" atentry); - let inst37 = build_inttoptr p1 void_ptr "build_inttoptr" atentry in - let inst38 = build_ptrtoint inst37 i64_type "build_ptrtoint" atentry in - ignore(build_bitcast inst38 double_type "build_bitcast" atentry); - ignore(build_zext_or_bitcast inst38 double_type "build_bitcast2" atentry); - ignore(build_sext_or_bitcast inst38 double_type "build_bitcast3" atentry); - ignore(build_trunc_or_bitcast inst38 double_type "build_bitcast4" atentry); - ignore(build_pointercast inst37 (pointer_type context) "build_pointercast" atentry); - - ignore(build_zext_or_bitcast inst28 i32_type "build_zext2" atentry); - ignore(build_sext_or_bitcast inst29 i64_type "build_sext2" atentry); - ignore(build_trunc_or_bitcast p1 i8_type "build_trunc2" atentry); - ignore(build_pointercast inst37 i64_type "build_ptrtoint2" atentry); - ignore(build_intcast inst29 i64_type "build_sext3" atentry); - ignore(build_intcast p1 i8_type "build_trunc3" atentry); - ignore(build_fpcast inst35 double_type "build_fpext2" atentry); - ignore(build_fpcast inst32 float_type "build_fptrunc2" atentry); - end; - - group "comparisons"; begin - (* CHECK: %build_icmp_ne = icmp ne i32 %P1, %P2 - * CHECK: %build_icmp_sle = icmp sle i32 %P2, %P1 - * CHECK: %build_fcmp_false = fcmp false float %F1, %F2 - * CHECK: %build_fcmp_true = fcmp true float %F2, %F1 - * CHECK: %build_is_null{{.*}}= icmp eq{{.*}}%X0,{{.*}}null - * CHECK: %build_is_not_null = icmp ne ptr %X1, null - * CHECK: %build_ptrdiff - *) - let c = build_icmp Icmp.Ne p1 p2 "build_icmp_ne" atentry in - insist (Some Icmp.Ne = icmp_predicate c); - insist (None = fcmp_predicate c); - - let c = build_icmp Icmp.Sle p2 p1 "build_icmp_sle" atentry in - insist (Some Icmp.Sle = icmp_predicate c); - insist (None = fcmp_predicate c); - - let c = build_fcmp Fcmp.False f1 f2 "build_fcmp_false" atentry in - (* insist (Some Fcmp.False = fcmp_predicate c); *) - insist (None = icmp_predicate c); - - let c = build_fcmp Fcmp.True f2 f1 "build_fcmp_true" atentry in - (* insist (Some Fcmp.True = fcmp_predicate c); *) - insist (None = icmp_predicate c); - - let g0 = declare_global (pointer_type context) "g0" m in - let g1 = declare_global (pointer_type context) "g1" m in - let p0 = build_load (pointer_type context) g0 "X0" atentry in - let p1 = build_load (pointer_type context) g1 "X1" atentry in - ignore (build_is_null p0 "build_is_null" atentry); - ignore (build_is_not_null p1 "build_is_not_null" atentry); - ignore (build_ptrdiff i8_type p1 p0 "build_ptrdiff" atentry); - end; - - group "miscellaneous"; begin - (* CHECK: %build_call = tail call cc63 zeroext i32 @{{.*}}(i32 signext %P2, i32 %P1) - * CHECK: %build_select = select i1 %build_icmp, i32 %P1, i32 %P2 - * CHECK: %build_va_arg = va_arg ptr null, i32 - * CHECK: %build_extractelement = extractelement <4 x i32> %Vec1, i32 %P2 - * CHECK: %build_insertelement = insertelement <4 x i32> %Vec1, i32 %P1, i32 %P2 - * CHECK: %build_shufflevector = shufflevector <4 x i32> %Vec1, <4 x i32> %Vec2, <4 x i32> - * CHECK: %build_insertvalue0 = insertvalue{{.*}}%bl, i32 1, 0 - * CHECK: %build_extractvalue = extractvalue{{.*}}%build_insertvalue1, 1 - *) - let ci = build_call fty fn [| p2; p1 |] "build_call" atentry in - insist (CallConv.c = instruction_call_conv ci); - set_instruction_call_conv 63 ci; - insist (63 = instruction_call_conv ci); - insist (not (is_tail_call ci)); - set_tail_call true ci; - insist (is_tail_call ci); - - let signext = create_enum_attr context "signext" 0L in - let zeroext = create_enum_attr context "zeroext" 0L in - let noalias = create_enum_attr context "noalias" 0L in - let noreturn = create_enum_attr context "noreturn" 0L in - let no_sse = create_string_attr context "no-sse" "" in - - add_call_site_attr ci signext (AttrIndex.Param 0); - add_call_site_attr ci noalias (AttrIndex.Param 1); - insist ((call_site_attrs ci (AttrIndex.Param 1)) = [|noalias|]); - remove_enum_call_site_attr ci (enum_attr_kind "noalias") (AttrIndex.Param 1); - add_call_site_attr ci no_sse (AttrIndex.Param 1); - insist ((call_site_attrs ci (AttrIndex.Param 1)) = [|no_sse|]); - remove_string_call_site_attr ci "no-sse" (AttrIndex.Param 1); - insist ((call_site_attrs ci (AttrIndex.Param 1)) = [||]); - add_call_site_attr ci noreturn AttrIndex.Function; - add_call_site_attr ci zeroext AttrIndex.Return; - - let inst46 = build_icmp Icmp.Eq p1 p2 "build_icmp" atentry in - ignore (build_select inst46 p1 p2 "build_select" atentry); - ignore (build_va_arg - (const_null (pointer_type context)) - i32_type "build_va_arg" atentry); - - (* Set up some vector vregs. *) - let one = const_int i32_type 1 in - let zero = const_int i32_type 0 in - let t1 = const_vector [| one; zero; one; zero |] in - let t2 = const_vector [| zero; one; zero; one |] in - let t3 = const_vector [| one; one; zero; zero |] in - let vec1 = build_insertelement t1 p1 p2 "Vec1" atentry in - let vec2 = build_insertelement t2 p1 p2 "Vec2" atentry in - let sty = struct_type context [| i32_type; i8_type |] in - - ignore (build_extractelement vec1 p2 "build_extractelement" atentry); - ignore (build_insertelement vec1 p1 p2 "build_insertelement" atentry); - ignore (build_shufflevector vec1 vec2 t3 "build_shufflevector" atentry); - - let p = build_alloca sty "ba" atentry in - let agg = build_load sty p "bl" atentry in - let agg0 = build_insertvalue agg (const_int i32_type 1) 0 - "build_insertvalue0" atentry in - let agg1 = build_insertvalue agg0 (const_int i8_type 2) 1 - "build_insertvalue1" atentry in - ignore (build_extractvalue agg1 1 "build_extractvalue" atentry) - end; - - group "metadata"; begin - (* CHECK: %metadata = add i32 %P1, %P2, !test !2 - * !2 is metadata emitted at EOF. - *) - let i = build_add p1 p2 "metadata" atentry in - insist ((has_metadata i) = false); - - let m1 = const_int i32_type 1 in - let m2 = mdstring context "metadata test" in - let md = mdnode context [| m1; m2 |] in - - let kind = mdkind_id context "test" in - set_metadata i kind md; - - insist ((has_metadata i) = true); - insist ((metadata i kind) = Some md); - insist ((get_mdnode_operands md) = [| m1; m2 |]); - - clear_metadata i kind; - - insist ((has_metadata i) = false); - insist ((metadata i kind) = None); - - set_metadata i kind md - end; - - group "named metadata"; begin - (* !llvm.module.flags is emitted at EOF. *) - let n1 = const_int i32_type 1 in - let n2 = mdstring context "Debug Info Version" in - let n3 = const_int i32_type 3 in - let md = mdnode context [| n1; n2; n3 |] in - add_named_metadata_operand m "llvm.module.flags" md; - - insist ((get_named_metadata m "llvm.module.flags") = [| md |]) - end; - - group "ret"; begin - (* CHECK: ret{{.*}}P1 - *) - let ret = build_ret p1 atentry in - position_before_dbg_records ret atentry - end; - - (* see test/Feature/exception.ll *) - let bblpad = append_block context "Bblpad" fn in - let rt = struct_type context [| pointer_type context; i32_type |] in - let ft = var_arg_function_type i32_type [||] in - let personality = declare_function "__gxx_personality_v0" ft m in - let ztic = declare_global (pointer_type context) "_ZTIc" m in - let ztid = declare_global (pointer_type context) "_ZTId" m in - let ztipkc = declare_global (pointer_type context) "_ZTIPKc" m in - begin - set_global_constant true ztic; - set_global_constant true ztid; - set_global_constant true ztipkc; - let lp = build_landingpad rt personality 0 "lpad" - (builder_at_end context bblpad) in begin - set_cleanup lp true; - add_clause lp ztic; - insist((pointer_type context) = type_of ztid); - let ety = pointer_type context in - add_clause lp (const_array ety [| ztipkc; ztid |]); - ignore (build_resume lp (builder_at_end context bblpad)); - end; - (* CHECK: landingpad - * CHECK: cleanup - * CHECK: catch{{.*}}ptr{{.*}}@_ZTIc - * CHECK: filter{{.*}}@_ZTIPKc{{.*}}@_ZTId - * CHECK: resume - * *) - end; - - group "br"; begin - (* CHECK: br{{.*}}Bb02 - *) - let bb02 = append_block context "Bb02" fn in - let b = builder_at_end context bb02 in - let br = build_br bb02 b in - insist (successors br = [| bb02 |]) ; - insist (is_conditional br = false) ; - insist (get_branch br = Some (`Unconditional bb02)) ; - end; - - group "cond_br"; begin - (* CHECK: br{{.*}}build_br{{.*}}Bb03{{.*}}Bb00 - *) - let bb03 = append_block context "Bb03" fn in - let b = builder_at_end context bb03 in - let cond = build_trunc p1 i1_type "build_br" b in - let br = build_cond_br cond bb03 bb00 b in - insist (num_successors br = 2) ; - insist (successor br 0 = bb03) ; - insist (successor br 1 = bb00) ; - insist (is_conditional br = true) ; - insist (get_branch br = Some (`Conditional (cond, bb03, bb00))) ; - end; - - group "switch"; begin - (* CHECK: switch{{.*}}P1{{.*}}SwiBlock3 - * CHECK: 2,{{.*}}SwiBlock2 - *) - let bb1 = append_block context "SwiBlock1" fn in - let bb2 = append_block context "SwiBlock2" fn in - ignore (build_unreachable (builder_at_end context bb2)); - let bb3 = append_block context "SwiBlock3" fn in - ignore (build_unreachable (builder_at_end context bb3)); - let si = build_switch p1 bb3 1 (builder_at_end context bb1) in begin - ignore (add_case si (const_int i32_type 2) bb2); - insist (switch_default_dest si = bb3); - end; - insist (num_successors si = 2) ; - insist (get_branch si = None) ; - end; - - group "malloc/free"; begin - (* CHECK: call{{.*}}@malloc(i32 ptrtoint - * CHECK: call{{.*}}@free(ptr - * CHECK: call{{.*}}@malloc(i32 % - *) - let bb1 = append_block context "MallocBlock1" fn in - let m1 = (build_malloc (pointer_type context) "m1" - (builder_at_end context bb1)) in - ignore (build_free m1 (builder_at_end context bb1)); - ignore (build_array_malloc i32_type p1 "m2" (builder_at_end context bb1)); - ignore (build_unreachable (builder_at_end context bb1)); - end; - - group "indirectbr"; begin - (* CHECK: indirectbr ptr blockaddress(@X7, %IBRBlock2), [label %IBRBlock2, label %IBRBlock3] - *) - let bb1 = append_block context "IBRBlock1" fn in - - let bb2 = append_block context "IBRBlock2" fn in - ignore (build_unreachable (builder_at_end context bb2)); - - let bb3 = append_block context "IBRBlock3" fn in - ignore (build_unreachable (builder_at_end context bb3)); - - let addr = block_address fn bb2 in - let ibr = build_indirect_br addr 2 (builder_at_end context bb1) in - ignore (add_destination ibr bb2); - ignore (add_destination ibr bb3) - end; - - group "invoke"; begin - (* CHECK: build_invoke{{.*}}invoke{{.*}}P1{{.*}}P2 - * CHECK: to{{.*}}Bb04{{.*}}unwind{{.*}}Bblpad - *) - let bb04 = append_block context "Bb04" fn in - let b = builder_at_end context bb04 in - ignore (build_invoke fty fn [| p1; p2 |] bb04 bblpad "build_invoke" b) - end; - - group "unreachable"; begin - (* CHECK: unreachable - *) - let bb06 = append_block context "Bb06" fn in - let b = builder_at_end context bb06 in - ignore (build_unreachable b) - end; - - group "arithmetic"; begin - let bb07 = append_block context "Bb07" fn in - let b = builder_at_end context bb07 in - - (* CHECK: %build_add = add i32 %P1, %P2 - * CHECK: %build_nsw_add = add nsw i32 %P1, %P2 - * CHECK: %build_nuw_add = add nuw i32 %P1, %P2 - * CHECK: %build_fadd = fadd float %F1, %F2 - * CHECK: %build_sub = sub i32 %P1, %P2 - * CHECK: %build_nsw_sub = sub nsw i32 %P1, %P2 - * CHECK: %build_nuw_sub = sub nuw i32 %P1, %P2 - * CHECK: %build_fsub = fsub float %F1, %F2 - * CHECK: %build_mul = mul i32 %P1, %P2 - * CHECK: %build_nsw_mul = mul nsw i32 %P1, %P2 - * CHECK: %build_nuw_mul = mul nuw i32 %P1, %P2 - * CHECK: %build_fmul = fmul float %F1, %F2 - * CHECK: %build_udiv = udiv i32 %P1, %P2 - * CHECK: %build_sdiv = sdiv i32 %P1, %P2 - * CHECK: %build_exact_sdiv = sdiv exact i32 %P1, %P2 - * CHECK: %build_fdiv = fdiv float %F1, %F2 - * CHECK: %build_urem = urem i32 %P1, %P2 - * CHECK: %build_srem = srem i32 %P1, %P2 - * CHECK: %build_frem = frem float %F1, %F2 - * CHECK: %build_shl = shl i32 %P1, %P2 - * CHECK: %build_lshl = lshr i32 %P1, %P2 - * CHECK: %build_ashl = ashr i32 %P1, %P2 - * CHECK: %build_and = and i32 %P1, %P2 - * CHECK: %build_or = or i32 %P1, %P2 - * CHECK: %build_xor = xor i32 %P1, %P2 - * CHECK: %build_neg = sub i32 0, %P1 - * CHECK: %build_nsw_neg = sub nsw i32 0, %P1 - * CHECK: %build_nuw_neg = sub nuw i32 0, %P1 - * CHECK: %build_fneg = fneg float %F1 - * CHECK: %build_not = xor i32 %P1, -1 - * CHECK: %build_freeze = freeze i32 %P1 - *) - ignore (build_add p1 p2 "build_add" b); - ignore (build_nsw_add p1 p2 "build_nsw_add" b); - ignore (build_nuw_add p1 p2 "build_nuw_add" b); - ignore (build_fadd f1 f2 "build_fadd" b); - ignore (build_sub p1 p2 "build_sub" b); - ignore (build_nsw_sub p1 p2 "build_nsw_sub" b); - ignore (build_nuw_sub p1 p2 "build_nuw_sub" b); - ignore (build_fsub f1 f2 "build_fsub" b); - ignore (build_mul p1 p2 "build_mul" b); - ignore (build_nsw_mul p1 p2 "build_nsw_mul" b); - ignore (build_nuw_mul p1 p2 "build_nuw_mul" b); - ignore (build_fmul f1 f2 "build_fmul" b); - ignore (build_udiv p1 p2 "build_udiv" b); - ignore (build_sdiv p1 p2 "build_sdiv" b); - ignore (build_exact_sdiv p1 p2 "build_exact_sdiv" b); - ignore (build_fdiv f1 f2 "build_fdiv" b); - ignore (build_urem p1 p2 "build_urem" b); - ignore (build_srem p1 p2 "build_srem" b); - ignore (build_frem f1 f2 "build_frem" b); - ignore (build_shl p1 p2 "build_shl" b); - ignore (build_lshr p1 p2 "build_lshl" b); - ignore (build_ashr p1 p2 "build_ashl" b); - ignore (build_and p1 p2 "build_and" b); - ignore (build_or p1 p2 "build_or" b); - ignore (build_xor p1 p2 "build_xor" b); - ignore (build_neg p1 "build_neg" b); - ignore (build_nsw_neg p1 "build_nsw_neg" b); - ignore (build_nuw_neg p1 "build_nuw_neg" b); - ignore (build_fneg f1 "build_fneg" b); - ignore (build_not p1 "build_not" b); - ignore (build_freeze p1 "build_freeze" b); - ignore (build_unreachable b) - end; - - group "memory"; begin - let bb08 = append_block context "Bb08" fn in - let b = builder_at_end context bb08 in - - (* CHECK: %build_alloca = alloca i32 - * CHECK: %build_array_alloca = alloca i32, i32 %P2 - * CHECK: %build_load = load volatile i32, ptr %build_array_alloca, align 4 - * CHECK: store volatile i32 %P2, ptr %build_alloca, align 4 - * CHECK: %build_gep = getelementptr i32, ptr %build_array_alloca, i32 %P2 - * CHECK: %build_in_bounds_gep = getelementptr inbounds i32, ptr %build_array_alloca, i32 %P2 - * CHECK: %build_struct_gep = getelementptr inbounds{{.*}}%build_alloca2, i32 0, i32 1 - * CHECK: %build_atomicrmw = atomicrmw xchg ptr %p, i8 42 seq_cst - *) - let alloca = build_alloca i32_type "build_alloca" b in - let array_alloca = build_array_alloca i32_type p2 "build_array_alloca" b in - - let load = build_load i32_type array_alloca "build_load" b in - ignore(set_alignment 4 load); - ignore(set_volatile true load); - insist(true = is_volatile load); - insist(4 = alignment load); - - let store = build_store p2 alloca b in - ignore(set_volatile true store); - ignore(set_alignment 4 store); - insist(true = is_volatile store); - insist(4 = alignment store); - ignore(build_gep i32_type array_alloca [| p2 |] "build_gep" b); - ignore(build_in_bounds_gep i32_type array_alloca [| p2 |] - "build_in_bounds_gep" b); - - let sty = struct_type context [| i32_type; i8_type |] in - let alloca2 = build_alloca sty "build_alloca2" b in - ignore(build_struct_gep sty alloca2 1 "build_struct_gep" b); - - let p = build_alloca i8_type "p" b in - ignore(build_atomicrmw AtomicRMWBinOp.Xchg p (const_int i8_type 42) - AtomicOrdering.SequentiallyConsistent false "build_atomicrmw" - b); - - ignore(build_unreachable b) - end; - - group "string"; begin - let bb09 = append_block context "Bb09" fn in - let b = builder_at_end context bb09 in - let p = build_alloca (pointer_type context) "p" b in - (* build_global_string is emitted above. - * CHECK: store{{.*}}build_global_string1{{.*}}p - * *) - ignore (build_global_string "stringval" "build_global_string" b); - let g = build_global_stringptr "stringval" "build_global_string1" b in - ignore (build_store g p b); - ignore(build_unreachable b); - end; - - group "phi"; begin - (* CHECK: PhiNode{{.*}}P1{{.*}}PhiBlock1{{.*}}P2{{.*}}PhiBlock2 - *) - let b1 = append_block context "PhiBlock1" fn in - let b2 = append_block context "PhiBlock2" fn in - - let jb = append_block context "PhiJoinBlock" fn in - ignore (build_br jb (builder_at_end context b1)); - ignore (build_br jb (builder_at_end context b2)); - let at_jb = builder_at_end context jb in - - let phi = build_phi [(p1, b1)] "PhiNode" at_jb in - insist ([(p1, b1)] = incoming phi); - - add_incoming (p2, b2) phi; - insist ([(p1, b1); (p2, b2)] = incoming phi); - - (* CHECK: %PhiEmptyNode = phi i8 - *) - let phi_empty = build_empty_phi i8_type "PhiEmptyNode" at_jb in - insist ([] = incoming phi_empty); - - (* can't emit an empty phi to bitcode *) - add_incoming (const_int i8_type 1, b1) phi_empty; - add_incoming (const_int i8_type 2, b2) phi_empty; - - ignore (build_unreachable at_jb); - end - -(* End-of-file checks for things like metdata and attributes. - * CHECK: !llvm.module.flags = !{!1} - * CHECK: !0 = !{i32 0, !"global test metadata"} - * CHECK: !1 = !{i32 1, !"Debug Info Version", i32 3} - * CHECK: !2 = !{i32 1, !"metadata test"} - *) - - -(*===-- Memory Buffer -----------------------------------------------------===*) - -let test_memory_buffer () = - group "memory buffer"; - let buf = MemoryBuffer.of_string "foobar" in - insist ((MemoryBuffer.as_string buf) = "foobar") - - -(*===-- Writer ------------------------------------------------------------===*) - -let test_writer () = - group "valid"; - insist (match Llvm_analysis.verify_module m with - | None -> true - | Some msg -> prerr_string msg; false); - - group "writer"; - insist (write_bitcode_file m filename); - - dispose_module m - - -(*===-- Driver ------------------------------------------------------------===*) - -let _ = - suite "modules" test_modules; - suite "contained types" test_contained_types; - suite "pointer types" test_pointer_types; - suite "other types" test_other_types; - suite "conversion" test_conversion; - suite "target" test_target; - suite "constants" test_constants; - suite "attributes" test_attributes; - suite "global values" test_global_values; - suite "global variables" test_global_variables; - suite "uses" test_uses; - suite "users" test_users; - suite "aliases" test_aliases; - suite "functions" test_functions; - suite "params" test_params; - suite "basic blocks" test_basic_blocks; - suite "instructions" test_instructions; - suite "builder" test_builder; - suite "memory buffer" test_memory_buffer; - suite "writer" test_writer; (* Keep this last; it disposes m. *) - exit !exit_status diff --git a/llvm-c/OCaml/debuginfo.ml b/llvm-c/OCaml/debuginfo.ml deleted file mode 100644 index 6ebc7c3..0000000 --- a/llvm-c/OCaml/debuginfo.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/diagnostic_handler.ml b/llvm-c/OCaml/diagnostic_handler.ml deleted file mode 100644 index 491b280..0000000 --- a/llvm-c/OCaml/diagnostic_handler.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/executionengine.ml b/llvm-c/OCaml/executionengine.ml deleted file mode 100644 index ec4031f..0000000 --- a/llvm-c/OCaml/executionengine.ml +++ /dev/null @@ -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 () diff --git a/llvm-c/OCaml/ext_exc.ml b/llvm-c/OCaml/ext_exc.ml deleted file mode 100644 index 8ff19cd..0000000 --- a/llvm-c/OCaml/ext_exc.ml +++ /dev/null @@ -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 _ -> ();; diff --git a/llvm-c/OCaml/irreader.ml b/llvm-c/OCaml/irreader.ml deleted file mode 100644 index 7d8e4a9..0000000 --- a/llvm-c/OCaml/irreader.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/linker.ml b/llvm-c/OCaml/linker.ml deleted file mode 100644 index 6375be8..0000000 --- a/llvm-c/OCaml/linker.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/lit.local.cfg b/llvm-c/OCaml/lit.local.cfg deleted file mode 100644 index 03e71e9..0000000 --- a/llvm-c/OCaml/lit.local.cfg +++ /dev/null @@ -1,4 +0,0 @@ -config.suffixes = [".ml"] - -if not "ocaml" in config.root.llvm_bindings: - config.unsupported = True diff --git a/llvm-c/OCaml/passbuilder.ml b/llvm-c/OCaml/passbuilder.ml deleted file mode 100644 index 180cc3a..0000000 --- a/llvm-c/OCaml/passbuilder.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/target.ml b/llvm-c/OCaml/target.ml deleted file mode 100644 index e9465fd..0000000 --- a/llvm-c/OCaml/target.ml +++ /dev/null @@ -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 diff --git a/llvm-c/OCaml/transform_utils.ml b/llvm-c/OCaml/transform_utils.ml deleted file mode 100644 index f951a0d..0000000 --- a/llvm-c/OCaml/transform_utils.ml +++ /dev/null @@ -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 () diff --git a/llvm_c_test/debuginfo.py b/llvm_c_test/debuginfo.py index a5ce04f..afc33e2 100644 --- a/llvm_c_test/debuginfo.py +++ b/llvm_c_test/debuginfo.py @@ -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: diff --git a/llvm_c_test/disassemble.py b/llvm_c_test/disassemble.py index b0e0cfe..ac5f41e 100644 --- a/llvm_c_test/disassemble.py +++ b/llvm_c_test/disassemble.py @@ -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) diff --git a/llvm_c_test/echo.py b/llvm_c_test/echo.py index 9e8b988..4bb529d 100644 --- a/llvm_c_test/echo.py +++ b/llvm_c_test/echo.py @@ -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: diff --git a/llvm_c_test/metadata.py b/llvm_c_test/metadata.py index 378e1f3..f8c2d63 100644 --- a/llvm_c_test/metadata.py +++ b/llvm_c_test/metadata.py @@ -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 diff --git a/llvm_c_test/targets.py b/llvm_c_test/targets.py index 9d0555d..29d6bd2 100644 --- a/llvm_c_test/targets.py +++ b/llvm_c_test/targets.py @@ -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: diff --git a/src/llvm-nanobind.cpp b/src/llvm-nanobind.cpp index 2fe2fd8..0a3278a 100644 --- a/src/llvm-nanobind.cpp +++ b/src/llvm-nanobind.cpp @@ -22,8 +22,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -127,6 +129,15 @@ struct LLVMAssertionError : std::runtime_error { using std::runtime_error::runtime_error; }; +static std::string consume_llvm_error(LLVMErrorRef err) { + if (!err) + return ""; + char *msg = LLVMGetErrorMessage(err); + std::string result = msg ? msg : "Unknown error"; + LLVMDisposeErrorMessage(msg); + return result; +} + static unsigned lookup_enum_attribute_kind(const std::string &name) { return LLVMGetEnumAttributeKindForName(name.c_str(), name.size()); } @@ -138,6 +149,141 @@ static unsigned require_enum_attribute_kind(const std::string &name) { return kind_id; } +static std::string trim_copy(const std::string &text) { + size_t start = 0; + while (start < text.size() && + std::isspace(static_cast(text[start]))) + ++start; + size_t end = text.size(); + while (end > start && std::isspace(static_cast(text[end - 1]))) + --end; + return text.substr(start, end - start); +} + +static std::string lower_copy(std::string text) { + for (char &c : text) + c = static_cast(std::tolower(static_cast(c))); + return text; +} + +static std::string normalize_memory_keyword(const std::string &text) { + std::string result; + for (char c : trim_copy(text)) { + unsigned char uc = static_cast(c); + if (std::isspace(uc) || c == '_' || c == '-') + continue; + result.push_back(static_cast(std::tolower(uc))); + } + return result; +} + +static uint64_t memory_access_bits(const std::string &access) { + std::string key = normalize_memory_keyword(access); + if (key == "none" || key == "nomodref" || key == "noaccess") + return 0; + if (key == "read" || key == "ref" || key == "readonly") + return 1; + if (key == "write" || key == "mod" || key == "writeonly") + return 2; + if (key == "readwrite" || key == "modref") + return 3; + throw LLVMAssertionError("Unknown memory access effect: " + access); +} + +static bool memory_effects_has_errno_mem() { + unsigned major = 0; + LLVMGetVersion(&major, nullptr, nullptr); + return major >= 21; +} + +static uint64_t encode_all_memory_locations(uint64_t access) { + uint64_t encoded = access | (access << 2); + if (memory_effects_has_errno_mem()) { + // LLVM 21 added ErrnoMem between InaccessibleMem and Other. + encoded |= (access << 4) | (access << 6); + } else { + encoded |= access << 4; + } + return encoded; +} + +struct EncodedMemoryLocationEffect { + uint64_t encoded = 0; + unsigned seen_bit = 0; +}; + +static EncodedMemoryLocationEffect +encode_memory_location_effect(const std::string &location, uint64_t access) { + std::string key = normalize_memory_keyword(location); + if (key == "argmem" || key == "arg" || key == "argument" || + key == "argumentmem") { + return {access, 1u << 0}; + } + if (key == "inaccessiblemem" || key == "inaccessible") { + return {access << 2, 1u << 1}; + } + if (key == "errnomem" || key == "errno") { + if (!memory_effects_has_errno_mem()) { + throw LLVMAssertionError( + "memory effect location 'errnomem' requires LLVM 21+"); + } + return {access << 4, 1u << 2}; + } + if (key == "other") { + if (memory_effects_has_errno_mem()) + return {access << 6, 1u << 3}; + return {access << 4, 1u << 2}; + } + throw LLVMAssertionError("Unknown memory effect location: " + location); +} + +static uint64_t encode_memory_effects(const std::string &effects) { + std::string spec = trim_copy(effects); + if (spec.empty()) + throw LLVMAssertionError("memory effects cannot be empty"); + + std::string lowered = lower_copy(spec); + if (lowered.size() >= 8 && lowered.compare(0, 7, "memory(") == 0 && + spec.back() == ')') { + spec = trim_copy(spec.substr(7, spec.size() - 8)); + } + + if (spec.find(':') == std::string::npos) { + return encode_all_memory_locations(memory_access_bits(spec)); + } + + uint64_t encoded = 0; + unsigned seen_locations = 0; + size_t pos = 0; + while (pos <= spec.size()) { + size_t comma = spec.find(',', pos); + std::string part = trim_copy(spec.substr( + pos, comma == std::string::npos ? std::string::npos : comma - pos)); + if (part.empty()) + throw LLVMAssertionError("empty memory effect component in: " + effects); + + size_t colon = part.find(':'); + if (colon == std::string::npos) { + throw LLVMAssertionError( + "memory location effects must use 'location: access': " + part); + } + + std::string location = trim_copy(part.substr(0, colon)); + std::string access_name = trim_copy(part.substr(colon + 1)); + EncodedMemoryLocationEffect effect = + encode_memory_location_effect(location, memory_access_bits(access_name)); + if (seen_locations & effect.seen_bit) + throw LLVMAssertionError("duplicate memory effect location: " + location); + seen_locations |= effect.seen_bit; + encoded |= effect.encoded; + + if (comma == std::string::npos) + break; + pos = comma + 1; + } + return encoded; +} + // ============================================================================= // Diagnostic Information // ============================================================================= @@ -224,6 +370,92 @@ struct ValidityToken { bool is_valid() const { return valid.load(); } }; +struct ModuleTokenRegistry { + std::mutex mutex; + std::unordered_map> tokens; + + static ModuleTokenRegistry &instance() { + static ModuleTokenRegistry registry; + return registry; + } + + void register_module(LLVMModuleRef mod, + const std::shared_ptr &token) { + if (!mod || !token) + return; + std::lock_guard lock(mutex); + tokens[mod] = token; + } + + void unregister_module(LLVMModuleRef mod) { + if (!mod) + return; + std::lock_guard lock(mutex); + tokens.erase(mod); + } + + std::shared_ptr lookup(LLVMModuleRef mod) { + if (!mod) + return nullptr; + std::lock_guard lock(mutex); + auto it = tokens.find(mod); + if (it == tokens.end()) + return nullptr; + return it->second.lock(); + } +}; + +static LLVMModuleRef module_for_basic_block_if_parented(LLVMBasicBlockRef bb) { + if (!bb) + return nullptr; + LLVMValueRef fn = LLVMGetBasicBlockParent(bb); + if (!fn) + return nullptr; + return LLVMGetGlobalParent(fn); +} + +static LLVMModuleRef module_for_value_if_parented(LLVMValueRef value) { + if (!value) + return nullptr; + + if (LLVMIsAInstruction(value)) { + LLVMBasicBlockRef bb = LLVMGetInstructionParent(value); + return module_for_basic_block_if_parented(bb); + } + + if (LLVMIsAArgument(value)) { + LLVMValueRef fn = LLVMGetParamParent(value); + if (!fn) + return nullptr; + return LLVMGetGlobalParent(fn); + } + + if (LLVMValueIsBasicBlock(value)) { + LLVMBasicBlockRef bb = LLVMValueAsBasicBlock(value); + return module_for_basic_block_if_parented(bb); + } + + if (LLVMIsAGlobalValue(value)) + return LLVMGetGlobalParent(value); + + return nullptr; +} + +static std::shared_ptr module_token_for_module( + LLVMModuleRef mod) { + return ModuleTokenRegistry::instance().lookup(mod); +} + +static std::shared_ptr module_token_for_basic_block( + LLVMBasicBlockRef bb) { + return module_token_for_module(module_for_basic_block_if_parented(bb)); +} + +static std::shared_ptr module_token_for_value( + LLVMValueRef value) { + return module_token_for_module(module_for_value_if_parented(value)); +} + // ============================================================================= // Base class to prevent copy // ============================================================================= @@ -244,6 +476,11 @@ struct LLVMTypeWrapper; struct LLVMValueWrapper; struct LLVMFunctionWrapper; struct LLVMAttributeAccessorWrapper; +struct LLVMMetadataMapWrapper; +struct LLVMNamedMetadataMapWrapper; +struct LLVMNamedMetadataListWrapper; +struct LLVMModuleFlagsWrapper; +struct LLVMBuilderDebugLocationManager; struct LLVMBasicBlockWrapper; struct LLVMBuilderWrapper; struct LLVMModuleManager; @@ -251,6 +488,8 @@ struct LLVMBuilderManager; struct LLVMDIBuilderManager; struct LLVMTargetMachineWrapper; struct LLVMPassBuilderOptionsWrapper; +struct LLVMJITWrapper; +struct LLVMCtypesFunctionWrapper; struct LLVMNamedMDNodeWrapper; struct LLVMOperandBundleWrapper; struct LLVMUseWrapper; @@ -263,11 +502,14 @@ struct LLVMDbgRecordWrapper; struct LLVMOperandBundleWrapper { LLVMOperandBundleRef m_ref = nullptr; std::shared_ptr m_context_token; + std::vector> m_module_tokens; LLVMOperandBundleWrapper() = default; - LLVMOperandBundleWrapper(LLVMOperandBundleRef ref, - std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMOperandBundleWrapper( + LLVMOperandBundleRef ref, std::shared_ptr context_token, + std::vector> module_tokens = {}) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_tokens(std::move(module_tokens)) {} ~LLVMOperandBundleWrapper() { if (m_ref) { @@ -281,7 +523,8 @@ struct LLVMOperandBundleWrapper { LLVMOperandBundleWrapper & operator=(const LLVMOperandBundleWrapper &) = delete; LLVMOperandBundleWrapper(LLVMOperandBundleWrapper &&other) noexcept - : m_ref(other.m_ref), m_context_token(std::move(other.m_context_token)) { + : m_ref(other.m_ref), m_context_token(std::move(other.m_context_token)), + m_module_tokens(std::move(other.m_module_tokens)) { other.m_ref = nullptr; } LLVMOperandBundleWrapper & @@ -291,6 +534,7 @@ struct LLVMOperandBundleWrapper { LLVMDisposeOperandBundle(m_ref); m_ref = other.m_ref; m_context_token = std::move(other.m_context_token); + m_module_tokens = std::move(other.m_module_tokens); other.m_ref = nullptr; } return *this; @@ -301,6 +545,10 @@ struct LLVMOperandBundleWrapper { throw LLVMMemoryError("OperandBundle is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("OperandBundle used after context was destroyed"); + for (const auto &token : m_module_tokens) { + if (token && !token->is_valid()) + throw LLVMMemoryError("OperandBundle used after module was disposed"); + } } std::string get_tag() const { @@ -864,6 +1112,16 @@ struct LLVMTypeFactoryWrapper { throw LLVMMemoryError("TypeFactory used after context was destroyed"); } + bool operator==(const LLVMTypeFactoryWrapper &other) const { + check_valid(); + other.check_valid(); + return m_ctx_ref == other.m_ctx_ref; + } + + bool operator!=(const LLVMTypeFactoryWrapper &other) const { + return !(*this == other); + } + // ========================================================================= // Fixed-width integer types (properties) // ========================================================================= @@ -1161,11 +1419,14 @@ struct LLVMTypeFactoryWrapper { struct LLVMNamedMDNodeWrapper { LLVMNamedMDNodeRef m_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; LLVMNamedMDNodeWrapper() = default; - LLVMNamedMDNodeWrapper(LLVMNamedMDNodeRef ref, - std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMNamedMDNodeWrapper( + LLVMNamedMDNodeRef ref, std::shared_ptr context_token, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} bool operator==(const LLVMNamedMDNodeWrapper &other) const { return m_ref == other.m_ref; @@ -1179,6 +1440,8 @@ struct LLVMNamedMDNodeWrapper { throw LLVMMemoryError("NamedMDNode is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("NamedMDNode used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("NamedMDNode used after module was disposed"); } std::string get_name() const { @@ -1193,7 +1456,7 @@ struct LLVMNamedMDNodeWrapper { LLVMNamedMDNodeRef next_md = LLVMGetNextNamedMetadata(m_ref); if (!next_md) return std::nullopt; - return LLVMNamedMDNodeWrapper(next_md, m_context_token); + return LLVMNamedMDNodeWrapper(next_md, m_context_token, m_module_token); } std::optional prev() { @@ -1201,7 +1464,7 @@ struct LLVMNamedMDNodeWrapper { LLVMNamedMDNodeRef prev_md = LLVMGetPreviousNamedMetadata(m_ref); if (!prev_md) return std::nullopt; - return LLVMNamedMDNodeWrapper(prev_md, m_context_token); + return LLVMNamedMDNodeWrapper(prev_md, m_context_token, m_module_token); } }; @@ -1212,16 +1475,23 @@ struct LLVMNamedMDNodeWrapper { struct LLVMUseWrapper { LLVMUseRef m_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; LLVMUseWrapper() = default; - LLVMUseWrapper(LLVMUseRef ref, std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMUseWrapper(LLVMUseRef ref, std::shared_ptr context_token, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_token(module_token ? std::move(module_token) + : module_token_for_value( + ref ? LLVMGetUser(ref) : nullptr)) {} void check_valid() const { if (!m_ref) throw LLVMMemoryError("Use is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("Use used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("Use used after module was disposed"); } // get_user, get_used_value, and get_operand_index are implemented after @@ -1242,11 +1512,14 @@ struct LLVMUseWrapper { struct LLVMDbgRecordWrapper { LLVMDbgRecordRef m_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; LLVMDbgRecordWrapper() = default; - LLVMDbgRecordWrapper(LLVMDbgRecordRef ref, - std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMDbgRecordWrapper( + LLVMDbgRecordRef ref, std::shared_ptr context_token, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} bool operator==(const LLVMDbgRecordWrapper &other) const { return m_ref == other.m_ref; @@ -1260,6 +1533,8 @@ struct LLVMDbgRecordWrapper { throw LLVMMemoryError("DbgRecord is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("DbgRecord used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("DbgRecord used after module was disposed"); } std::optional next() const { @@ -1267,7 +1542,7 @@ struct LLVMDbgRecordWrapper { LLVMDbgRecordRef next_ref = LLVMGetNextDbgRecord(m_ref); if (!next_ref) return std::nullopt; - return LLVMDbgRecordWrapper(next_ref, m_context_token); + return LLVMDbgRecordWrapper(next_ref, m_context_token, m_module_token); } std::optional prev() const { @@ -1275,7 +1550,7 @@ struct LLVMDbgRecordWrapper { LLVMDbgRecordRef prev_ref = LLVMGetPreviousDbgRecord(m_ref); if (!prev_ref) return std::nullopt; - return LLVMDbgRecordWrapper(prev_ref, m_context_token); + return LLVMDbgRecordWrapper(prev_ref, m_context_token, m_module_token); } }; @@ -1286,10 +1561,14 @@ struct LLVMDbgRecordWrapper { struct LLVMValueWrapper { LLVMValueRef m_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; LLVMValueWrapper() = default; - LLVMValueWrapper(LLVMValueRef ref, std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMValueWrapper(LLVMValueRef ref, std::shared_ptr context_token, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_token(module_token ? std::move(module_token) + : module_token_for_value(ref)) {} bool operator==(const LLVMValueWrapper &other) const { return m_ref == other.m_ref; @@ -1303,6 +1582,8 @@ struct LLVMValueWrapper { throw LLVMMemoryError("Value is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("Value used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("Value used after module was disposed"); } static const char *opcode_name(LLVMOpcode op) { @@ -2816,7 +3097,11 @@ struct LLVMValueWrapper { throw LLVMAssertionError("get_operand_bundle_at_index: operand bundle at index " + std::to_string(index) + " is null"); } - return LLVMOperandBundleWrapper(bundle, m_context_token); + std::vector> module_tokens; + if (m_module_token) + module_tokens.push_back(m_module_token); + return LLVMOperandBundleWrapper(bundle, m_context_token, + std::move(module_tokens)); } // Get indices for extractvalue/insertvalue @@ -3249,6 +3534,19 @@ struct LLVMValueWrapper { // LLVMMetadataWrapper LLVMMetadataWrapper as_metadata() const; + LLVMMetadataMapWrapper metadata() const; + + LLVMTypeFactoryWrapper types() const { + check_valid(); + LLVMTypeRef ty = LLVMTypeOf(m_ref); + if (!ty) + throw LLVMAssertionError("Value has no type context"); + LLVMContextRef ctx_ref = LLVMGetTypeContext(ty); + if (!ctx_ref) + throw LLVMAssertionError("Value has no type context"); + return LLVMTypeFactoryWrapper(ctx_ref, m_context_token); + } + // Unified set_metadata - works for both instructions and globals // Declared here, implemented after LLVMMetadataWrapper // Takes a context for converting metadata to value (needed for instructions) @@ -3275,7 +3573,7 @@ struct LLVMValueWrapper { LLVMDbgRecordRef ref = LLVMGetFirstDbgRecord(m_ref); if (!ref) return std::nullopt; - return LLVMDbgRecordWrapper(ref, m_context_token); + return LLVMDbgRecordWrapper(ref, m_context_token, m_module_token); } std::optional last_dbg_record() const { @@ -3286,7 +3584,7 @@ struct LLVMValueWrapper { LLVMDbgRecordRef ref = LLVMGetLastDbgRecord(m_ref); if (!ref) return std::nullopt; - return LLVMDbgRecordWrapper(ref, m_context_token); + return LLVMDbgRecordWrapper(ref, m_context_token, m_module_token); } // Create a builder positioned before this instruction @@ -3300,11 +3598,15 @@ struct LLVMValueWrapper { struct LLVMBasicBlockWrapper { LLVMBasicBlockRef m_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; LLVMBasicBlockWrapper() = default; - LLVMBasicBlockWrapper(LLVMBasicBlockRef ref, - std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMBasicBlockWrapper( + LLVMBasicBlockRef ref, std::shared_ptr context_token, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_token(module_token ? std::move(module_token) + : module_token_for_basic_block(ref)) {} bool operator==(const LLVMBasicBlockWrapper &other) const { return m_ref == other.m_ref; @@ -3318,6 +3620,8 @@ struct LLVMBasicBlockWrapper { throw LLVMMemoryError("BasicBlock is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("BasicBlock used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("BasicBlock used after module was disposed"); } std::string get_name() const { @@ -3746,6 +4050,17 @@ struct LLVMBasicBlockWrapper { return result; } + LLVMTypeFactoryWrapper types() const { + check_valid(); + LLVMTypeRef ty = LLVMTypeOf(LLVMBasicBlockAsValue(m_ref)); + if (!ty) + throw LLVMAssertionError("BasicBlock has no type context"); + LLVMContextRef ctx_ref = LLVMGetTypeContext(ty); + if (!ctx_ref) + throw LLVMAssertionError("BasicBlock has no type context"); + return LLVMTypeFactoryWrapper(ctx_ref, m_context_token); + } + // Create a builder positioned at the end of this basic block, or before the // first non-PHI instruction when requested. LLVMBuilderManager *create_builder(bool first_non_phi = false) const; @@ -3757,8 +4072,11 @@ struct LLVMBasicBlockWrapper { struct LLVMFunctionWrapper : LLVMValueWrapper { LLVMFunctionWrapper() = default; - LLVMFunctionWrapper(LLVMValueRef ref, std::shared_ptr token) - : LLVMValueWrapper(ref, std::move(token)) {} + LLVMFunctionWrapper( + LLVMValueRef ref, std::shared_ptr context_token, + std::shared_ptr module_token = nullptr) + : LLVMValueWrapper(ref, std::move(context_token), + std::move(module_token)) {} unsigned param_count() const { check_valid(); @@ -3886,6 +4204,7 @@ struct LLVMFunctionWrapper : LLVMValueWrapper { "append_existing_basic_block requires an unattached basic block"); } LLVMAppendExistingBasicBlock(m_ref, bb.m_ref); + const_cast(bb).m_module_token = m_module_token; } void erase() { @@ -3951,6 +4270,12 @@ struct LLVMFunctionWrapper : LLVMValueWrapper { // Get the context this function belongs to (derived via module) LLVMContextWrapper *context() const; + LLVMTypeFactoryWrapper types() const { return LLVMValueWrapper::types(); } + + // Create a builder positioned at the end of this function's entry block. + // If the function has no blocks yet, create an entry block first. + LLVMBuilderManager *create_builder(bool first_non_phi = false) const; + // ========================================================================= // Function verification (Analysis.h) // ========================================================================= @@ -3963,6 +4288,9 @@ struct LLVMFunctionWrapper : LLVMValueWrapper { return !LLVMVerifyFunction(m_ref, LLVMReturnStatusAction); } + void optimize(const std::string &pipeline, LLVMTargetMachineWrapper *tm, + LLVMPassBuilderOptionsWrapper *opts); + /// Verify this function and print any errors to stderr. /// Wraps LLVMVerifyFunction with LLVMPrintMessageAction. bool verify_and_print() const { @@ -4058,16 +4386,19 @@ enum class LLVMAttributeAccessorTarget { Function, CallSite }; struct LLVMAttributeAccessorWrapper { LLVMValueRef m_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; unsigned m_idx = LLVMAttributeReturnIndex; LLVMAttributeAccessorTarget m_target = LLVMAttributeAccessorTarget::Function; LLVMAttributeAccessorWrapper() = default; - LLVMAttributeAccessorWrapper(LLVMValueRef ref, - std::shared_ptr token, - unsigned idx, - LLVMAttributeAccessorTarget target) - : m_ref(ref), m_context_token(std::move(token)), m_idx(idx), - m_target(target) {} + LLVMAttributeAccessorWrapper( + LLVMValueRef ref, std::shared_ptr context_token, + unsigned idx, LLVMAttributeAccessorTarget target, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_context_token(std::move(context_token)), + m_module_token(module_token ? std::move(module_token) + : module_token_for_value(ref)), + m_idx(idx), m_target(target) {} static unsigned normalize_function_index(LLVMValueRef fn, const char *api_name, int idx) { @@ -4152,6 +4483,8 @@ struct LLVMAttributeAccessorWrapper { if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError( "AttributeAccessor used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("AttributeAccessor used after module was disposed"); if (m_target == LLVMAttributeAccessorTarget::Function) { if (!LLVMIsAFunction(m_ref)) { throw LLVMAssertionError( @@ -4262,6 +4595,10 @@ struct LLVMAttributeAccessorWrapper { add(wrapped); } + void add_memory(const std::string &effects = "none") { + add("memory", encode_memory_effects(effects)); + } + void add_type(const std::string &name, const LLVMTypeWrapper &type) { check_valid(); type.check_valid(); @@ -4452,7 +4789,7 @@ inline LLVMValueWrapper LLVMUseWrapper::get_user() const { LLVMValueRef user = LLVMGetUser(m_ref); if (!user) throw LLVMAssertionError("Use has no user"); - return LLVMValueWrapper(user, m_context_token); + return LLVMValueWrapper(user, m_context_token, m_module_token); } inline LLVMValueWrapper LLVMUseWrapper::get_used_value() const { @@ -4460,7 +4797,7 @@ inline LLVMValueWrapper LLVMUseWrapper::get_used_value() const { LLVMValueRef used = LLVMGetUsedValue(m_ref); if (!used) throw LLVMAssertionError("Use has no used value"); - return LLVMValueWrapper(used, m_context_token); + return LLVMValueWrapper(used, m_context_token, m_module_token); } // Implementation of LLVMUseWrapper::get_operand_index @@ -4778,6 +5115,12 @@ struct LLVMBuilderWrapper : NoMoveCopy { LLVMModuleWrapper *module() const; LLVMContextWrapper *context() const; + LLVMBuilderDebugLocationManager *debug_location( + const LLVMMetadataWrapper &loc) const; + LLVMBuilderDebugLocationManager *debug_location( + unsigned line, unsigned column, const LLVMMetadataWrapper &scope, + const LLVMMetadataWrapper *inlined_at) const; + void position_at(const LLVMBasicBlockWrapper &bb, const LLVMValueWrapper &inst) { check_valid(); @@ -5135,14 +5478,14 @@ struct LLVMBuilderWrapper : NoMoveCopy { m_context_token); } - LLVMValueWrapper build_array_alloca(const LLVMTypeWrapper &ty, - const LLVMValueWrapper &size, - const std::string &name = "") { + LLVMValueWrapper build_alloca(const LLVMTypeWrapper &ty, + const LLVMValueWrapper &count, + const std::string &name = "") { check_valid(); ty.check_valid(); - size.check_valid(); + count.check_valid(); return LLVMValueWrapper( - LLVMBuildArrayAlloca(m_ref, ty.m_ref, size.m_ref, name.c_str()), + LLVMBuildArrayAlloca(m_ref, ty.m_ref, count.m_ref, name.c_str()), m_context_token); } @@ -5572,6 +5915,70 @@ struct LLVMBuilderWrapper : NoMoveCopy { m_context_token); } + LLVMValueWrapper intrinsic(const std::string &intrinsic_name, + const Iterable &args, + const Iterable &overloaded_types, + const std::string &name_hint = "") { + check_valid(); + if (intrinsic_name.empty()) + throw LLVMAssertionError("intrinsic name cannot be empty"); + + unsigned id = + LLVMLookupIntrinsicID(intrinsic_name.c_str(), intrinsic_name.size()); + if (id == 0) { + throw LLVMAssertionError("Unknown LLVM intrinsic: " + intrinsic_name); + } + if (LLVMIntrinsicIsOverloaded(id) && overloaded_types.empty()) { + throw LLVMAssertionError("Intrinsic '" + intrinsic_name + + "' is overloaded; pass overloaded_types=[...]"); + } + + LLVMBasicBlockRef bb = LLVMGetInsertBlock(m_ref); + if (!bb) + throw LLVMAssertionError("intrinsic requires a builder insertion block"); + LLVMValueRef parent_fn = LLVMGetBasicBlockParent(bb); + if (!parent_fn) + throw LLVMAssertionError( + "intrinsic requires an insertion block in a function"); + LLVMModuleRef mod_ref = LLVMGetGlobalParent(parent_fn); + if (!mod_ref) + throw LLVMAssertionError( + "intrinsic requires an insertion function in a module"); + + std::vector type_refs; + type_refs.reserve(overloaded_types.size()); + for (const auto &ty : overloaded_types) { + ty.check_valid(); + type_refs.push_back(ty.m_ref); + } + + LLVMValueRef decl = LLVMGetIntrinsicDeclaration( + mod_ref, id, type_refs.data(), type_refs.size()); + if (!decl) { + throw LLVMError("Failed to get declaration for intrinsic '" + + intrinsic_name + "'"); + } + + std::vector arg_refs; + arg_refs.reserve(args.size()); + for (const auto &arg : args) { + arg.check_valid(); + arg_refs.push_back(arg.m_ref); + } + + LLVMTypeRef func_ty = LLVMGlobalGetValueType(decl); + LLVMTypeRef ret_ty = LLVMGetReturnType(func_ty); + if (LLVMGetTypeKind(ret_ty) == LLVMVoidTypeKind && !name_hint.empty()) { + throw LLVMAssertionError("Cannot name call to intrinsic returning void"); + } + + return LLVMValueWrapper( + LLVMBuildCall2(m_ref, func_ty, decl, arg_refs.data(), + static_cast(arg_refs.size()), + name_hint.c_str()), + m_context_token); + } + LLVMValueWrapper unreachable() { check_valid(); return LLVMValueWrapper(LLVMBuildUnreachable(m_ref), m_context_token); @@ -6027,13 +6434,16 @@ struct LLVMModuleWrapper : NoMoveCopy { : m_context_token(std::move(context_token)), m_token(std::make_shared()), m_ctx_ref(ctx) { m_ref = LLVMModuleCreateWithNameInContext(name.c_str(), ctx); + ModuleTokenRegistry::instance().register_module(m_ref, m_token); } // Constructor for wrapping an existing module (from bitcode parsing) LLVMModuleWrapper(LLVMModuleRef mod, LLVMContextRef ctx, std::shared_ptr context_token) : m_ref(mod), m_context_token(std::move(context_token)), - m_token(std::make_shared()), m_ctx_ref(ctx) {} + m_token(std::make_shared()), m_ctx_ref(ctx) { + ModuleTokenRegistry::instance().register_module(m_ref, m_token); + } // Constructor for non-owning (borrowed) reference to an existing module. // Used by function.module to return the function's parent module. @@ -6045,6 +6455,8 @@ struct LLVMModuleWrapper : NoMoveCopy { auto *wrapper = new LLVMModuleWrapper(); wrapper->m_ref = mod; wrapper->m_context_token = std::move(context_token); + if (!module_token) + module_token = module_token_for_module(mod); wrapper->m_token = module_token ? std::move(module_token) : std::make_shared(); wrapper->m_ctx_ref = ctx; @@ -6054,6 +6466,7 @@ struct LLVMModuleWrapper : NoMoveCopy { ~LLVMModuleWrapper() { if (m_ref && !m_borrowed) { + ModuleTokenRegistry::instance().unregister_module(m_ref); // Only dispose the module if its context is still alive. // If the context was already destroyed, the module memory is already // freed and calling LLVMDisposeModule would cause a use-after-free crash. @@ -6088,10 +6501,19 @@ struct LLVMModuleWrapper : NoMoveCopy { void dispose() { if (m_ref) { - LLVMDisposeModule(m_ref); + if (!m_borrowed) { + ModuleTokenRegistry::instance().unregister_module(m_ref); + if (m_context_token && m_context_token->is_valid()) { + LLVMDisposeModule(m_ref); + } else { + fprintf(stderr, "Warning: LLVM Module outlived its Context. " + "This may cause a memory leak. " + "Ensure modules are deleted before their context.\n"); + } + } m_ref = nullptr; } - if (m_token) { + if (m_token && !m_borrowed) { m_token->invalidate(); } } @@ -6387,6 +6809,7 @@ struct LLVMModuleWrapper : NoMoveCopy { throw LLVMError("Failed to link modules"); } // LLVMLinkModules2 destroys the source module, so mark it as disposed + ModuleTokenRegistry::instance().unregister_module(src.m_ref); src.m_ref = nullptr; if (src.m_token) { src.m_token->invalidate(); @@ -6539,7 +6962,7 @@ struct LLVMModuleWrapper : NoMoveCopy { LLVMNamedMDNodeRef md = LLVMGetFirstNamedMetadata(m_ref); if (!md) return std::nullopt; - return LLVMNamedMDNodeWrapper(md, m_context_token); + return LLVMNamedMDNodeWrapper(md, m_context_token, m_token); } std::optional last_named_metadata() { @@ -6547,7 +6970,7 @@ struct LLVMModuleWrapper : NoMoveCopy { LLVMNamedMDNodeRef md = LLVMGetLastNamedMetadata(m_ref); if (!md) return std::nullopt; - return LLVMNamedMDNodeWrapper(md, m_context_token); + return LLVMNamedMDNodeWrapper(md, m_context_token, m_token); } std::optional @@ -6557,14 +6980,14 @@ struct LLVMModuleWrapper : NoMoveCopy { LLVMGetNamedMetadata(m_ref, name.c_str(), name.size()); if (!md) return std::nullopt; - return LLVMNamedMDNodeWrapper(md, m_context_token); + return LLVMNamedMDNodeWrapper(md, m_context_token, m_token); } LLVMNamedMDNodeWrapper add_named_metadata(const std::string &name) { check_valid(); return LLVMNamedMDNodeWrapper( LLVMGetOrInsertNamedMetadata(m_ref, name.c_str(), name.size()), - m_context_token); + m_context_token, m_token); } unsigned get_named_metadata_num_operands(const std::string &name) { @@ -6619,6 +7042,14 @@ struct LLVMModuleWrapper : NoMoveCopy { /// Get a module-level flag by key. std::optional get_module_flag(const std::string &key); + LLVMNamedMetadataMapWrapper named_metadata() const; + LLVMModuleFlagsWrapper module_flags() const; + + LLVMTypeFactoryWrapper types() const { + check_valid(); + return LLVMTypeFactoryWrapper(m_ctx_ref, m_context_token); + } + // ========================================================================= // Module API Refactor: Methods moved from global functions // ========================================================================= @@ -6666,6 +7097,12 @@ struct LLVMModuleWrapper : NoMoveCopy { void run_passes(const std::string &passes, LLVMTargetMachineWrapper *tm, LLVMPassBuilderOptionsWrapper *opts); + void optimize(const std::string &pipeline, LLVMTargetMachineWrapper *tm, + LLVMPassBuilderOptionsWrapper *opts); + + nb::bytes emit_object(LLVMTargetMachineWrapper *tm = nullptr); + nb::bytes emit_assembly(LLVMTargetMachineWrapper *tm = nullptr); + // Clone - returns a ModuleManager that must be used with 'with' or .dispose() LLVMModuleManager *clone() const; @@ -7070,6 +7507,9 @@ struct LLVMContextWrapper : NoMoveCopy { LLVMMetadataWrapper create_debug_location( unsigned line, unsigned column, const LLVMMetadataWrapper &scope, const LLVMMetadataWrapper *inlined_at); + LLVMMetadataWrapper debug_location(unsigned line, unsigned column, + const LLVMMetadataWrapper &scope, + const LLVMMetadataWrapper *inlined_at); // Module creation (returns context manager) - defined after LLVMModuleManager LLVMModuleManager *create_module(const std::string &name); @@ -7092,18 +7532,25 @@ struct LLVMContextWrapper : NoMoveCopy { struct LLVMContextManager : NoMoveCopy { std::unique_ptr m_context; + bool m_entered = false; + bool m_disposed = false; LLVMContextWrapper &enter() { - if (m_context) + if (m_entered) throw LLVMMemoryError("Context manager already entered"); + if (m_disposed) + throw LLVMMemoryError("Context manager has already exited"); m_context = std::make_unique(); + m_entered = true; return *m_context; } void exit(const nb::object &, const nb::object &, const nb::object &) { - if (!m_context) + if (!m_entered || !m_context) throw LLVMMemoryError("Context manager not entered"); - m_context.reset(); + m_context->dispose(); + m_entered = false; + m_disposed = true; } }; @@ -7160,7 +7607,7 @@ struct LLVMModuleManager : NoMoveCopy { throw LLVMMemoryError("Module has already been disposed"); if (!m_entered) throw LLVMMemoryError("Module manager was not entered"); - m_module.reset(); + m_module->dispose(); m_disposed = true; } @@ -7239,7 +7686,7 @@ struct LLVMBuilderManager : NoMoveCopy { throw LLVMMemoryError("Builder has already been disposed"); if (!m_entered) throw LLVMMemoryError("Builder manager was not entered"); - m_builder.reset(); + m_builder->dispose(); m_disposed = true; } @@ -7268,7 +7715,12 @@ LLVMBuilderManager * LLVMContextWrapper::create_builder(const LLVMBasicBlockWrapper &bb) { check_valid(); bb.check_valid(); + if (bb.m_context_token != m_token) { + throw LLVMAssertionError( + "Context.create_builder requires a basic block from this context"); + } auto manager = new LLVMBuilderManager(this); + manager->m_module_token = bb.m_module_token; manager->m_initial_bb = bb.m_ref; return manager; } @@ -7283,7 +7735,12 @@ LLVMContextWrapper::create_builder(const LLVMValueWrapper &inst, if (!LLVMGetInstructionParent(inst.m_ref)) throw LLVMAssertionError( "create_builder requires an instruction in a basic block"); + if (inst.m_context_token != m_token) { + throw LLVMAssertionError( + "Context.create_builder requires an instruction from this context"); + } auto manager = new LLVMBuilderManager(this); + manager->m_module_token = inst.m_module_token; manager->m_initial_inst = inst.m_ref; manager->m_before_dbg = before_dbg; return manager; @@ -7302,6 +7759,7 @@ LLVMBuilderManager *LLVMBasicBlockWrapper::create_builder( LLVMContextWrapper *ctx = const_cast(this)->context(); auto manager = new LLVMBuilderManager(ctx); + manager->m_module_token = m_module_token; if (first_non_phi) { LLVMValueRef inst = LLVMGetFirstInstruction(m_ref); @@ -7320,6 +7778,25 @@ LLVMBuilderManager *LLVMBasicBlockWrapper::create_builder( return manager; } +LLVMBuilderManager *LLVMFunctionWrapper::create_builder( + bool first_non_phi) const { + check_valid(); + LLVMModuleRef mod_ref = LLVMGetGlobalParent(m_ref); + if (!mod_ref) + throw LLVMAssertionError("create_builder requires a function in a module"); + + LLVMBasicBlockRef entry = nullptr; + if (LLVMCountBasicBlocks(m_ref) == 0) { + LLVMContextRef ctx_ref = LLVMGetModuleContext(mod_ref); + entry = LLVMAppendBasicBlockInContext(ctx_ref, m_ref, "entry"); + } else { + entry = LLVMGetEntryBasicBlock(m_ref); + } + + LLVMBasicBlockWrapper entry_wrapper(entry, m_context_token, m_module_token); + return entry_wrapper.create_builder(first_non_phi); +} + LLVMBuilderManager *LLVMValueWrapper::create_builder(bool before_dbg) const { check_valid(); if (!is_a_instruction()) @@ -7330,6 +7807,7 @@ LLVMBuilderManager *LLVMValueWrapper::create_builder(bool before_dbg) const { // Get the context for this value LLVMContextWrapper *ctx = get_context(); auto manager = new LLVMBuilderManager(ctx); + manager->m_module_token = m_module_token; manager->m_initial_inst = m_ref; manager->m_before_dbg = before_dbg; return manager; @@ -7467,6 +7945,8 @@ LLVMModuleManager *LLVMModuleWrapper::clone() const { raw_wrapper->m_context_token = m_context_token; raw_wrapper->m_token = std::make_shared(); raw_wrapper->m_ctx_ref = m_ctx_ref; + ModuleTokenRegistry::instance().register_module(cloned_ref, + raw_wrapper->m_token); // Wrap in unique_ptr and return a manager that owns the cloned module return new LLVMModuleManager(std::unique_ptr(raw_wrapper)); @@ -8010,27 +8490,6 @@ struct LLVMTargetWrapper { } }; -// Target initialization functions -void initialize_all_target_infos() { LLVMInitializeAllTargetInfos(); } - -void initialize_all_targets() { LLVMInitializeAllTargets(); } - -void initialize_all_target_mcs() { LLVMInitializeAllTargetMCs(); } - -void initialize_all_asm_printers() { LLVMInitializeAllAsmPrinters(); } - -void initialize_all_asm_parsers() { LLVMInitializeAllAsmParsers(); } - -void initialize_all_disassemblers() { LLVMInitializeAllDisassemblers(); } - -// Convenience: initialize all targets, MCs, asm printers, and asm parsers -void initialize_all() { - LLVMInitializeAllTargetInfos(); - LLVMInitializeAllTargets(); - LLVMInitializeAllTargetMCs(); - LLVMInitializeAllAsmPrinters(); - LLVMInitializeAllAsmParsers(); -} std::optional get_first_target() { LLVMTargetRef ref = LLVMGetFirstTarget(); @@ -8094,21 +8553,6 @@ std::string get_host_cpu_features() { return result; } -// Native target initialization -bool initialize_native_target() { return LLVMInitializeNativeTarget() == 0; } - -bool initialize_native_asm_printer() { - return LLVMInitializeNativeAsmPrinter() == 0; -} - -bool initialize_native_asm_parser() { - return LLVMInitializeNativeAsmParser() == 0; -} - -bool initialize_native_disassembler() { - return LLVMInitializeNativeDisassembler() == 0; -} - // ============================================================================= // Target Data Wrapper // ============================================================================= @@ -8362,6 +8806,46 @@ LLVMTargetMachineWrapper *create_target_machine(const LLVMTargetWrapper &target, return new LLVMTargetMachineWrapper(ref); } +LLVMTargetMachineWrapper *create_host_target_machine( + const std::string &cpu = "", const std::string &features = "", + LLVMCodeGenOptLevel opt_level = LLVMCodeGenLevelDefault, + LLVMRelocMode reloc_mode = LLVMRelocDefault, + LLVMCodeModel code_model = LLVMCodeModelDefault) { + if (LLVMInitializeNativeTarget() != 0) + throw LLVMError("Failed to initialize native target"); + if (LLVMInitializeNativeAsmPrinter() != 0) + throw LLVMError("Failed to initialize native ASM printer"); + + char *triple_msg = LLVMGetDefaultTargetTriple(); + std::string triple = triple_msg ? triple_msg : ""; + if (triple_msg) + LLVMDisposeMessage(triple_msg); + if (triple.empty()) + throw LLVMError("Failed to determine default target triple"); + + LLVMTargetRef target_ref = nullptr; + char *error = nullptr; + if (LLVMGetTargetFromTriple(triple.c_str(), &target_ref, &error)) { + std::string msg = error ? error : "Unknown error"; + if (error) + LLVMDisposeMessage(error); + throw LLVMError("Failed to get host target from triple '" + triple + + "': " + msg); + } + if (error) + LLVMDisposeMessage(error); + if (!target_ref) + throw LLVMError("Failed to get host target from triple '" + triple + "'"); + + LLVMTargetMachineRef ref = LLVMCreateTargetMachine( + target_ref, triple.c_str(), cpu.c_str(), features.c_str(), opt_level, + reloc_mode, code_model); + if (!ref) + throw LLVMError("Failed to create host target machine for triple '" + + triple + "'"); + return new LLVMTargetMachineWrapper(ref); +} + // ============================================================================= // Pass Builder Options Wrapper // ============================================================================= @@ -8455,12 +8939,21 @@ void run_passes(LLVMModuleWrapper &mod, const std::string &passes, tm_ref = tm->m_ref; } LLVMPassBuilderOptionsRef opts_ref = nullptr; + LLVMPassBuilderOptionsRef owned_opts = nullptr; if (opts) { opts->check_valid(); opts_ref = opts->m_ref; + } else { + // LLVMRunPasses expects a valid options object on some LLVM builds. + owned_opts = LLVMCreatePassBuilderOptions(); + if (!owned_opts) + throw LLVMError("Failed to create pass builder options"); + opts_ref = owned_opts; } LLVMErrorRef err = LLVMRunPasses(mod.m_ref, passes.c_str(), tm_ref, opts_ref); + if (owned_opts) + LLVMDisposePassBuilderOptions(owned_opts); if (err) { char *msg = LLVMGetErrorMessage(err); std::string error_msg = msg ? msg : "Unknown error"; @@ -8475,6 +8968,399 @@ inline void LLVMModuleWrapper::run_passes(const std::string &passes, ::run_passes(*this, passes, tm, opts); } +void run_passes_on_function(LLVMFunctionWrapper &fn, const std::string &passes, + LLVMTargetMachineWrapper *tm, + LLVMPassBuilderOptionsWrapper *opts) { + fn.check_valid(); + if (LLVMIsDeclaration(fn.m_ref)) { + throw LLVMAssertionError( + "Function.optimize requires a function definition (check " + "is_declaration first)"); + } + + LLVMTargetMachineRef tm_ref = nullptr; + if (tm) { + tm->check_valid(); + tm_ref = tm->m_ref; + } + + LLVMPassBuilderOptionsRef opts_ref = nullptr; + LLVMPassBuilderOptionsRef owned_opts = nullptr; + if (opts) { + opts->check_valid(); + opts_ref = opts->m_ref; + } else { + // LLVMRunPassesOnFunction expects a valid options object on some LLVM + // builds. + owned_opts = LLVMCreatePassBuilderOptions(); + if (!owned_opts) + throw LLVMError("Failed to create pass builder options"); + opts_ref = owned_opts; + } + + LLVMErrorRef err = LLVMRunPassesOnFunction(fn.m_ref, passes.c_str(), tm_ref, + opts_ref); + if (owned_opts) + LLVMDisposePassBuilderOptions(owned_opts); + if (err) { + char *msg = LLVMGetErrorMessage(err); + std::string error_msg = msg ? msg : "Unknown error"; + LLVMDisposeErrorMessage(msg); + throw LLVMError("Failed to run function passes: " + error_msg); + } +} + +inline void LLVMFunctionWrapper::optimize( + const std::string &pipeline, LLVMTargetMachineWrapper *tm, + LLVMPassBuilderOptionsWrapper *opts) { + try { + ::run_passes_on_function(*this, pipeline, tm, opts); + } catch (const LLVMError &e) { + throw LLVMError("Failed to optimize function with pipeline '" + pipeline + + "': " + e.what()); + } +} + +inline void LLVMModuleWrapper::optimize(const std::string &pipeline, + LLVMTargetMachineWrapper *tm, + LLVMPassBuilderOptionsWrapper *opts) { + try { + ::run_passes(*this, pipeline, tm, opts); + } catch (const LLVMError &e) { + throw LLVMError("Failed to optimize with pipeline '" + pipeline + + "': " + e.what()); + } +} + +inline nb::bytes LLVMModuleWrapper::emit_object(LLVMTargetMachineWrapper *tm) { + check_valid(); + if (tm) { + tm->check_valid(); + return tm->emit_to_memory_buffer(*this, LLVMObjectFile); + } + std::unique_ptr host_tm(create_host_target_machine()); + return host_tm->emit_to_memory_buffer(*this, LLVMObjectFile); +} + +inline nb::bytes LLVMModuleWrapper::emit_assembly(LLVMTargetMachineWrapper *tm) { + check_valid(); + if (tm) { + tm->check_valid(); + return tm->emit_to_memory_buffer(*this, LLVMAssemblyFile); + } + std::unique_ptr host_tm(create_host_target_machine()); + return host_tm->emit_to_memory_buffer(*this, LLVMAssemblyFile); +} + +// ============================================================================= +// ORC LLJIT Wrapper +// ============================================================================= + +struct LLVMCtypesFunctionWrapper : NoMoveCopy { + nb::object m_func; + std::shared_ptr m_jit_token; + + LLVMCtypesFunctionWrapper(nb::object func, + std::shared_ptr jit_token) + : m_func(std::move(func)), m_jit_token(std::move(jit_token)) {} + + nb::object call(const nb::args &args) const { + if (!m_jit_token || !m_jit_token->is_valid()) { + throw LLVMMemoryError("JIT function called after JIT was disposed"); + } + PyObject *result = PyObject_CallObject(m_func.ptr(), args.ptr()); + if (!result) + throw nb::python_error(); + return nb::steal(result); + } + + nb::object ctypes_callable() const { return m_func; } +}; + +static uint64_t python_address_from_object(const nb::object &value) { + if (PyLong_Check(value.ptr())) { + unsigned long long address = PyLong_AsUnsignedLongLong(value.ptr()); + if (PyErr_Occurred()) { + PyErr_Clear(); + throw LLVMAssertionError( + "symbol address must be a non-negative integer or ctypes object"); + } + return static_cast(address); + } + + nb::object ctypes = nb::module_::import_("ctypes"); + nb::object c_void_p = ctypes.attr("c_void_p"); + nb::object cast = ctypes.attr("cast"); + nb::object address_obj = cast(value, c_void_p).attr("value"); + if (address_obj.is_none()) { + throw LLVMAssertionError( + "ctypes object could not be converted to a non-null address"); + } + unsigned long long address = PyLong_AsUnsignedLongLong(address_obj.ptr()); + if (PyErr_Occurred()) { + PyErr_Clear(); + throw LLVMAssertionError( + "ctypes object address did not fit in an unsigned 64-bit integer"); + } + return static_cast(address); +} + +struct LLVMJITWrapper : NoMoveCopy { + LLVMOrcLLJITRef m_ref = nullptr; + std::shared_ptr m_token = std::make_shared(); + std::vector m_pinned_symbols; + + LLVMJITWrapper() = default; + explicit LLVMJITWrapper(LLVMOrcLLJITRef ref) : m_ref(ref) {} + + ~LLVMJITWrapper() { + m_pinned_symbols.clear(); + if (m_ref) { + LLVMErrorRef err = LLVMOrcDisposeLLJIT(m_ref); + if (err) { + // Destructors must not throw. Consume and drop the error. + consume_llvm_error(err); + } + m_ref = nullptr; + } + if (m_token) + m_token->invalidate(); + } + + void check_valid() const { + if (!m_ref) + throw LLVMMemoryError("JIT has been disposed"); + if (!m_token || !m_token->is_valid()) + throw LLVMMemoryError("JIT has been disposed"); + } + + static LLVMJITWrapper *host() { + if (LLVMInitializeNativeTarget() != 0) + throw LLVMError("Failed to initialize native target for JIT"); + if (LLVMInitializeNativeAsmPrinter() != 0) + throw LLVMError("Failed to initialize native ASM printer for JIT"); + + LLVMOrcLLJITRef jit = nullptr; + LLVMErrorRef err = LLVMOrcCreateLLJIT(&jit, nullptr); + if (err) { + throw LLVMError("Failed to create host JIT: " + consume_llvm_error(err)); + } + if (!jit) + throw LLVMError("Failed to create host JIT"); + return new LLVMJITWrapper(jit); + } + + LLVMJITWrapper &enter() { + check_valid(); + return *this; + } + + void exit(const nb::object &, const nb::object &, const nb::object &) { + dispose(); + } + + void dispose() { + check_valid(); + m_pinned_symbols.clear(); + LLVMErrorRef err = LLVMOrcDisposeLLJIT(m_ref); + m_ref = nullptr; + if (m_token) + m_token->invalidate(); + if (err) { + throw LLVMError("Failed to dispose JIT: " + consume_llvm_error(err)); + } + } + + std::string triple() const { + check_valid(); + const char *triple = LLVMOrcLLJITGetTripleString(m_ref); + return triple ? std::string(triple) : std::string(); + } + + std::string data_layout() const { + check_valid(); + const char *dl = LLVMOrcLLJITGetDataLayoutStr(m_ref); + return dl ? std::string(dl) : std::string(); + } + + void add_module(LLVMModuleWrapper &mod) { + check_valid(); + mod.check_valid(); + if (mod.m_borrowed) { + throw LLVMAssertionError("JIT.add_module requires an owning Module"); + } + + std::string module_name = mod.get_name(); + LLVMMemoryBufferRef bitcode = LLVMWriteBitcodeToMemoryBuffer(mod.m_ref); + if (!bitcode) + throw LLVMError("Failed to serialize module for JIT"); + + LLVMContextRef jit_ctx = LLVMContextCreate(); + LLVMModuleRef jit_mod = nullptr; + LLVMBool failed = LLVMParseBitcodeInContext2(jit_ctx, bitcode, &jit_mod); + LLVMDisposeMemoryBuffer(bitcode); + if (failed) { + LLVMContextDispose(jit_ctx); + throw LLVMError("Failed to parse serialized module for JIT: " + + module_name); + } + + const char *jit_triple = LLVMOrcLLJITGetTripleString(m_ref); + if (jit_triple && std::string(LLVMGetTarget(jit_mod)).empty()) + LLVMSetTarget(jit_mod, jit_triple); + const char *jit_dl = LLVMOrcLLJITGetDataLayoutStr(m_ref); + if (jit_dl && std::string(LLVMGetDataLayoutStr(jit_mod)).empty()) + LLVMSetDataLayout(jit_mod, jit_dl); + + LLVMOrcThreadSafeContextRef tsc = + LLVMOrcCreateNewThreadSafeContextFromLLVMContext(jit_ctx); + if (!tsc) { + LLVMDisposeModule(jit_mod); + // jit_ctx ownership was not transferred if creation failed. + LLVMContextDispose(jit_ctx); + throw LLVMError("Failed to create ORC thread-safe context for JIT"); + } + + LLVMOrcThreadSafeModuleRef tsm = + LLVMOrcCreateNewThreadSafeModule(jit_mod, tsc); + if (!tsm) { + LLVMDisposeModule(jit_mod); + LLVMOrcDisposeThreadSafeContext(tsc); + throw LLVMError("Failed to create ORC thread-safe module for JIT"); + } + + LLVMErrorRef err = LLVMOrcLLJITAddLLVMIRModule( + m_ref, LLVMOrcLLJITGetMainJITDylib(m_ref), tsm); + // The ORC C API documents ThreadSafeContext ownership as shared. The + // ThreadSafeModule/JIT retains the underlying context data; disposing this + // local reference after creating the ThreadSafeModule is required. + LLVMOrcDisposeThreadSafeContext(tsc); + if (err) { + throw LLVMError("Failed to add module to JIT: " + + consume_llvm_error(err)); + } + + mod.dispose(); + } + + uint64_t lookup(const std::string &name) const { + check_valid(); + if (name.empty()) + throw LLVMAssertionError("JIT.lookup requires a non-empty symbol name"); + LLVMOrcExecutorAddress address = 0; + LLVMErrorRef err = LLVMOrcLLJITLookup(m_ref, &address, name.c_str()); + if (err) { + throw LLVMError("Failed to look up JIT symbol '" + name + "': " + + consume_llvm_error(err)); + } + return static_cast(address); + } + + LLVMCtypesFunctionWrapper *ctypes_function(const std::string &name, + nb::object restype, + nb::object argtypes) const { + uint64_t address = lookup(name); + + nb::object ctypes = nb::module_::import_("ctypes"); + nb::object cfunctype = ctypes.attr("CFUNCTYPE"); + + PyObject *argtypes_tuple = PySequence_Tuple(argtypes.ptr()); + if (!argtypes_tuple) + throw nb::python_error(); + + Py_ssize_t arg_count = PyTuple_Size(argtypes_tuple); + if (arg_count < 0) { + Py_DECREF(argtypes_tuple); + throw nb::python_error(); + } + PyObject *signature_args = PyTuple_New(arg_count + 1); + if (!signature_args) { + Py_DECREF(argtypes_tuple); + throw nb::python_error(); + } + + Py_INCREF(restype.ptr()); + PyTuple_SetItem(signature_args, 0, restype.ptr()); + for (Py_ssize_t i = 0; i < arg_count; ++i) { + PyObject *item = PyTuple_GetItem(argtypes_tuple, i); + if (!item) { + Py_DECREF(argtypes_tuple); + Py_DECREF(signature_args); + throw nb::python_error(); + } + Py_INCREF(item); + PyTuple_SetItem(signature_args, i + 1, item); + } + Py_DECREF(argtypes_tuple); + + PyObject *callable_type = PyObject_CallObject(cfunctype.ptr(), signature_args); + Py_DECREF(signature_args); + if (!callable_type) + throw nb::python_error(); + + PyObject *address_obj = PyLong_FromUnsignedLongLong(address); + if (!address_obj) { + Py_DECREF(callable_type); + throw nb::python_error(); + } + PyObject *call_args = PyTuple_Pack(1, address_obj); + Py_DECREF(address_obj); + if (!call_args) { + Py_DECREF(callable_type); + throw nb::python_error(); + } + + PyObject *callable = PyObject_CallObject(callable_type, call_args); + Py_DECREF(callable_type); + Py_DECREF(call_args); + if (!callable) + throw nb::python_error(); + + return new LLVMCtypesFunctionWrapper(nb::steal(callable), + m_token); + } + + void add_symbol(const std::string &name, nb::object target) { + check_valid(); + if (name.empty()) + throw LLVMAssertionError("JIT.add_symbol requires a non-empty symbol name"); + + bool should_pin = !PyLong_Check(target.ptr()); + uint64_t address = python_address_from_object(target); + if (address == 0) + throw LLVMAssertionError("JIT.add_symbol requires a non-null address"); + + LLVMOrcSymbolStringPoolEntryRef symbol = + LLVMOrcLLJITMangleAndIntern(m_ref, name.c_str()); + if (!symbol) + throw LLVMError("Failed to intern JIT symbol '" + name + "'"); + LLVMOrcCSymbolMapPair pair; + pair.Name = symbol; + pair.Sym.Address = static_cast(address); + pair.Sym.Flags.GenericFlags = LLVMJITSymbolGenericFlagsExported | + LLVMJITSymbolGenericFlagsCallable; + pair.Sym.Flags.TargetFlags = 0; + + LLVMOrcMaterializationUnitRef mu = LLVMOrcAbsoluteSymbols(&pair, 1); + if (!mu) { + LLVMOrcReleaseSymbolStringPoolEntry(symbol); + throw LLVMError("Failed to create absolute symbol for JIT symbol '" + + name + "'"); + } + + LLVMErrorRef err = LLVMOrcJITDylibDefine(LLVMOrcLLJITGetMainJITDylib(m_ref), + mu); + if (err) { + LLVMOrcDisposeMaterializationUnit(mu); + throw LLVMError("Failed to add JIT symbol '" + name + "': " + + consume_llvm_error(err)); + } + + if (should_pin) + m_pinned_symbols.push_back(target); + } +}; + // ============================================================================= // Memory Buffer Wrapper // ============================================================================= @@ -9100,7 +9986,7 @@ struct LLVMBinaryManager : NoMoveCopy { throw LLVMMemoryError("Binary has already been disposed"); if (!m_entered) throw LLVMMemoryError("Binary manager was not entered"); - m_binary.reset(); + m_binary->dispose_internal(); m_disposed = true; } @@ -9191,15 +10077,22 @@ struct LLVMMetadataWrapper; struct LLVMDIBuilderWrapper : NoMoveCopy { LLVMDIBuilderRef m_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; + std::shared_ptr m_context_token; std::shared_ptr m_module_token; LLVMDIBuilderWrapper(LLVMModuleRef mod, + std::shared_ptr context_token, std::shared_ptr module_token) - : m_module_token(std::move(module_token)) { + : m_ctx_ref(mod ? LLVMGetModuleContext(mod) : nullptr), + m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) { m_ref = LLVMCreateDIBuilder(mod); } - ~LLVMDIBuilderWrapper() { + ~LLVMDIBuilderWrapper() { dispose(); } + + void dispose() { if (m_ref) { LLVMDisposeDIBuilder(m_ref); m_ref = nullptr; @@ -9209,6 +10102,8 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { void check_valid() const { if (!m_ref) throw LLVMMemoryError("DIBuilder is null"); + if (!m_context_token || !m_context_token->is_valid()) + throw LLVMMemoryError("DIBuilder used after context was destroyed"); if (!m_module_token || !m_module_token->is_valid()) throw LLVMMemoryError("DIBuilder used after module was destroyed"); } @@ -9225,6 +10120,9 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { LLVMMetadataWrapper create_file(const std::string &filename, const std::string &directory); + LLVMMetadataWrapper file(const std::string &filename, + const std::string &directory = "."); + LLVMMetadataWrapper create_compile_unit( int lang, const LLVMMetadataWrapper &file, const std::string &producer, bool is_optimized, const std::string &flags, unsigned runtime_ver, @@ -9232,6 +10130,16 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { bool split_debug_inlining, bool debug_info_for_profiling, const std::string &sys_root, const std::string &sdk); + LLVMMetadataWrapper compile_unit( + LLVMDWARFSourceLanguage language, const LLVMMetadataWrapper &file, + const std::string &producer, bool is_optimized = false, + const std::string &flags = "", unsigned runtime_ver = 0, + const std::string &split_name = "", + LLVMDWARFEmissionKind kind = LLVMDWARFEmissionFull, + unsigned dwo_id = 0, bool split_debug_inlining = true, + bool debug_info_for_profiling = false, const std::string &sys_root = "", + const std::string &sdk = ""); + LLVMMetadataWrapper create_module(const LLVMMetadataWrapper &parent_scope, const std::string &name, const std::string &config_macros, @@ -9263,6 +10171,16 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { const Iterable ¶m_types, unsigned flags); + LLVMMetadataWrapper function( + LLVMFunctionWrapper &fn, const std::string &name, + const LLVMMetadataWrapper &file, unsigned line, + const LLVMTypeWrapper &return_type, + const Iterable ¶m_types, + const LLVMMetadataWrapper *scope = nullptr, + const std::string &linkage_name = "", bool is_local_to_unit = false, + bool is_definition = true, unsigned scope_line = 0, unsigned flags = 0, + bool is_optimized = false); + // ========================================================================= // Type Creation Methods // ========================================================================= @@ -9360,6 +10278,15 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { bool always_preserve, unsigned flags, uint32_t align_in_bits); + LLVMMetadataWrapper local_variable(const LLVMMetadataWrapper &scope, + const std::string &name, + const LLVMMetadataWrapper &file, + unsigned line_no, + const LLVMTypeWrapper &type, + bool always_preserve = true, + unsigned flags = 0, + uint32_t align_in_bits = 0); + LLVMMetadataWrapper create_global_variable_expression( const LLVMMetadataWrapper &scope, const std::string &name, const std::string &linkage, const LLVMMetadataWrapper &file, @@ -9586,24 +10513,104 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { struct LLVMMetadataWrapper { LLVMMetadataRef m_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; std::shared_ptr m_context_token; + std::shared_ptr m_module_token; LLVMMetadataWrapper() = default; - LLVMMetadataWrapper(LLVMMetadataRef ref, std::shared_ptr token) - : m_ref(ref), m_context_token(std::move(token)) {} + LLVMMetadataWrapper( + LLVMMetadataRef ref, std::shared_ptr context_token, + LLVMContextRef ctx = nullptr, + std::shared_ptr module_token = nullptr) + : m_ref(ref), m_ctx_ref(ctx), m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} void check_valid() const { if (!m_ref) throw LLVMMemoryError("Metadata is null"); if (!m_context_token || !m_context_token->is_valid()) throw LLVMMemoryError("Metadata used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("Metadata used after module was disposed"); + } + + unsigned kind() const { + check_valid(); + return LLVMGetMetadataKind(m_ref); + } + + bool is_string() const { return kind() == LLVMMDStringMetadataKind; } + + bool is_value() const { + unsigned k = kind(); + return k == LLVMConstantAsMetadataMetadataKind || + k == LLVMLocalAsMetadataMetadataKind; + } + + bool is_node() const { + check_valid(); + if (!m_ctx_ref) + return false; + LLVMValueRef value = LLVMMetadataAsValue(m_ctx_ref, m_ref); + return LLVMIsAMDNode(value) != nullptr; + } + + std::string string() const { + check_valid(); + if (!is_string()) + throw LLVMAssertionError("metadata is not an MDString"); + if (!m_ctx_ref) + throw LLVMAssertionError("metadata string access requires context"); + LLVMValueRef value = LLVMMetadataAsValue(m_ctx_ref, m_ref); + unsigned len = 0; + const char *str = LLVMGetMDString(value, &len); + if (!str) + throw LLVMAssertionError("metadata is not an MDString"); + return std::string(str, len); + } + + LLVMValueWrapper value() const { + check_valid(); + if (!is_value()) + throw LLVMAssertionError( + "metadata is not ConstantAsMetadata or LocalAsMetadata"); + PyErr_SetString(PyExc_NotImplementedError, + "LLVM-C cannot unwrap ValueAsMetadata to Value"); + throw nb::python_error(); + } + + LLVMValueRef as_md_node_value() const { + check_valid(); + if (!m_ctx_ref) + throw LLVMAssertionError("metadata node access requires context"); + LLVMValueRef value = LLVMMetadataAsValue(m_ctx_ref, m_ref); + if (!LLVMIsAMDNode(value)) + throw LLVMAssertionError("metadata is not an MDNode"); + return value; + } + + std::vector operands() const { + LLVMValueRef value = as_md_node_value(); + unsigned count = LLVMGetMDNodeNumOperands(value); + std::vector refs(count); + LLVMGetMDNodeOperands(value, refs.data()); + std::vector result; + result.reserve(count); + for (LLVMValueRef operand : refs) { + if (!operand) + throw LLVMAssertionError("metadata node operand is null"); + result.emplace_back(LLVMValueAsMetadata(operand), m_context_token, + m_ctx_ref, m_module_token); + } + return result; } // Convert metadata to value (requires context) LLVMValueWrapper as_value(LLVMContextWrapper &ctx) const { check_valid(); ctx.check_valid(); - return LLVMValueWrapper(LLVMMetadataAsValue(ctx.m_ref, m_ref), ctx.m_token); + return LLVMValueWrapper(LLVMMetadataAsValue(ctx.m_ref, m_ref), ctx.m_token, + m_module_token); } // Replace all uses of this temporary metadata with another @@ -9620,6 +10627,462 @@ struct LLVMMetadataWrapper { } }; +static LLVMModuleRef module_for_metadata_value(LLVMValueRef value) { + return module_for_value_if_parented(value); +} + +static LLVMContextRef context_for_metadata_value(LLVMValueRef value) { + if (!LLVMIsAInstruction(value) && !LLVMIsAGlobalValue(value)) { + throw LLVMAssertionError( + "metadata view requires an instruction or global value"); + } + + LLVMTypeRef ty = LLVMTypeOf(value); + if (ty) + return LLVMGetTypeContext(ty); + + if (LLVMIsAInstruction(value)) { + LLVMBasicBlockRef bb = LLVMGetInstructionParent(value); + if (!bb) + throw LLVMAssertionError("metadata requires an instruction in a basic block"); + LLVMValueRef fn = LLVMGetBasicBlockParent(bb); + if (!fn) + throw LLVMAssertionError("metadata requires an instruction in a function"); + LLVMModuleRef mod = LLVMGetGlobalParent(fn); + if (!mod) + throw LLVMAssertionError("metadata requires an instruction in a module"); + return LLVMGetModuleContext(mod); + } + + LLVMModuleRef mod = LLVMGetGlobalParent(value); + if (!mod) + throw LLVMAssertionError("metadata requires a global value in a module"); + return LLVMGetModuleContext(mod); +} + +static void require_metadata_context(LLVMContextRef ctx, + const LLVMMetadataWrapper &md, + const char *api_name) { + if (md.m_ctx_ref && md.m_ctx_ref != ctx) { + throw LLVMAssertionError(std::string(api_name) + + " requires metadata from the same context"); + } +} + +static LLVMMetadataRef normalize_metadata_for_attachment( + LLVMContextRef ctx, const LLVMMetadataWrapper &md) { + md.check_valid(); + require_metadata_context(ctx, md, "metadata attachment"); + LLVMMetadataRef metadata_ref = md.m_ref; + LLVMValueRef md_value = LLVMMetadataAsValue(ctx, metadata_ref); + bool is_md_node = LLVMIsAMDNode(md_value) && !LLVMIsAValueAsMetadata(md_value); + if (!is_md_node) { + LLVMMetadataRef refs[] = {metadata_ref}; + metadata_ref = LLVMMDNodeInContext2(ctx, refs, 1); + } + return metadata_ref; +} + +struct LLVMMetadataMapWrapper { + LLVMValueRef m_value_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; + std::shared_ptr m_context_token; + std::shared_ptr m_module_token; + + LLVMMetadataMapWrapper() = default; + LLVMMetadataMapWrapper(LLVMValueRef value, LLVMContextRef ctx, + std::shared_ptr context_token, + std::shared_ptr module_token) + : m_value_ref(value), m_ctx_ref(ctx), + m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} + + void check_valid() const { + if (!m_value_ref) + throw LLVMMemoryError("Metadata view has no value"); + if (!m_ctx_ref) + throw LLVMMemoryError("Metadata view has no context"); + if (!m_context_token || !m_context_token->is_valid()) + throw LLVMMemoryError("Metadata view used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError("Metadata view used after module was disposed"); + if (!LLVMIsAInstruction(m_value_ref) && !LLVMIsAGlobalValue(m_value_ref)) { + throw LLVMAssertionError( + "metadata view requires an instruction or global value"); + } + } + + unsigned kind_id(const std::string &name) const { + check_valid(); + if (name.empty()) + throw LLVMAssertionError("metadata kind name cannot be empty"); + return LLVMGetMDKindIDInContext(m_ctx_ref, name.c_str(), name.size()); + } + + std::optional get(const std::string &name) const { + unsigned kind = kind_id(name); + if (LLVMIsAInstruction(m_value_ref)) { + LLVMValueRef val = LLVMGetMetadata(m_value_ref, kind); + if (!val) + return std::nullopt; + return LLVMMetadataWrapper(LLVMValueAsMetadata(val), m_context_token, + m_ctx_ref, m_module_token); + } + + size_t count = 0; + LLVMValueMetadataEntry *entries = LLVMGlobalCopyAllMetadata(m_value_ref, &count); + for (size_t i = 0; i < count; ++i) { + if (LLVMValueMetadataEntriesGetKind(entries, i) == kind) { + LLVMMetadataRef md = LLVMValueMetadataEntriesGetMetadata(entries, i); + LLVMDisposeValueMetadataEntries(entries); + return LLVMMetadataWrapper(md, m_context_token, m_ctx_ref, + m_module_token); + } + } + if (entries) + LLVMDisposeValueMetadataEntries(entries); + return std::nullopt; + } + + LLVMMetadataWrapper get_item(const std::string &name) const { + auto md = get(name); + if (!md) + throw nb::key_error(name.c_str()); + return *md; + } + + bool contains(const std::string &name) const { return get(name).has_value(); } + + void set(const std::string &name, const LLVMMetadataWrapper &md) { + unsigned kind = kind_id(name); + LLVMMetadataRef metadata_ref = normalize_metadata_for_attachment(m_ctx_ref, md); + if (LLVMIsAGlobalValue(m_value_ref)) { + LLVMGlobalSetMetadata(m_value_ref, kind, metadata_ref); + } else { + LLVMValueRef md_value = LLVMMetadataAsValue(m_ctx_ref, metadata_ref); + LLVMSetMetadata(m_value_ref, kind, md_value); + } + } + + void erase(const std::string &name) { + unsigned kind = kind_id(name); + if (LLVMIsAGlobalValue(m_value_ref)) { + LLVMGlobalEraseMetadata(m_value_ref, kind); + } else { + LLVMSetMetadata(m_value_ref, kind, nullptr); + } + } + + void attach_entry(LLVMValueRef target_ref, LLVMContextRef target_ctx, + unsigned kind, LLVMMetadataRef md) const { + LLVMMetadataWrapper md_wrapper(md, m_context_token, m_ctx_ref, + m_module_token); + LLVMMetadataRef metadata_ref = + normalize_metadata_for_attachment(target_ctx, md_wrapper); + if (LLVMIsAGlobalValue(target_ref)) { + LLVMGlobalSetMetadata(target_ref, kind, metadata_ref); + } else { + LLVMValueRef md_value = LLVMMetadataAsValue(target_ctx, metadata_ref); + LLVMSetMetadata(target_ref, kind, md_value); + } + } + + void copy_entries_to(LLVMValueMetadataEntry *entries, size_t count, + LLVMValueRef target_ref, + LLVMContextRef target_ctx) const { + for (size_t i = 0; i < count; ++i) { + unsigned kind = LLVMValueMetadataEntriesGetKind(entries, i); + LLVMMetadataRef md = LLVMValueMetadataEntriesGetMetadata(entries, i); + attach_entry(target_ref, target_ctx, kind, md); + } + } + + void copy_to(const LLVMValueWrapper &target, + bool include_debug_location = false) const { + check_valid(); + target.check_valid(); + if (!LLVMIsAInstruction(target.m_ref) && !LLVMIsAGlobalValue(target.m_ref)) { + throw LLVMAssertionError( + "metadata copy target requires an instruction or global value"); + } + if (target.m_context_token != m_context_token) { + throw LLVMAssertionError( + "metadata copy requires source and target in the same context"); + } + if (include_debug_location && + (!LLVMIsAInstruction(m_value_ref) || !LLVMIsAInstruction(target.m_ref))) { + throw LLVMAssertionError( + "include_debug_location requires instruction source and target"); + } + + LLVMContextRef target_ctx = context_for_metadata_value(target.m_ref); + size_t count = 0; + LLVMValueMetadataEntry *entries = nullptr; + if (LLVMIsAInstruction(m_value_ref)) { + entries = LLVMInstructionGetAllMetadataOtherThanDebugLoc(m_value_ref, + &count); + } else { + entries = LLVMGlobalCopyAllMetadata(m_value_ref, &count); + } + + copy_entries_to(entries, count, target.m_ref, target_ctx); + if (entries) + LLVMDisposeValueMetadataEntries(entries); + + if (include_debug_location) { + LLVMInstructionSetDebugLoc(target.m_ref, + LLVMInstructionGetDebugLoc(m_value_ref)); + } + } +}; + +struct LLVMNamedMetadataListWrapper { + LLVMModuleRef m_module_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; + std::string m_name; + std::shared_ptr m_context_token; + std::shared_ptr m_module_token; + + LLVMNamedMetadataListWrapper() = default; + LLVMNamedMetadataListWrapper(LLVMModuleRef mod, LLVMContextRef ctx, + std::string name, + std::shared_ptr context_token, + std::shared_ptr module_token) + : m_module_ref(mod), m_ctx_ref(ctx), m_name(std::move(name)), + m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} + + void check_valid() const { + if (!m_module_ref) + throw LLVMMemoryError("Named metadata view has no module"); + if (!m_context_token || !m_context_token->is_valid()) + throw LLVMMemoryError("Named metadata view used after context was destroyed"); + if (!m_module_token || !m_module_token->is_valid()) + throw LLVMMemoryError("Named metadata view used after module was disposed"); + } + + size_t size() const { + check_valid(); + return LLVMGetNamedMetadataNumOperands(m_module_ref, m_name.c_str()); + } + + void append(const LLVMMetadataWrapper &md) { + check_valid(); + md.check_valid(); + if (md.m_ctx_ref && md.m_ctx_ref != m_ctx_ref) { + throw LLVMAssertionError( + "named metadata requires metadata from the same context"); + } + LLVMValueRef val = LLVMMetadataAsValue(m_ctx_ref, md.m_ref); + LLVMAddNamedMetadataOperand(m_module_ref, m_name.c_str(), val); + } + + std::vector all() const { + check_valid(); + unsigned count = LLVMGetNamedMetadataNumOperands(m_module_ref, m_name.c_str()); + if (count == 0) + return {}; + std::vector values(count); + LLVMGetNamedMetadataOperands(m_module_ref, m_name.c_str(), values.data()); + std::vector result; + result.reserve(count); + for (LLVMValueRef value : values) { + result.emplace_back(LLVMValueAsMetadata(value), m_context_token, + m_ctx_ref, m_module_token); + } + return result; + } + + LLVMMetadataWrapper get_item(size_t index) const { + auto values = all(); + if (index >= values.size()) + throw nb::index_error("named metadata index out of range"); + return values[index]; + } +}; + +struct LLVMNamedMetadataMapWrapper { + LLVMModuleRef m_module_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; + std::shared_ptr m_context_token; + std::shared_ptr m_module_token; + + LLVMNamedMetadataMapWrapper() = default; + LLVMNamedMetadataMapWrapper(LLVMModuleRef mod, LLVMContextRef ctx, + std::shared_ptr context_token, + std::shared_ptr module_token) + : m_module_ref(mod), m_ctx_ref(ctx), + m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} + + void check_valid() const { + if (!m_module_ref) + throw LLVMMemoryError("Named metadata map has no module"); + if (!m_context_token || !m_context_token->is_valid()) + throw LLVMMemoryError("Named metadata map used after context was destroyed"); + if (!m_module_token || !m_module_token->is_valid()) + throw LLVMMemoryError("Named metadata map used after module was disposed"); + } + + bool contains(const std::string &name) const { + check_valid(); + return LLVMGetNamedMetadata(m_module_ref, name.c_str(), name.size()) != nullptr; + } + + LLVMNamedMetadataListWrapper get_item(const std::string &name) const { + check_valid(); + if (name.empty()) + throw LLVMAssertionError("named metadata name cannot be empty"); + LLVMGetOrInsertNamedMetadata(m_module_ref, name.c_str(), name.size()); + return LLVMNamedMetadataListWrapper(m_module_ref, m_ctx_ref, name, + m_context_token, m_module_token); + } + + std::optional get(const std::string &name) const { + check_valid(); + if (!contains(name)) + return std::nullopt; + return LLVMNamedMetadataListWrapper(m_module_ref, m_ctx_ref, name, + m_context_token, m_module_token); + } + + std::vector keys() const { + check_valid(); + std::vector result; + for (LLVMNamedMDNodeRef cur = LLVMGetFirstNamedMetadata(m_module_ref); cur; + cur = LLVMGetNextNamedMetadata(cur)) { + size_t len = 0; + const char *name = LLVMGetNamedMetadataName(cur, &len); + result.emplace_back(name, len); + } + return result; + } +}; + +struct LLVMModuleFlagsWrapper { + LLVMModuleRef m_module_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; + std::shared_ptr m_context_token; + std::shared_ptr m_module_token; + + LLVMModuleFlagsWrapper() = default; + LLVMModuleFlagsWrapper(LLVMModuleRef mod, + std::shared_ptr context_token, + std::shared_ptr module_token) + : m_module_ref(mod), m_ctx_ref(mod ? LLVMGetModuleContext(mod) : nullptr), + m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)) {} + + void check_valid() const { + if (!m_module_ref) + throw LLVMMemoryError("Module flags view has no module"); + if (!m_context_token || !m_context_token->is_valid()) + throw LLVMMemoryError("Module flags view used after context was destroyed"); + if (!m_module_token || !m_module_token->is_valid()) + throw LLVMMemoryError("Module flags view used after module was disposed"); + } + + void add(const std::string &key, LLVMModuleFlagBehavior behavior, + const LLVMMetadataWrapper &value) { + check_valid(); + value.check_valid(); + if (value.m_ctx_ref && value.m_ctx_ref != m_ctx_ref) { + throw LLVMAssertionError( + "module flag requires metadata from the same context"); + } + if (key.empty()) + throw LLVMAssertionError("module flag key cannot be empty"); + LLVMAddModuleFlag(m_module_ref, behavior, key.c_str(), key.size(), value.m_ref); + } + + std::optional get(const std::string &key) const { + check_valid(); + LLVMMetadataRef md = LLVMGetModuleFlag(m_module_ref, key.c_str(), key.size()); + if (!md) + return std::nullopt; + return LLVMMetadataWrapper(md, m_context_token, m_ctx_ref, + m_module_token); + } + + LLVMMetadataWrapper get_item(const std::string &key) const { + auto md = get(key); + if (!md) + throw nb::key_error(key.c_str()); + return *md; + } + + bool contains(const std::string &key) const { return get(key).has_value(); } + + std::vector keys() const { + check_valid(); + size_t count = 0; + LLVMModuleFlagEntry *entries = LLVMCopyModuleFlagsMetadata(m_module_ref, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; ++i) { + size_t len = 0; + const char *key = LLVMModuleFlagEntriesGetKey(entries, i, &len); + result.emplace_back(key, len); + } + if (entries) + LLVMDisposeModuleFlagsMetadata(entries); + return result; + } +}; + +struct LLVMBuilderDebugLocationManager : NoMoveCopy { + LLVMBuilderRef m_builder_ref = nullptr; + LLVMMetadataRef m_new_loc = nullptr; + LLVMMetadataRef m_saved_loc = nullptr; + std::shared_ptr m_context_token; + std::shared_ptr m_module_token; + std::shared_ptr m_builder_token; + bool m_entered = false; + + LLVMBuilderDebugLocationManager() = default; + LLVMBuilderDebugLocationManager( + LLVMBuilderRef builder, LLVMMetadataRef loc, + std::shared_ptr context_token, + std::shared_ptr module_token, + std::shared_ptr builder_token) + : m_builder_ref(builder), m_new_loc(loc), + m_context_token(std::move(context_token)), + m_module_token(std::move(module_token)), + m_builder_token(std::move(builder_token)) {} + + void check_valid() const { + if (!m_builder_ref) + throw LLVMMemoryError("Debug location manager has no builder"); + if (!m_builder_token || !m_builder_token->is_valid()) + throw LLVMMemoryError( + "Debug location manager used after builder was disposed"); + if (!m_context_token || !m_context_token->is_valid()) + throw LLVMMemoryError( + "Debug location manager used after context was destroyed"); + if (m_module_token && !m_module_token->is_valid()) + throw LLVMMemoryError( + "Debug location manager used after module was disposed"); + } + + LLVMBuilderDebugLocationManager &enter() { + check_valid(); + if (m_entered) + throw LLVMMemoryError("Debug location manager already entered"); + m_saved_loc = LLVMGetCurrentDebugLocation2(m_builder_ref); + LLVMSetCurrentDebugLocation2(m_builder_ref, m_new_loc); + m_entered = true; + return *this; + } + + void exit(const nb::object &, const nb::object &, const nb::object &) { + check_valid(); + if (!m_entered) + throw LLVMMemoryError("Debug location manager was not entered"); + LLVMSetCurrentDebugLocation2(m_builder_ref, m_saved_loc); + m_entered = false; + } +}; + // Implementation of get_metadata for ValueMetadataEntriesWrapper inline LLVMMetadataWrapper LLVMValueMetadataEntriesWrapper_get_metadata( const LLVMValueMetadataEntriesWrapper &entries, unsigned index) { @@ -9634,7 +11097,21 @@ inline LLVMMetadataWrapper LLVMValueMetadataEntriesWrapper_get_metadata( // Implementation of LLVMValueWrapper::as_metadata() - needs LLVMMetadataWrapper inline LLVMMetadataWrapper LLVMValueWrapper::as_metadata() const { check_valid(); - return LLVMMetadataWrapper(LLVMValueAsMetadata(m_ref), m_context_token); + LLVMContextRef ctx_ref = nullptr; + LLVMTypeRef ty = LLVMTypeOf(m_ref); + if (ty) + ctx_ref = LLVMGetTypeContext(ty); + return LLVMMetadataWrapper(LLVMValueAsMetadata(m_ref), m_context_token, + ctx_ref, m_module_token); +} + +inline LLVMMetadataMapWrapper LLVMValueWrapper::metadata() const { + check_valid(); + LLVMContextRef ctx_ref = context_for_metadata_value(m_ref); + std::shared_ptr module_token = m_module_token; + if (!module_token) + module_token = module_token_for_module(module_for_metadata_value(m_ref)); + return LLVMMetadataMapWrapper(m_ref, ctx_ref, m_context_token, module_token); } // Implementation of replace_md_node_operand_with - needs LLVMMetadataWrapper @@ -9686,7 +11163,7 @@ inline LLVMMetadataWrapper LLVMContextWrapper::md_string(const std::string &str) { check_valid(); return LLVMMetadataWrapper( - LLVMMDStringInContext2(m_ref, str.c_str(), str.size()), m_token); + LLVMMDStringInContext2(m_ref, str.c_str(), str.size()), m_token, m_ref); } // Implementation of LLVMContextWrapper::md_node() - needs LLVMMetadataWrapper @@ -9697,10 +11174,11 @@ LLVMContextWrapper::md_node(const Iterable &mds) { refs.reserve(mds.size()); for (const auto &md : mds) { md.check_valid(); + require_metadata_context(m_ref, md, "Context.md_node"); refs.push_back(md.m_ref); } return LLVMMetadataWrapper( - LLVMMDNodeInContext2(m_ref, refs.data(), refs.size()), m_token); + LLVMMDNodeInContext2(m_ref, refs.data(), refs.size()), m_token, m_ref); } inline LLVMMetadataWrapper LLVMContextWrapper::create_debug_location( @@ -9708,15 +11186,52 @@ inline LLVMMetadataWrapper LLVMContextWrapper::create_debug_location( const LLVMMetadataWrapper *inlined_at) { check_valid(); scope.check_valid(); + require_metadata_context(m_ref, scope, "Context.debug_location scope"); LLVMMetadataRef inlined = nullptr; if (inlined_at) { inlined_at->check_valid(); + require_metadata_context(m_ref, *inlined_at, + "Context.debug_location inlined_at"); inlined = inlined_at->m_ref; } return LLVMMetadataWrapper( LLVMDIBuilderCreateDebugLocation(m_ref, line, column, scope.m_ref, inlined), - m_token); + m_token, m_ref); +} + +inline LLVMMetadataWrapper LLVMContextWrapper::debug_location( + unsigned line, unsigned column, const LLVMMetadataWrapper &scope, + const LLVMMetadataWrapper *inlined_at) { + return create_debug_location(line, column, scope, inlined_at); +} + +inline LLVMBuilderDebugLocationManager * +LLVMBuilderWrapper::debug_location(const LLVMMetadataWrapper &loc) const { + check_valid(); + loc.check_valid(); + require_metadata_context(m_ctx_ref, loc, "Builder.debug_location"); + return new LLVMBuilderDebugLocationManager(m_ref, loc.m_ref, m_context_token, + m_module_token, m_token); +} + +inline LLVMBuilderDebugLocationManager *LLVMBuilderWrapper::debug_location( + unsigned line, unsigned column, const LLVMMetadataWrapper &scope, + const LLVMMetadataWrapper *inlined_at) const { + check_valid(); + scope.check_valid(); + require_metadata_context(m_ctx_ref, scope, "Builder.debug_location scope"); + LLVMMetadataRef inlined = nullptr; + if (inlined_at) { + inlined_at->check_valid(); + require_metadata_context(m_ctx_ref, *inlined_at, + "Builder.debug_location inlined_at"); + inlined = inlined_at->m_ref; + } + LLVMMetadataRef loc = LLVMDIBuilderCreateDebugLocation( + m_ctx_ref, line, column, scope.m_ref, inlined); + return new LLVMBuilderDebugLocationManager(m_ref, loc, m_context_token, + m_module_token, m_token); } // Implementation of LLVMContextWrapper::create_type_attribute() - needs @@ -9739,6 +11254,7 @@ LLVMModuleWrapper::add_named_metadata_operand(const std::string &name, const LLVMMetadataWrapper &md) { check_valid(); md.check_valid(); + require_metadata_context(m_ctx_ref, md, "Module.add_named_metadata_operand"); // Need to convert metadata to value first for LLVMAddNamedMetadataOperand LLVMValueRef val = LLVMMetadataAsValue(m_ctx_ref, md.m_ref); LLVMAddNamedMetadataOperand(m_ref, name.c_str(), val); @@ -9751,6 +11267,7 @@ inline void LLVMModuleWrapper::add_module_flag(LLVMModuleFlagBehavior behavior, const LLVMMetadataWrapper &val) { check_valid(); val.check_valid(); + require_metadata_context(m_ctx_ref, val, "Module.add_module_flag"); LLVMAddModuleFlag(m_ref, behavior, key.c_str(), key.size(), val.m_ref); } @@ -9762,7 +11279,17 @@ LLVMModuleWrapper::get_module_flag(const std::string &key) { LLVMMetadataRef md = LLVMGetModuleFlag(m_ref, key.c_str(), key.size()); if (!md) return std::nullopt; - return LLVMMetadataWrapper(md, m_context_token); + return LLVMMetadataWrapper(md, m_context_token, m_ctx_ref, m_token); +} + +inline LLVMNamedMetadataMapWrapper LLVMModuleWrapper::named_metadata() const { + check_valid(); + return LLVMNamedMetadataMapWrapper(m_ref, m_ctx_ref, m_context_token, m_token); +} + +inline LLVMModuleFlagsWrapper LLVMModuleWrapper::module_flags() const { + check_valid(); + return LLVMModuleFlagsWrapper(m_ref, m_context_token, m_token); } // Implementation of LLVMFunctionWrapper::set_subprogram() - needs @@ -9777,6 +11304,31 @@ inline void LLVMFunctionWrapper::set_subprogram(const LLVMMetadataWrapper &sp) { // DIBuilder Method Implementations // ============================================================================= +static LLVMMetadataRef create_debug_type_from_ir_type(LLVMDIBuilderRef dib, + LLVMTypeRef ty) { + LLVMTypeKind kind = LLVMGetTypeKind(ty); + switch (kind) { + case LLVMVoidTypeKind: + return nullptr; + case LLVMIntegerTypeKind: { + unsigned width = LLVMGetIntTypeWidth(ty); + std::string name = width == 1 ? "bool" : "i" + std::to_string(width); + unsigned encoding = width == 1 ? 0x02 : 0x05; // DW_ATE_boolean/signed + return LLVMDIBuilderCreateBasicType(dib, name.c_str(), name.size(), width, + encoding, LLVMDIFlagZero); + } + case LLVMFloatTypeKind: + return LLVMDIBuilderCreateBasicType(dib, "float", 5, 32, 0x04, + LLVMDIFlagZero); + case LLVMDoubleTypeKind: + return LLVMDIBuilderCreateBasicType(dib, "double", 6, 64, 0x04, + LLVMDIFlagZero); + default: + throw LLVMAssertionError( + "DIBuilder type recipe supports void, integer, float, and double IR types"); + } +} + // File & Scope Creation inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_file(const std::string &filename, @@ -9785,7 +11337,13 @@ LLVMDIBuilderWrapper::create_file(const std::string &filename, return LLVMMetadataWrapper( LLVMDIBuilderCreateFile(m_ref, filename.c_str(), filename.size(), directory.c_str(), directory.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); +} + +inline LLVMMetadataWrapper +LLVMDIBuilderWrapper::file(const std::string &filename, + const std::string &directory) { + return create_file(filename, directory); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_compile_unit( @@ -9804,7 +11362,21 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_compile_unit( (LLVMDWARFEmissionKind)kind, dwo_id, split_debug_inlining, debug_info_for_profiling, sys_root.c_str(), sys_root.size(), sdk.c_str(), sdk.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); +} + +inline LLVMMetadataWrapper LLVMDIBuilderWrapper::compile_unit( + LLVMDWARFSourceLanguage language, const LLVMMetadataWrapper &file, + const std::string &producer, bool is_optimized, + const std::string &flags, unsigned runtime_ver, + const std::string &split_name, LLVMDWARFEmissionKind kind, + unsigned dwo_id, bool split_debug_inlining, + bool debug_info_for_profiling, const std::string &sys_root, + const std::string &sdk) { + return create_compile_unit((int)language, file, producer, is_optimized, flags, + runtime_ver, split_name, (unsigned)kind, dwo_id, + split_debug_inlining, debug_info_for_profiling, + sys_root, sdk); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_module( @@ -9818,7 +11390,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_module( m_ref, parent_scope.m_ref, name.c_str(), name.size(), config_macros.c_str(), config_macros.size(), include_path.c_str(), include_path.size(), api_notes_file.c_str(), api_notes_file.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -9830,7 +11402,7 @@ LLVMDIBuilderWrapper::create_namespace(const LLVMMetadataWrapper &parent_scope, return LLVMMetadataWrapper( LLVMDIBuilderCreateNameSpace(m_ref, parent_scope.m_ref, name.c_str(), name.size(), export_symbols), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -9842,7 +11414,7 @@ LLVMDIBuilderWrapper::create_lexical_block(const LLVMMetadataWrapper &scope, file.check_valid(); return LLVMMetadataWrapper(LLVMDIBuilderCreateLexicalBlock( m_ref, scope.m_ref, file.m_ref, line, column), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Function & Subroutine Creation @@ -9861,7 +11433,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_function( m_ref, scope.m_ref, name.c_str(), name.size(), linkage_name.c_str(), linkage_name.size(), file.m_ref, line_no, type, is_local_to_unit, is_definition, scope_line, (LLVMDIFlags)flags, is_optimized), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_subroutine_type( @@ -9878,7 +11450,44 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_subroutine_type( return LLVMMetadataWrapper( LLVMDIBuilderCreateSubroutineType(m_ref, file.m_ref, param_refs.data(), param_refs.size(), (LLVMDIFlags)flags), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); +} + +inline LLVMMetadataWrapper LLVMDIBuilderWrapper::function( + LLVMFunctionWrapper &fn, const std::string &name, + const LLVMMetadataWrapper &file, unsigned line, + const LLVMTypeWrapper &return_type, + const Iterable ¶m_types, + const LLVMMetadataWrapper *scope, const std::string &linkage_name, + bool is_local_to_unit, bool is_definition, unsigned scope_line, + unsigned flags, bool is_optimized) { + check_valid(); + fn.check_valid(); + file.check_valid(); + return_type.check_valid(); + LLVMMetadataRef scope_ref = scope ? scope->m_ref : file.m_ref; + if (scope) + scope->check_valid(); + + std::vector di_types; + di_types.reserve(param_types.size() + 1); + di_types.push_back(create_debug_type_from_ir_type(m_ref, return_type.m_ref)); + for (const auto ¶m_ty : param_types) { + param_ty.check_valid(); + di_types.push_back(create_debug_type_from_ir_type(m_ref, param_ty.m_ref)); + } + + LLVMMetadataRef subroutine_type = LLVMDIBuilderCreateSubroutineType( + m_ref, file.m_ref, di_types.data(), di_types.size(), LLVMDIFlagZero); + std::string linkage = linkage_name.empty() ? fn.get_name() : linkage_name; + unsigned actual_scope_line = scope_line == 0 ? line : scope_line; + LLVMMetadataRef sp = LLVMDIBuilderCreateFunction( + m_ref, scope_ref, name.c_str(), name.size(), linkage.c_str(), linkage.size(), + file.m_ref, line, subroutine_type, is_local_to_unit, is_definition, + actual_scope_line, (LLVMDIFlags)flags, is_optimized); + LLVMMetadataWrapper wrapper(sp, m_context_token, m_ctx_ref, m_module_token); + fn.set_subprogram(wrapper); + return wrapper; } // Type Creation @@ -9890,7 +11499,7 @@ LLVMDIBuilderWrapper::create_basic_type(const std::string &name, return LLVMMetadataWrapper( LLVMDIBuilderCreateBasicType(m_ref, name.c_str(), name.size(), size_in_bits, encoding, (LLVMDIFlags)flags), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_pointer_type( @@ -9902,7 +11511,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_pointer_type( LLVMDIBuilderCreatePointerType(m_ref, pointee.m_ref, size_in_bits, align_in_bits, address_space, name.c_str(), name.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_vector_type( @@ -9921,7 +11530,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_vector_type( LLVMDIBuilderCreateVectorType(m_ref, size_in_bits, align_in_bits, element_type.m_ref, sub_refs.data(), sub_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_typedef( @@ -9936,7 +11545,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_typedef( LLVMDIBuilderCreateTypedef(m_ref, type.m_ref, name.c_str(), name.size(), file.m_ref, line_no, scope.m_ref, align_in_bits), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_struct_type( @@ -9964,7 +11573,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_struct_type( elem_refs.empty() ? nullptr : elem_refs.data(), static_cast(elem_refs.size()), runtime_lang, vtable, unique_id.c_str(), unique_id.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_enumeration_type( @@ -9988,7 +11597,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_enumeration_type( file.m_ref, line_number, size_in_bits, align_in_bits, elem_refs.data(), elem_refs.size(), underlying_type.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_forward_decl( @@ -10004,7 +11613,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_forward_decl( scope.m_ref, file.m_ref, line, runtime_lang, size_in_bits, align_in_bits, unique_id.c_str(), unique_id.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10021,7 +11630,7 @@ LLVMDIBuilderWrapper::create_replaceable_composite_type( m_ref, tag, name.c_str(), name.size(), scope.m_ref, file.m_ref, line, runtime_lang, size_in_bits, align_in_bits, (LLVMDIFlags)flags, unique_id.c_str(), unique_id.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_subrange_type( @@ -10045,7 +11654,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_subrange_type( line, file.m_ref, size_in_bits, align_in_bits, (LLVMDIFlags)flags, element_type.m_ref, lb, ub, st, bi), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_set_type( @@ -10060,7 +11669,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_set_type( LLVMDIBuilderCreateSetType(m_ref, scope.m_ref, name.c_str(), name.size(), file.m_ref, line, size_in_bits, align_in_bits, base_type.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_dynamic_array_type( @@ -10091,7 +11700,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_dynamic_array_type( m_ref, scope.m_ref, name.c_str(), name.size(), line, file.m_ref, size_in_bits, align_in_bits, element_type.m_ref, sub_refs.data(), sub_refs.size(), data_location.m_ref, assoc, alloc, rnk, stride_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Variable & Expression @@ -10107,7 +11716,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_parameter_variable( m_ref, scope.m_ref, name.c_str(), name.size(), arg_no, file.m_ref, line_no, type.m_ref, always_preserve, (LLVMDIFlags)flags), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_auto_variable( @@ -10123,7 +11732,26 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_auto_variable( LLVMDIBuilderCreateAutoVariable( m_ref, scope.m_ref, name.c_str(), name.size(), file.m_ref, line_no, type.m_ref, always_preserve, (LLVMDIFlags)flags, align_in_bits), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); +} + +inline LLVMMetadataWrapper LLVMDIBuilderWrapper::local_variable( + const LLVMMetadataWrapper &scope, const std::string &name, + const LLVMMetadataWrapper &file, unsigned line_no, + const LLVMTypeWrapper &type, bool always_preserve, unsigned flags, + uint32_t align_in_bits) { + check_valid(); + scope.check_valid(); + file.check_valid(); + type.check_valid(); + LLVMMetadataRef di_type = create_debug_type_from_ir_type(m_ref, type.m_ref); + if (!di_type) + throw LLVMAssertionError("local_variable type cannot be void"); + return LLVMMetadataWrapper( + LLVMDIBuilderCreateAutoVariable( + m_ref, scope.m_ref, name.c_str(), name.size(), file.m_ref, line_no, + di_type, always_preserve, (LLVMDIFlags)flags, align_in_bits), + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10144,7 +11772,7 @@ LLVMDIBuilderWrapper::create_global_variable_expression( linkage.c_str(), linkage.size(), file.m_ref, line_no, type.m_ref, is_local_to_unit, expr.m_ref, decl_ref, align_in_bits), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10155,14 +11783,14 @@ LLVMDIBuilderWrapper::create_expression(const Iterable &addr) { std::vector addr_copy = addr; return LLVMMetadataWrapper( LLVMDIBuilderCreateExpression(m_ref, addr_copy.data(), addr_copy.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_constant_value_expression(uint64_t value) { check_valid(); return LLVMMetadataWrapper( - LLVMDIBuilderCreateConstantValueExpression(m_ref, value), m_module_token); + LLVMDIBuilderCreateConstantValueExpression(m_ref, value), m_context_token, m_ctx_ref, m_module_token); } // Label Methods @@ -10175,7 +11803,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_label( return LLVMMetadataWrapper( LLVMDIBuilderCreateLabel(m_ref, scope.m_ref, name.c_str(), name.size(), file.m_ref, line_no, always_preserve), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline void @@ -10238,7 +11866,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_subrange(int64_t lo, int64_t count) { check_valid(); return LLVMMetadataWrapper(LLVMDIBuilderGetOrCreateSubrange(m_ref, lo, count), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_array( @@ -10252,7 +11880,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_array( } return LLVMMetadataWrapper( LLVMDIBuilderGetOrCreateArray(m_ref, elem_refs.data(), elem_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Enumerator Methods @@ -10263,7 +11891,7 @@ LLVMDIBuilderWrapper::create_enumerator(const std::string &name, int64_t value, return LLVMMetadataWrapper(LLVMDIBuilderCreateEnumerator(m_ref, name.c_str(), name.size(), value, is_unsigned), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10274,7 +11902,7 @@ LLVMDIBuilderWrapper::create_enumerator_of_arbitrary_precision( return LLVMMetadataWrapper(LLVMDIBuilderCreateEnumeratorOfArbitraryPrecision( m_ref, name.c_str(), name.size(), value.size() * 64, value.data(), is_unsigned), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // ObjC & Inheritance Methods @@ -10290,7 +11918,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_objc_property( m_ref, name.c_str(), name.size(), file.m_ref, line_no, getter_name.c_str(), getter_name.size(), setter_name.c_str(), setter_name.size(), property_attributes, type.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_objc_ivar( @@ -10307,7 +11935,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_objc_ivar( line_no, size_in_bits, align_in_bits, offset_in_bits, (LLVMDIFlags)flags, type.m_ref, property.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_inheritance( @@ -10321,7 +11949,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_inheritance( LLVMDIBuilderCreateInheritance(m_ref, derived_type.m_ref, base_type.m_ref, offset_in_bits, v_bptr_offset, (LLVMDIFlags)flags), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Import & Macro Methods @@ -10344,7 +11972,7 @@ LLVMDIBuilderWrapper::create_imported_module_from_module( m_ref, scope.m_ref, import_module.m_ref, file.m_ref, line, elem_refs.data(), elem_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10366,7 +11994,7 @@ LLVMDIBuilderWrapper::create_imported_module_from_alias( m_ref, scope.m_ref, imported_entity.m_ref, file.m_ref, line, elem_refs.data(), elem_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_temp_macro_file( @@ -10378,7 +12006,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_temp_macro_file( parent_macro_file ? parent_macro_file->m_ref : nullptr; return LLVMMetadataWrapper( LLVMDIBuilderCreateTempMacroFile(m_ref, parent, line, file.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_macro( @@ -10391,7 +12019,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_macro( (LLVMDWARFMacinfoRecordType)macro_type, name.c_str(), name.size(), value.c_str(), value.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Missing DIBuilder Method Implementations @@ -10417,7 +12045,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_member_type( name.size(), file.m_ref, line_no, size_in_bits, align_in_bits, offset_in_bits, (LLVMDIFlags)flags, type.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_union_type( @@ -10441,7 +12069,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_union_type( line_number, size_in_bits, align_in_bits, (LLVMDIFlags)flags, elem_refs.data(), elem_refs.size(), runtime_lang, unique_id.c_str(), unique_id.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_array_type( @@ -10460,7 +12088,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_array_type( LLVMDIBuilderCreateArrayType(m_ref, size_in_bits, align_in_bits, element_type.m_ref, sub_refs.data(), sub_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10469,7 +12097,7 @@ LLVMDIBuilderWrapper::create_qualified_type(unsigned tag, check_valid(); type.check_valid(); return LLVMMetadataWrapper( - LLVMDIBuilderCreateQualifiedType(m_ref, tag, type.m_ref), m_module_token); + LLVMDIBuilderCreateQualifiedType(m_ref, tag, type.m_ref), m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10478,13 +12106,13 @@ LLVMDIBuilderWrapper::create_reference_type(unsigned tag, check_valid(); type.check_valid(); return LLVMMetadataWrapper( - LLVMDIBuilderCreateReferenceType(m_ref, tag, type.m_ref), m_module_token); + LLVMDIBuilderCreateReferenceType(m_ref, tag, type.m_ref), m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_null_ptr_type() { check_valid(); return LLVMMetadataWrapper(LLVMDIBuilderCreateNullPtrType(m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_bit_field_member_type( @@ -10501,7 +12129,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_bit_field_member_type( file.m_ref, line_no, size_in_bits, offset_in_bits, storage_offset_in_bits, (LLVMDIFlags)flags, type.m_ref), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10509,7 +12137,7 @@ LLVMDIBuilderWrapper::create_artificial_type(const LLVMMetadataWrapper &type) { check_valid(); type.check_valid(); return LLVMMetadataWrapper( - LLVMDIBuilderCreateArtificialType(m_ref, type.m_ref), m_module_token); + LLVMDIBuilderCreateArtificialType(m_ref, type.m_ref), m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_type_array( @@ -10523,7 +12151,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_type_array( } return LLVMMetadataWrapper(LLVMDIBuilderGetOrCreateTypeArray( m_ref, type_refs.data(), type_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_lexical_block_file( @@ -10534,7 +12162,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_lexical_block_file( file.check_valid(); return LLVMMetadataWrapper(LLVMDIBuilderCreateLexicalBlockFile( m_ref, scope.m_ref, file.m_ref, discriminator), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_imported_declaration( @@ -10555,7 +12183,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_imported_declaration( m_ref, scope.m_ref, decl.m_ref, file.m_ref, line, name.c_str(), name.size(), elem_refs.data(), elem_refs.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } inline LLVMMetadataWrapper @@ -10569,7 +12197,7 @@ LLVMDIBuilderWrapper::create_imported_module_from_namespace( return LLVMMetadataWrapper( LLVMDIBuilderCreateImportedModuleFromNamespace( m_ref, scope.m_ref, ns.m_ref, file.m_ref, line), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Utility Methods @@ -10623,7 +12251,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_class_type( vtable_holder ? vtable_holder->m_ref : nullptr, template_params ? template_params->m_ref : nullptr, unique_id.c_str(), unique_id.size()), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Create static member type debug info @@ -10644,7 +12272,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_static_member_type( m_ref, scope.m_ref, name.c_str(), name.size(), file.m_ref, line_no, type.m_ref, (LLVMDIFlags)flags, const_val ? const_val->m_ref : nullptr, align_in_bits), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Create member pointer type debug info (C++ pointer-to-member) @@ -10660,7 +12288,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_member_pointer_type( LLVMDIBuilderCreateMemberPointerType(m_ref, pointee_type.m_ref, class_type.m_ref, size_in_bits, align_in_bits, (LLVMDIFlags)flags), - m_module_token); + m_context_token, m_ctx_ref, m_module_token); } // Insert declare record before an instruction @@ -10722,8 +12350,8 @@ struct LLVMDIBuilderManager : NoMoveCopy { if (!m_module_token || !m_module_token->is_valid()) throw LLVMMemoryError("DIBuilder's module has been destroyed"); m_module->check_valid(); - m_dibuilder = std::make_unique(m_module->m_ref, - m_module->m_token); + m_dibuilder = std::make_unique( + m_module->m_ref, m_module->m_context_token, m_module->m_token); m_entered = true; return *m_dibuilder; } @@ -10733,9 +12361,9 @@ struct LLVMDIBuilderManager : NoMoveCopy { throw LLVMMemoryError("DIBuilder has already been disposed"); if (!m_entered) throw LLVMMemoryError("DIBuilder manager was not entered"); - // Finalize is typically called explicitly, but we could auto-finalize here - // For now, just clean up - user should call finalize() before exiting - m_dibuilder.reset(); + // Finalize is typically called explicitly; users should call finalize() + // before exiting when debug info must be complete. + m_dibuilder->dispose(); m_disposed = true; } @@ -10860,7 +12488,8 @@ inline LLVMModuleWrapper *LLVMBasicBlockWrapper::module() const { if (!mod_ref) throw LLVMAssertionError("BasicBlock's function has no parent module"); LLVMContextRef ctx_ref = LLVMGetModuleContext(mod_ref); - return LLVMModuleWrapper::create_borrowed(mod_ref, ctx_ref, m_context_token); + return LLVMModuleWrapper::create_borrowed(mod_ref, ctx_ref, m_context_token, + m_module_token); } inline LLVMContextWrapper *LLVMBasicBlockWrapper::context() const { @@ -10886,7 +12515,8 @@ inline LLVMModuleWrapper *LLVMFunctionWrapper::module() const { throw LLVMAssertionError("Function has no parent module"); LLVMContextRef ctx_ref = LLVMGetModuleContext(mod_ref); // Return a non-owning (borrowed) wrapper for the function's module - return LLVMModuleWrapper::create_borrowed(mod_ref, ctx_ref, m_context_token); + return LLVMModuleWrapper::create_borrowed(mod_ref, ctx_ref, m_context_token, + m_module_token); } inline LLVMContextWrapper *LLVMFunctionWrapper::context() const { @@ -10959,7 +12589,8 @@ inline LLVMModuleWrapper *LLVMValueWrapper::get_module() const { if (!mod_ref) throw LLVMAssertionError("Value has no parent module"); LLVMContextRef ctx_ref = LLVMGetModuleContext(mod_ref); - return LLVMModuleWrapper::create_borrowed(mod_ref, ctx_ref, m_context_token); + return LLVMModuleWrapper::create_borrowed(mod_ref, ctx_ref, m_context_token, + m_module_token); } inline LLVMContextWrapper *LLVMValueWrapper::get_context() const { @@ -11002,6 +12633,14 @@ inline LLVMContextWrapper *LLVMValueWrapper::get_context() const { // ============================================================================= NB_MODULE(llvm, m) { + // Initialize LLVM-C + LLVMInitializeAllTargetInfos(); + LLVMInitializeAllTargets(); + LLVMInitializeAllTargetMCs(); + LLVMInitializeAllAsmPrinters(); + LLVMInitializeAllAsmParsers(); + LLVMInitializeAllDisassemblers(); + // Register exceptions auto exc_error = nb::exception(m, "LLVMError"); auto exc_memory = @@ -11110,6 +12749,26 @@ NB_MODULE(llvm, m) { "Appends the two values, dropping duplicates from the second list.") .export_values(); + nb::enum_(m, "DwarfLanguage") + .value("C89", LLVMDWARFSourceLanguageC89) + .value("C", LLVMDWARFSourceLanguageC) + .value("C99", LLVMDWARFSourceLanguageC99) + .value("C11", LLVMDWARFSourceLanguageC11) + .value("C17", LLVMDWARFSourceLanguageC17) + .value("CPlusPlus", LLVMDWARFSourceLanguageC_plus_plus) + .value("CPlusPlus11", LLVMDWARFSourceLanguageC_plus_plus_11) + .value("CPlusPlus14", LLVMDWARFSourceLanguageC_plus_plus_14) + .value("CPlusPlus17", LLVMDWARFSourceLanguageC_plus_plus_17) + .value("CPlusPlus20", LLVMDWARFSourceLanguageC_plus_plus_20) + .value("Python", LLVMDWARFSourceLanguagePython) + .value("Rust", LLVMDWARFSourceLanguageRust) + .value("Swift", LLVMDWARFSourceLanguageSwift); + + nb::enum_(m, "DwarfEmissionKind") + .value("None_", LLVMDWARFEmissionNone) + .value("Full", LLVMDWARFEmissionFull) + .value("LineTablesOnly", LLVMDWARFEmissionLineTablesOnly); + nb::enum_(m, "Visibility") .value("Default", LLVMDefaultVisibility) .value("Hidden", LLVMHiddenVisibility) @@ -11893,6 +13552,75 @@ Valid when: C API: LLVMGetPreviousDbgRecord)"); + nb::class_(m, "MetadataMap") + .def("__getitem__", &LLVMMetadataMapWrapper::get_item, "name"_a) + .def("__setitem__", &LLVMMetadataMapWrapper::set, "name"_a, "md"_a) + .def("__delitem__", &LLVMMetadataMapWrapper::erase, "name"_a) + .def("__contains__", &LLVMMetadataMapWrapper::contains, "name"_a) + .def("get", &LLVMMetadataMapWrapper::get, "name"_a, + R"(Get metadata by kind name, or None.)") + .def("remove", &LLVMMetadataMapWrapper::erase, "name"_a, + R"(Remove metadata by kind name.)") + .def("copy_to", &LLVMMetadataMapWrapper::copy_to, "target"_a, + "include_debug_location"_a = false, + R"(Copy all attached metadata to another instruction or global value. + +Instruction debug locations are copied only when include_debug_location=True.)"); + + nb::class_(m, "NamedMetadataList") + .def("__len__", &LLVMNamedMetadataListWrapper::size) + .def("__getitem__", &LLVMNamedMetadataListWrapper::get_item, "index"_a) + .def( + "__iter__", + [](const LLVMNamedMetadataListWrapper &self) { + nb::object items = nb::cast(self.all()); + return nb::iter(items); + }) + .def("append", &LLVMNamedMetadataListWrapper::append, "md"_a, + R"(Append a metadata node to this named metadata list.)") + .def("all", &LLVMNamedMetadataListWrapper::all, + R"(Return all operands as Metadata objects.)"); + + nb::class_(m, "NamedMetadataMap") + .def("__getitem__", &LLVMNamedMetadataMapWrapper::get_item, "name"_a, + R"(Return a named metadata list, creating it if needed.)") + .def("__contains__", &LLVMNamedMetadataMapWrapper::contains, "name"_a) + .def("get", &LLVMNamedMetadataMapWrapper::get, "name"_a, + R"(Return a named metadata list, or None if missing.)") + .def("keys", &LLVMNamedMetadataMapWrapper::keys, + R"(Return named metadata keys.)") + .def( + "__iter__", + [](const LLVMNamedMetadataMapWrapper &self) { + nb::object keys = nb::cast(self.keys()); + return nb::iter(keys); + }); + + nb::class_(m, "ModuleFlags") + .def("add", &LLVMModuleFlagsWrapper::add, "key"_a, "behavior"_a, + "value"_a, + R"(Add a module flag by key.)") + .def("get", &LLVMModuleFlagsWrapper::get, "key"_a, + R"(Get a module flag by key, or None.)") + .def("__getitem__", &LLVMModuleFlagsWrapper::get_item, "key"_a) + .def("__contains__", &LLVMModuleFlagsWrapper::contains, "key"_a) + .def("keys", &LLVMModuleFlagsWrapper::keys, + R"(Return module flag keys.)") + .def( + "__iter__", + [](const LLVMModuleFlagsWrapper &self) { + nb::object keys = nb::cast(self.keys()); + return nb::iter(keys); + }); + + nb::class_(m, "DebugLocationManager") + .def("__enter__", &LLVMBuilderDebugLocationManager::enter, + nb::rv_policy::reference_internal, + R"(Set the builder's current debug location.)") + .def("__exit__", &LLVMBuilderDebugLocationManager::exit, + "exc_type"_a.none(), "exc_value"_a.none(), "traceback"_a.none(), + R"(Restore the builder's previous debug location.)"); + // Value wrapper nb::class_(m, "Value") .def("__eq__", [](const LLVMValueWrapper &a, @@ -12500,7 +14228,7 @@ Returns True when the alloca size operand is not the constant 1. C++ API parity: AllocaInst::isArrayAllocation)") .def_prop_ro("function_type", &LLVMValueWrapper::get_function_type, R"(Get function type. - + C API: LLVMGlobalGetValueType)") .def_prop_ro("value_kind", &LLVMValueWrapper::value_kind, R"(Get value kind. @@ -12723,6 +14451,10 @@ Alias for `.block`. C API: LLVMGetTypeContext)", nb::rv_policy::take_ownership) + .def_prop_ro("types", &LLVMValueWrapper::types, + R"(Type factory for this value's context. + +C API: LLVMTypeOf, LLVMGetTypeContext)") // BasicBlock properties .def_prop_ro("normal_dest", &LLVMValueWrapper::get_normal_dest, R"(Get normal destination. @@ -12780,17 +14512,6 @@ Alias for `.block`. R"(Get the indices for GEP or ExtractValue/InsertValue instructions. C API: LLVMGetIndices)") - // Global/instruction metadata - .def("global_copy_all_metadata", - &LLVMValueWrapper::global_copy_all_metadata, - R"(Copy all metadata. - -C API: LLVMGlobalCopyAllMetadata)") - .def("instruction_get_all_metadata_other_than_debug_loc", - &LLVMValueWrapper::instruction_get_all_metadata_other_than_debug_loc, - R"(Get all metadata except debug loc. - -C API: LLVMInstructionGetAllMetadataOtherThanDebugLoc)") // Value methods .def("const_bitcast", &LLVMValueWrapper::const_bitcast, "type"_a, R"(Create constant bitcast. @@ -12810,6 +14531,15 @@ Alias for `.block`. R"(Convert to metadata. C API: LLVMValueAsMetadata)") + .def_prop_ro("metadata", &LLVMValueWrapper::metadata, + R"(Metadata mapping view for this instruction or global value. + +Use metadata kind names instead of numeric kind IDs: + inst.metadata["llvm.loop"] = md + md = inst.metadata.get("llvm.loop") + del inst.metadata["llvm.loop"] + +C API: LLVMGetMDKindIDInContext, LLVMSetMetadata, LLVMGlobalSetMetadata)") .def("delete_instruction", &LLVMValueWrapper::delete_instruction, R"(Delete this instruction from its parent block. @@ -12901,12 +14631,6 @@ TODO: needs safe LLVM-C API equivalent. }, "index"_a, R"(Attributes for a callsite argument, using a 0-based Python index.)") - // Unified metadata method - .def("set_metadata", &LLVMValueWrapper::set_metadata, "kind"_a, "md"_a, - "ctx"_a, - R"(Set metadata on value. - -C API: LLVMSetMetadata, LLVMGlobalSetMetadata)") // Builder creation for instructions. .def("create_builder", &LLVMValueWrapper::create_builder, nb::kw_only(), "before_dbg"_a = false, nb::rv_policy::take_ownership, @@ -13006,6 +14730,10 @@ Returns None when the block has no non-PHI instruction.)") C API: LLVMGetTypeContext)", nb::rv_policy::take_ownership) + .def_prop_ro("types", &LLVMBasicBlockWrapper::types, + R"(Type factory for this basic block's context. + +C API: LLVMBasicBlockAsValue, LLVMTypeOf, LLVMGetTypeContext)") .def_prop_ro("successors", &LLVMBasicBlockWrapper::successors, R"(Successor blocks. @@ -13234,24 +14962,55 @@ Valid when: C API: LLVMGetTypeContext)", nb::rv_policy::take_ownership) + .def_prop_ro("types", &LLVMFunctionWrapper::types, + R"(Type factory for this function's context. + +C API: LLVMTypeOf, LLVMGetTypeContext)") + .def("create_builder", &LLVMFunctionWrapper::create_builder, + nb::kw_only(), "first_non_phi"_a = false, + nb::rv_policy::take_ownership, + R"(Create a Builder in this function's entry block. + +If this function has no basic blocks yet, creates an entry block first. + +With `first_non_phi=True`, positions before the first non-PHI instruction in +the entry block. If the entry block has no non-PHI instruction, the builder is +positioned at the end of the block. + +Returns a BuilderManager for use with Python's 'with' statement. + +C API: LLVMAppendBasicBlockInContext, LLVMCreateBuilderInContext)") // Verification .def("verify", &LLVMFunctionWrapper::verify, R"(Verify function. C API: LLVMVerifyFunction - + Returns True if the function is valid, False otherwise. C API: LLVMVerifyFunction)") .def("verify_and_print", &LLVMFunctionWrapper::verify_and_print, R"(Verify function and print errors to stderr. C API: LLVMVerifyFunction)") + .def("optimize", &LLVMFunctionWrapper::optimize, "pipeline"_a, + "target_machine"_a.none() = nullptr, + "options"_a.none() = nullptr, + R"doc(Optimize this function with an LLVM PassBuilder function pipeline string. + +Args: + pipeline: Function pass pipeline string (e.g., 'instcombine,simplifycfg'). + target_machine: Optional target machine for target-specific passes. + options: Optional PassBuilderOptions. + +This mutates the function in place. + +C API: LLVMRunPassesOnFunction)doc") // ===================================================================== // Intrinsic functions // ===================================================================== .def_prop_ro("intrinsic_id", &LLVMFunctionWrapper::intrinsic_id, R"(Get the intrinsic ID for this function. - + Returns 0 if the function is not an intrinsic. C API: LLVMGetIntrinsicID)") @@ -13281,7 +15040,7 @@ Valid when reading: .def_prop_rw("gc", &LLVMFunctionWrapper::get_gc, &LLVMFunctionWrapper::set_gc, R"(Get or set the GC name for this function. - + Returns None if no GC is set. C API: LLVMGetGC, LLVMSetGC)") @@ -13344,6 +15103,30 @@ Valid when: R"(Get the context this builder was created in. C API: LLVMCreateBuilderInContext)") + .def("debug_location", + nb::overload_cast( + &LLVMBuilderWrapper::debug_location, nb::const_), + "loc"_a, nb::rv_policy::take_ownership, + R"(Temporarily set this builder's current debug location. + +Use with a with-statement: + with builder.debug_location(loc): + inst = builder.add(a, b) + +C API: LLVMSetCurrentDebugLocation2)") + .def("debug_location", + nb::overload_cast( + &LLVMBuilderWrapper::debug_location, nb::const_), + nb::kw_only(), "line"_a, "column"_a, "scope"_a, + "inlined_at"_a.none() = nullptr, nb::rv_policy::take_ownership, + R"(Create and temporarily set this builder's current debug location. + +Use with a with-statement: + with builder.debug_location(line=12, column=4, scope=subprogram): + inst = builder.add(a, b) + +C API: LLVMDIBuilderCreateDebugLocation, LLVMSetCurrentDebugLocation2)") // Arithmetic .def("add", &LLVMBuilderWrapper::add, "lhs"_a, "rhs"_a, "name"_a = "", R"(Build add. @@ -13473,12 +15256,18 @@ Valid when: C API: LLVMBuildBinOp)") // Memory - .def("alloca", &LLVMBuilderWrapper::build_alloca, "ty"_a, "name"_a = "", - R"(Build alloca. + .def("alloca", + nb::overload_cast( + &LLVMBuilderWrapper::build_alloca), + "ty"_a, "name"_a = "", + R"(Build scalar alloca. C API: LLVMBuildAlloca)") - .def("array_alloca", &LLVMBuilderWrapper::build_array_alloca, "ty"_a, - "size"_a, "name"_a = "", + .def("alloca", + nb::overload_cast( + &LLVMBuilderWrapper::build_alloca), + "ty"_a, "count"_a, "name"_a = "", R"(Build array alloca. C API: LLVMBuildArrayAlloca)") @@ -13644,6 +15433,33 @@ For indirect calls through raw pointers, use the explicit-type overload. Prefer the 2-arg form call(func, args) for direct calls. C API: LLVMBuildCall2)") + .def("intrinsic", + [](LLVMBuilderWrapper &self, const std::string &name, + const Iterable &args, + const std::string &name_hint) { + return self.intrinsic(name, args, Iterable(), + name_hint); + }, + "name"_a, "args"_a, nb::kw_only(), "name_hint"_a = "", + R"(Build a call to a non-overloaded LLVM intrinsic by intrinsic name. + +Args: + name: Intrinsic name, such as "llvm.trap". + args: Call operands. + name_hint: Optional result name for non-void intrinsics. + +C API: LLVMLookupIntrinsicID, LLVMGetIntrinsicDeclaration, LLVMBuildCall2)") + .def("intrinsic", &LLVMBuilderWrapper::intrinsic, "name"_a, "args"_a, + nb::kw_only(), "overloaded_types"_a, "name_hint"_a = "", + R"(Build a call to an overloaded LLVM intrinsic by intrinsic name. + +Args: + name: Intrinsic name, such as "llvm.sqrt" or "llvm.memcpy". + args: Call operands. + overloaded_types: LLVM overload-disambiguation types for overloaded intrinsics. + name_hint: Optional result name for non-void intrinsics. + +C API: LLVMLookupIntrinsicID, LLVMGetIntrinsicDeclaration, LLVMBuildCall2)") .def("unreachable", &LLVMBuilderWrapper::unreachable, R"(Build unreachable. @@ -13873,6 +15689,25 @@ Examples: Attribute.enum(ctx, "noreturn") Attribute.enum(ctx, "align", 16) +C API: LLVMCreateEnumAttribute)") + .def_static( + "memory", + [](LLVMContextWrapper &ctx, const std::string &effects) { + return ctx.create_enum_attribute("memory", + encode_memory_effects(effects)); + }, + "context"_a, "effects"_a = "none", + R"(Create a memory(...) enum attribute. + +The effects string accepts "none", "read", "write", "readwrite", or +location-specific entries such as "argmem: read". LLVM-version-specific memory +locations such as errnomem are encoded when supported by the linked LLVM. + +Examples: + Attribute.memory(ctx, "none") + Attribute.memory(ctx, "read") + Attribute.memory(ctx, "argmem: read") + C API: LLVMCreateEnumAttribute)") .def_static( "type", @@ -13970,6 +15805,20 @@ Examples: attrs.add("align", 16) C API: LLVMAddAttributeAtIndex / LLVMAddCallSiteAttribute)") + .def("add_memory", &LLVMAttributeAccessorWrapper::add_memory, + "effects"_a = "none", + R"(Add a memory(...) enum attribute. + +The effects string accepts "none", "read", "write", "readwrite", or +location-specific entries such as "argmem: read". LLVM-version-specific memory +locations such as errnomem are encoded when supported by the linked LLVM. + +Examples: + attrs.add_memory("none") + attrs.add_memory("read") + attrs.add_memory("argmem: read") + +C API: LLVMCreateEnumAttribute, LLVMAddAttributeAtIndex / LLVMAddCallSiteAttribute)") .def("add_type", &LLVMAttributeAccessorWrapper::add_type, "name"_a, "type"_a, R"(Add a type attribute by name.)") @@ -13998,42 +15847,6 @@ removes a string attribute with the same key.)") C API: LLVMGetComdatSelectionKind, LLVMSetComdatSelectionKind)"); - // Value metadata entries wrapper (for global/instruction metadata copying) - nb::class_(m, "ValueMetadataEntries") - .def("__len__", &LLVMValueMetadataEntriesWrapper::size) - .def("get_kind", &LLVMValueMetadataEntriesWrapper::get_kind, "index"_a, - R"(Get metadata kind at index. - -C API: LLVMValueMetadataEntriesGetKind)") - .def("get_metadata", &LLVMValueMetadataEntriesWrapper_get_metadata, - "index"_a, - R"(Get metadata at index. - -C API: LLVMValueMetadataEntriesGetMetadata)"); - - // Named metadata node wrapper - nb::class_(m, "NamedMDNode") - .def("__eq__", [](const LLVMNamedMDNodeWrapper &a, - const LLVMNamedMDNodeWrapper &b) { return a == b; }) - .def("__ne__", [](const LLVMNamedMDNodeWrapper &a, - const LLVMNamedMDNodeWrapper &b) { return a != b; }) - .def("__hash__", - [](const LLVMNamedMDNodeWrapper &v) { - return std::hash{}(v.m_ref); - }) - .def_prop_ro("name", &LLVMNamedMDNodeWrapper::get_name, - R"(Get name. - -C API: LLVMGetNamedMetadataName)") - .def_prop_ro("next", &LLVMNamedMDNodeWrapper::next, - R"(Get next. - -C API: LLVMGetNextNamedMetadata)") - .def_prop_ro("prev", &LLVMNamedMDNodeWrapper::prev, - R"(Get previous. - -C API: LLVMGetPreviousNamedMetadata)"); - // Module wrapper nb::class_(m, "Module") .def("__eq__", [](const LLVMModuleWrapper &a, @@ -14207,40 +16020,6 @@ address space, and resolver match. Pass reuse_existing=False for LLVM's raw inserting behavior. C API: LLVMGetNamedGlobalIFunc, LLVMAddGlobalIFunc)") - // Named metadata support - .def_prop_ro("first_named_metadata", - &LLVMModuleWrapper::first_named_metadata, - R"(First named metadata. - -C API: LLVMGetFirstNamedMetadata)") - .def_prop_ro("last_named_metadata", - &LLVMModuleWrapper::last_named_metadata, - R"(Last named metadata. - -C API: LLVMGetLastNamedMetadata)") - .def("get_named_metadata", &LLVMModuleWrapper::get_named_metadata, - "name"_a, R"(Get named metadata. - -C API: LLVMGetNamedMetadata)") - .def("add_named_metadata", - &LLVMModuleWrapper::add_named_metadata, "name"_a, - R"(Add named metadata, or return the existing node with this name. - -This method has get-or-insert behavior: if named metadata with `name` already -exists in the module, it is returned unchanged; otherwise a new empty named -metadata node is created and returned. - -C API: LLVMGetOrInsertNamedMetadata)") - .def("get_named_metadata_num_operands", - &LLVMModuleWrapper::get_named_metadata_num_operands, "name"_a, - R"(Get operand count. - -C API: LLVMGetNamedMetadataNumOperands)") - .def("get_named_metadata_operands", - &LLVMModuleWrapper::get_named_metadata_operands, "name"_a, - R"(Get operands. - -C API: LLVMGetNamedMetadataOperands)") // Inline assembly support .def_prop_rw("inline_asm", &LLVMModuleWrapper::get_inline_asm, &LLVMModuleWrapper::set_inline_asm, @@ -14255,7 +16034,7 @@ metadata node is created and returned. .def("write_bitcode_to_file", &LLVMModuleWrapper::write_bitcode_to_file, "path"_a, R"(Write the module as bitcode to a file. - + Args: path: Output file path @@ -14263,7 +16042,7 @@ metadata node is created and returned. .def("write_bitcode_to_memory_buffer", &LLVMModuleWrapper::write_bitcode_to_memory_buffer, R"(Write the module as bitcode to a bytes object. - + Returns: bytes: The bitcode data. @@ -14271,9 +16050,9 @@ metadata node is created and returned. // Linker methods .def("link_module", &LLVMModuleWrapper::link_module, "src"_a, R"(Link another module into this module. - + The source module is destroyed after linking. - + Args: src: Source module to link in @@ -14299,45 +16078,34 @@ Returns: // Module printing to file .def("print_to_file", &LLVMModuleWrapper::print_to_file, "filename"_a, R"(Print the module IR to a file. - + Args: filename: Output file path C API: LLVMPrintModuleToFile)") - // Named metadata operand - .def("add_named_metadata_operand", - &LLVMModuleWrapper::add_named_metadata_operand, "name"_a, "md"_a, - R"(Add operand. + .def_prop_ro("named_metadata", &LLVMModuleWrapper::named_metadata, + R"(Named metadata mapping view. -C API: LLVMAddNamedMetadataOperand)") - // ===================================================================== - // Module Flags API - // ===================================================================== - .def("add_module_flag", &LLVMModuleWrapper::add_module_flag, "behavior"_a, - "key"_a, "val"_a, - R"(Add a module-level flag. +Example: + mod.named_metadata["llvm.dbg.cu"].append(compile_unit) -Args: - behavior: The merge behavior (ModuleFlagBehavior enum) - key: The flag name - val: The metadata value +C API: LLVMGetOrInsertNamedMetadata, LLVMAddNamedMetadataOperand)") + .def_prop_ro("module_flags", &LLVMModuleWrapper::module_flags, + R"(Module flags view keyed by flag name. -C API: LLVMAddModuleFlag)") - .def("get_module_flag", &LLVMModuleWrapper::get_module_flag, "key"_a, - R"(Get a module-level flag by key. +Example: + mod.module_flags.add("Debug Info Version", llvm.ModuleFlagBehavior.Warning, md) -Args: - key: The flag name - -Returns: - The metadata value, or None if not found. - -C API: LLVMGetModuleFlag)") +C API: LLVMAddModuleFlag, LLVMGetModuleFlag)") // Module methods .def_prop_ro("context", &LLVMModuleWrapper::get_context, nb::rv_policy::take_ownership, R"(Get the context for this module. +C API: LLVMGetModuleContext)") + .def_prop_ro("types", &LLVMModuleWrapper::types, + R"(Type factory for this module's context. + C API: LLVMGetModuleContext)") .def_prop_rw("is_new_dbg_info_format", &LLVMModuleWrapper::is_new_dbg_info_format, @@ -14362,6 +16130,43 @@ Args: options: Optional PassBuilderOptions C API: LLVMRunPasses)doc") + .def("optimize", &LLVMModuleWrapper::optimize, "pipeline"_a, + "target_machine"_a.none() = nullptr, + "options"_a.none() = nullptr, + R"doc(Optimize this module with an LLVM PassBuilder pipeline string. + +Args: + pipeline: Pass pipeline string (e.g., 'default'). + target_machine: Optional target machine for target-specific passes. + options: Optional PassBuilderOptions. + +This mutates the module in place. + +C API: LLVMRunPasses)doc") + .def("emit_object", &LLVMModuleWrapper::emit_object, + "target_machine"_a.none() = nullptr, + R"doc(Emit this module to an object file. + +Args: + target_machine: Optional target machine. If omitted, a host target machine + is created internally. + +Returns: + bytes: The generated object file. + +C API: LLVMTargetMachineEmitToMemoryBuffer)doc") + .def("emit_assembly", &LLVMModuleWrapper::emit_assembly, + "target_machine"_a.none() = nullptr, + R"doc(Emit this module to assembly. + +Args: + target_machine: Optional target machine. If omitted, a host target machine + is created internally. + +Returns: + bytes: The generated assembly. + +C API: LLVMTargetMachineEmitToMemoryBuffer)doc") .def("create_dibuilder", &LLVMModuleWrapper::create_dibuilder, nb::rv_policy::take_ownership, R"(Create a debug info builder for this module. @@ -14395,6 +16200,14 @@ Returns a BuilderManager for use with Python's 'with' statement. // TypeFactory wrapper (property-based type namespace) nb::class_(m, "TypeFactory") + .def("__eq__", &LLVMTypeFactoryWrapper::operator==, "other"_a, + R"(Return True when both type factories use the same LLVM context. + +C API limitation: compares stored LLVMContextRef pointers)") + .def("__ne__", &LLVMTypeFactoryWrapper::operator!=, "other"_a, + R"(Return True when type factories use different LLVM contexts. + +C API limitation: compares stored LLVMContextRef pointers)") // Fixed-width integer types .def_prop_ro("i1", &LLVMTypeFactoryWrapper::i1, R"(1-bit int. @@ -14643,10 +16456,6 @@ Returns a BuilderManager for use with Python's 'with' statement. R"(Create a struct constant in this context. C API: LLVMConstStructInContext)") - .def("get_md_kind_id", &LLVMContextWrapper::get_md_kind_id, "name"_a, - R"(Get metadata kind ID. - -C API: LLVMGetMDKindIDInContext)") // Metadata creation methods .def("md_string", &LLVMContextWrapper::md_string, "str"_a, R"(Create metadata string. @@ -14670,8 +16479,9 @@ Common scope names include "singlethread" for thread-local synchronization. R"(Get the function type of an intrinsic in this context. C API: LLVMIntrinsicGetType)") - .def("create_debug_location", &LLVMContextWrapper::create_debug_location, - "line"_a, "column"_a, "scope"_a, "inlined_at"_a.none(), + .def("debug_location", &LLVMContextWrapper::debug_location, + "line"_a, "column"_a, "scope"_a, + "inlined_at"_a.none() = nullptr, R"(Create a debug location metadata node. C API: LLVMDIBuilderCreateDebugLocation)"); @@ -14781,12 +16591,25 @@ Valid when: "create_operand_bundle", [](const std::string &tag, const Iterable &args, LLVMContextWrapper *ctx) { + if (!ctx) + throw LLVMMemoryError("create_operand_bundle requires a context"); + ctx->check_valid(); std::vector arg_refs; - for (const auto &a : args) + std::vector> module_tokens; + for (const auto &a : args) { + a.check_valid(); + if (a.m_context_token != ctx->m_token) { + throw LLVMAssertionError( + "create_operand_bundle requires values from the context"); + } + if (a.m_module_token) + module_tokens.push_back(a.m_module_token); arg_refs.push_back(a.m_ref); + } LLVMOperandBundleRef bundle = LLVMCreateOperandBundle( tag.c_str(), tag.size(), arg_refs.data(), arg_refs.size()); - return new LLVMOperandBundleWrapper(bundle, ctx->m_token); + return new LLVMOperandBundleWrapper(bundle, ctx->m_token, + std::move(module_tokens)); }, "tag"_a, "args"_a, "ctx"_a, nb::rv_policy::take_ownership, R"(Create operand bundle. @@ -14839,68 +16662,11 @@ Raises LLVMError if the target is not found. C API: LLVMGetTargetFromName)"); - // Target initialization functions are explicit so callers control process-wide - // LLVM target registration. - m.def("initialize_all", &initialize_all, - R"(Initialize all targets, MCs, ASM printers, and ASM parsers. -Convenience function that calls initialize_all_target_infos(), -initialize_all_targets(), initialize_all_target_mcs(), -initialize_all_asm_printers(), and initialize_all_asm_parsers().)"); - m.def("initialize_all_target_infos", &initialize_all_target_infos, - R"(Initialize all target infos. - -C API: LLVMInitializeAllTargetInfos)"); - m.def("initialize_all_targets", &initialize_all_targets, - R"(Initialize all targets. - -C API: LLVMInitializeAllTargets)"); - m.def("initialize_all_target_mcs", &initialize_all_target_mcs, - R"(Initialize all target MCs. - -C API: LLVMInitializeAllTargetMCs)"); - m.def("initialize_all_asm_printers", &initialize_all_asm_printers, - R"(Initialize all ASM printers. - -C API: LLVMInitializeAllAsmPrinters)"); - m.def("initialize_all_asm_parsers", &initialize_all_asm_parsers, - R"(Initialize all ASM parsers. - -C API: LLVMInitializeAllAsmParsers)"); - m.def("initialize_all_disassemblers", &initialize_all_disassemblers, - R"(Initialize all disassemblers. - -C API: LLVMInitializeAllDisassemblers)"); m.def("get_first_target", &get_first_target, R"(Get the first registered target (returns None if no targets). C API: LLVMGetFirstTarget)"); - // Native target initialization - m.def("initialize_native_target", &initialize_native_target, - R"(Initialize the native target. - - Returns True on success, False on failure. - -C API: LLVMInitializeNativeTarget)"); - m.def("initialize_native_asm_printer", &initialize_native_asm_printer, - R"(Initialize the native ASM printer. - - Returns True on success, False on failure. - -C API: LLVMInitializeNativeAsmPrinter)"); - m.def("initialize_native_asm_parser", &initialize_native_asm_parser, - R"(Initialize the native ASM parser. - - Returns True on success, False on failure. - -C API: LLVMInitializeNativeAsmParser)"); - m.def("initialize_native_disassembler", &initialize_native_disassembler, - R"(Initialize the native disassembler. - - Returns True on success, False on failure. - -C API: LLVMInitializeNativeDisassembler)"); - // Host target queries m.attr("default_target_triple") = get_default_target_triple(); m.def("normalize_target_triple", &normalize_target_triple, "triple"_a, @@ -14962,7 +16728,7 @@ initialize_all_asm_printers(), and initialize_all_asm_parsers().)"); nb::class_(m, "TargetData", R"(Target data layout information. - + Provides information about type sizes and alignment for a specific target.)") .def("__str__", &LLVMTargetDataWrapper::to_string) .def_prop_ro("byte_order", &LLVMTargetDataWrapper::byte_order, @@ -15092,7 +16858,7 @@ Returns: .def("emit_to_file", &LLVMTargetMachineWrapper::emit_to_file, "mod"_a, "filename"_a, "file_type"_a, R"(Emit the module to a file. - + Args: mod: Module to emit filename: Output filename @@ -15103,11 +16869,11 @@ Returns: &LLVMTargetMachineWrapper::emit_to_memory_buffer, "mod"_a, "file_type"_a, R"(Emit the module to a memory buffer. - + Args: mod: Module to emit file_type: Type of output (AssemblyFile or ObjectFile) - + Returns: bytes: The generated output. @@ -15120,6 +16886,15 @@ Returns: nb::rv_policy::take_ownership, R"(Create a target machine for code generation. +C API: LLVMCreateTargetMachine)") + .def_static("host", &create_host_target_machine, "cpu"_a = "", + "features"_a = "", + "opt_level"_a = LLVMCodeGenLevelDefault, + "reloc_mode"_a = LLVMRelocDefault, + "code_model"_a = LLVMCodeModelDefault, + nb::rv_policy::take_ownership, + R"(Create a target machine for the host target. + C API: LLVMCreateTargetMachine)"); // NOTE: target machine creation moved to TargetMachine.create(). @@ -15130,7 +16905,7 @@ Returns: nb::class_(m, "PassBuilderOptions", R"(Options for the pass builder. - + Used to configure optimization passes when calling run_passes().)") .def(nb::init<>(), R"(Create options. @@ -15200,6 +16975,78 @@ Returns: // NOTE: pass execution moved to Module.run_passes(). + // ========================================================================== + // JIT Wrapper + // ========================================================================== + + nb::class_(m, "JITCtypesFunction", + R"(Callable ctypes wrapper for a JIT symbol.)") + .def("__call__", &LLVMCtypesFunctionWrapper::call, + R"(Call the underlying ctypes function. + +Valid while the owning JIT has not been disposed.)") + .def_prop_ro("ctypes_callable", &LLVMCtypesFunctionWrapper::ctypes_callable, + R"(The underlying ctypes callable.)"); + + nb::class_(m, "JIT", R"(In-process LLVM JIT using LLVM-C ORC LLJIT.)") + .def_static("host", &LLVMJITWrapper::host, nb::rv_policy::take_ownership, + R"(Create a host JIT. + +Use with a context manager: + with llvm.JIT.host() as jit: + ... + +C API: LLVMOrcCreateLLJIT)") + .def("__enter__", &LLVMJITWrapper::enter, nb::rv_policy::reference_internal, + R"(Enter the JIT context manager.)") + .def("__exit__", &LLVMJITWrapper::exit, "exc_type"_a.none(), + "exc_value"_a.none(), "traceback"_a.none(), + R"(Dispose the JIT when leaving the context manager.)") + .def("dispose", &LLVMJITWrapper::dispose, R"(Dispose the JIT.)") + .def_prop_ro("triple", &LLVMJITWrapper::triple, + R"(Get this JIT's target triple. + +C API: LLVMOrcLLJITGetTripleString)") + .def_prop_ro("data_layout", &LLVMJITWrapper::data_layout, + R"(Get this JIT's data layout string. + +C API: LLVMOrcLLJITGetDataLayoutStr)") + .def("add_module", &LLVMJITWrapper::add_module, "mod"_a, + R"(Add a module to the JIT. + +This transfers ownership from the user's perspective: the Python Module wrapper +is invalid after the call succeeds. + +C API: LLVMOrcLLJITAddLLVMIRModule)") + .def("lookup", &LLVMJITWrapper::lookup, "name"_a, + R"(Look up a JIT symbol address by name. + +Returns: + int: The symbol address. + +C API: LLVMOrcLLJITLookup)") + .def("ctypes_function", &LLVMJITWrapper::ctypes_function, "name"_a, + "restype"_a, "argtypes"_a, nb::rv_policy::take_ownership, + nb::keep_alive<0, 1>(), + R"(Return a callable ctypes wrapper for a JIT symbol. + +Args: + name: Symbol name to look up. + restype: ctypes return type. + argtypes: Iterable of ctypes argument types. + +The returned wrapper keeps the JIT object alive and checks that the JIT has +not been disposed before calling. + +C API: LLVMOrcLLJITLookup)") + .def("add_symbol", &LLVMJITWrapper::add_symbol, "name"_a, "target"_a, + R"(Add an absolute symbol to the JIT. + +`target` may be a non-negative integer address or a ctypes object. ctypes +objects are pinned by the JIT to keep callbacks alive. + +C API: LLVMOrcAbsoluteSymbols, LLVMOrcJITDylibDefine)"); + // Memory buffer is internal only - not exposed to Python // ============================================================================= @@ -15216,12 +17063,12 @@ Returns: .def("disasm_instruction", &LLVMDisasmContextWrapper::disasm_instruction, "bytes"_a, "offset"_a, "pc"_a, R"(Disassemble a single instruction. - + Args: bytes: The byte array containing machine code offset: Offset into bytes to start disassembling pc: Program counter value for the instruction - + Returns: Tuple of (bytes_consumed, disassembly_string) If bytes_consumed is 0, disassembly failed. @@ -15334,9 +17181,9 @@ Valid when: C API: LLVMObjectFileCopySymbolIterator)") .def("copy_to_memory_buffer", &LLVMBinaryWrapper::copy_to_memory_buffer, R"(Copy the binary's contents to a memory buffer. - + Returns a copy of the binary's backing memory buffer as bytes. - + Returns: bytes: A copy of the binary data. @@ -15431,8 +17278,11 @@ Valid when: self.m_binary_token); }, nb::rv_policy::take_ownership, + nb::keep_alive<0, 1>(), R"(Relocation iterator. +The returned relocation iterator keeps this section iterator alive. + Valid when: - parent binary is still valid (not disposed) - section iterator is not at end (`is_at_end() == False`) @@ -15445,14 +17295,15 @@ Valid when: "__next__", [](LLVMSectionIteratorWrapper &self) -> LLVMSectionIteratorWrapper * { self.check_valid(); + if (self.is_at_end()) + throw nb::stop_iteration(); if (self.m_python_iter_started) { self.move_next(); + if (self.is_at_end()) + throw nb::stop_iteration(); } else { self.m_python_iter_started = true; } - if (self.is_at_end()) { - throw nb::stop_iteration(); - } return &self; }, nb::rv_policy::reference_internal); @@ -15510,14 +17361,15 @@ TODO: needs safe LLVM-C API equivalent. "__next__", [](LLVMSymbolIteratorWrapper &self) -> LLVMSymbolIteratorWrapper * { self.check_valid(); + if (self.is_at_end()) + throw nb::stop_iteration(); if (self.m_python_iter_started) { self.move_next(); + if (self.is_at_end()) + throw nb::stop_iteration(); } else { self.m_python_iter_started = true; } - if (self.is_at_end()) { - throw nb::stop_iteration(); - } return &self; }, nb::rv_policy::reference_internal); @@ -15580,14 +17432,15 @@ Valid when: [](LLVMRelocationIteratorWrapper &self) -> LLVMRelocationIteratorWrapper * { self.check_valid(); + if (self.is_at_end()) + throw nb::stop_iteration(); if (self.m_python_iter_started) { self.move_next(); + if (self.is_at_end()) + throw nb::stop_iteration(); } else { self.m_python_iter_started = true; } - if (self.is_at_end()) { - throw nb::stop_iteration(); - } return &self; }, nb::rv_policy::reference_internal); @@ -15634,17 +17487,6 @@ Valid when: // BitReader functions - // Static metadata function - m.def( - "get_md_kind_id", - [](const std::string &name) { - return LLVMGetMDKindID(name.c_str(), - static_cast(name.size())); - }, - "name"_a, R"(Get metadata kind ID. - -C API: LLVMGetMDKindID)"); - // NOTE: get_module_context has been moved to Module.context property // ========================================================================== @@ -15688,15 +17530,20 @@ Valid when: C API: LLVMDIBuilderFinalize)") // File & Scope Creation - .def("create_file", &LLVMDIBuilderWrapper::create_file, "filename"_a, - "directory"_a, R"(Create file debug info metadata. + .def("file", &LLVMDIBuilderWrapper::file, "filename"_a, + "directory"_a = ".", + R"(Create file debug info metadata. C API: LLVMDIBuilderCreateFile)") - .def("create_compile_unit", &LLVMDIBuilderWrapper::create_compile_unit, - "lang"_a, "file"_a, "producer"_a, "is_optimized"_a, "flags"_a, - "runtime_ver"_a, "split_name"_a, "kind"_a, "dwo_id"_a, - "split_debug_inlining"_a, "debug_info_for_profiling"_a, "sys_root"_a, - "sdk"_a, R"(Create compile unit debug info. + .def("compile_unit", &LLVMDIBuilderWrapper::compile_unit, + "language"_a, "file"_a, "producer"_a, + "is_optimized"_a = false, "flags"_a = "", + "runtime_ver"_a = 0, "split_name"_a = "", + "kind"_a = LLVMDWARFEmissionFull, "dwo_id"_a = 0, + "split_debug_inlining"_a = true, + "debug_info_for_profiling"_a = false, "sys_root"_a = "", + "sdk"_a = "", + R"(Create compile unit debug info with defaults for common use. C API: LLVMDIBuilderCreateCompileUnit)") .def("create_module", &LLVMDIBuilderWrapper::create_module, @@ -15729,6 +17576,16 @@ Valid when: R"(Create subroutine type debug info (function signature). C API: LLVMDIBuilderCreateSubroutineType)") + .def("function", &LLVMDIBuilderWrapper::function, "fn"_a, "name"_a, + "file"_a, "line"_a, "return_type"_a, "param_types"_a, + "scope"_a.none() = nullptr, "linkage_name"_a = "", + "is_local_to_unit"_a = false, "is_definition"_a = true, + "scope_line"_a = 0, "flags"_a = 0, "is_optimized"_a = false, + R"(Create function debug info, set it on the LLVM function, and return it. + +This convenience method maps simple IR types to debug types. + +C API: LLVMDIBuilderCreateFunction, LLVMSetSubprogram)") // Type Creation .def("create_basic_type", &LLVMDIBuilderWrapper::create_basic_type, "name"_a, "size_in_bits"_a, "encoding"_a, "flags"_a, @@ -15816,6 +17673,13 @@ Valid when: "always_preserve"_a, "flags"_a, "align_in_bits"_a, R"(Create local (auto) variable debug info. +C API: LLVMDIBuilderCreateAutoVariable)") + .def("local_variable", &LLVMDIBuilderWrapper::local_variable, + "scope"_a, "name"_a, "file"_a, "line_no"_a, "type"_a, + "always_preserve"_a = true, "flags"_a = 0, + "align_in_bits"_a = 0, + R"(Create local variable debug info from a simple IR type. + C API: LLVMDIBuilderCreateAutoVariable)") .def("create_global_variable_expression", &LLVMDIBuilderWrapper::create_global_variable_expression, "scope"_a, @@ -16009,7 +17873,7 @@ Valid when: "derived_from"_a.none(), "elements"_a, "vtable_holder"_a.none(), "template_params"_a.none(), "unique_id"_a, R"(Create a C++ class type. - + Args: scope: The scope containing this class. name: Class name. @@ -16031,9 +17895,9 @@ Valid when: "name"_a, "file"_a, "line_no"_a, "type"_a, "flags"_a, "const_val"_a.none(), "align_in_bits"_a, R"(Create a static member type. - + Used for C++ static class members. - + Args: scope: The scope containing this member. name: Member name. @@ -16049,9 +17913,9 @@ Valid when: &LLVMDIBuilderWrapper::create_member_pointer_type, "pointee_type"_a, "class_type"_a, "size_in_bits"_a, "align_in_bits"_a, "flags"_a, R"(Create a C++ member pointer type. - + Used for pointer-to-member types (e.g., int MyClass::*). - + Args: pointee_type: The type being pointed to. class_type: The class type. @@ -16065,9 +17929,9 @@ Valid when: &LLVMDIBuilderWrapper::insert_declare_record_before, "storage"_a, "var_info"_a, "expr"_a, "debug_loc"_a, "insert_before"_a, R"(Insert a declare record before an instruction. - + Only use in "new debug format" mode (LLVMIsNewDbgInfoFormat is true). - + Args: storage: The storage location (alloca). var_info: Variable debug info. @@ -16080,9 +17944,9 @@ Valid when: &LLVMDIBuilderWrapper::insert_dbg_value_record_before, "val"_a, "var_info"_a, "expr"_a, "debug_loc"_a, "insert_before"_a, R"(Insert a dbg value record before an instruction. - + Only use in "new debug format" mode (LLVMIsNewDbgInfoFormat is true). - + Args: val: The value to track. var_info: Variable debug info. @@ -16126,6 +17990,24 @@ Valid when: // NOTE: create_dibuilder has been moved to Module.create_dibuilder() method + auto require_di_type_metadata = [](const LLVMMetadataWrapper &self, + const char *accessor) { + self.check_valid(); + switch (LLVMGetMetadataKind(self.m_ref)) { + case LLVMDIBasicTypeMetadataKind: + case LLVMDIDerivedTypeMetadataKind: + case LLVMDICompositeTypeMetadataKind: + case LLVMDISubroutineTypeMetadataKind: + case LLVMDIStringTypeMetadataKind: + case LLVMDISubrangeTypeMetadataKind: + case LLVMDIFixedPointTypeMetadataKind: + return; + default: + throw LLVMAssertionError(std::string(accessor) + + " requires DIType metadata"); + } + }; + nb::class_(m, "Metadata") .def("__eq__", [](const LLVMMetadataWrapper &a, const LLVMMetadataWrapper &b) { return a.m_ref == b.m_ref; }) @@ -16135,10 +18017,6 @@ Valid when: [](const LLVMMetadataWrapper &v) { return std::hash{}(v.m_ref); }) - .def("as_value", &LLVMMetadataWrapper::as_value, "ctx"_a, - R"(Convert metadata to value. - -C API: LLVMMetadataAsValue)") .def("replace_all_uses_with", &LLVMMetadataWrapper::replace_all_uses_with, "md"_a, R"(Replace all uses of this temporary metadata. @@ -16149,6 +18027,40 @@ Valid when: R"(Replace this DISubprogram's subroutine type metadata. C API: LLVMDISubprogramReplaceType)") + .def_prop_ro("kind", &LLVMMetadataWrapper::kind, + R"(Return this metadata's LLVMMetadataKind value. + +C API: LLVMGetMetadataKind)") + .def_prop_ro("is_string", &LLVMMetadataWrapper::is_string, + R"(True if this metadata is an MDString. + +C API: LLVMGetMetadataKind)") + .def_prop_ro("is_node", &LLVMMetadataWrapper::is_node, + R"(True if this metadata is an MDNode. + +C API: LLVMMetadataAsValue, LLVMIsAMDNode)") + .def_prop_ro("is_value", &LLVMMetadataWrapper::is_value, + R"(True if this metadata wraps a Value. + +C API: LLVMGetMetadataKind)") + .def_prop_ro("string", &LLVMMetadataWrapper::string, + R"(Return this MDString's Python string value. + +Raises LLVMAssertionError when this metadata is not an MDString. + +C API: LLVMMetadataAsValue, LLVMGetMDString)") + .def_prop_ro("operands", &LLVMMetadataWrapper::operands, + R"(Return this MDNode's operands as Metadata objects. + +Raises LLVMAssertionError when this metadata is not an MDNode. + +C API: LLVMMetadataAsValue, LLVMGetMDNodeNumOperands, LLVMGetMDNodeOperands)") + .def_prop_ro("value", &LLVMMetadataWrapper::value, + R"(Return the Value wrapped by ConstantAsMetadata or LocalAsMetadata. + +Raises NotImplementedError because LLVM-C has no unwrap API for ValueAsMetadata. + +C API limitation)") // DI accessors as properties .def_prop_ro("di_node_tag", [](const LLVMMetadataWrapper &self) -> unsigned { self.check_valid(); @@ -16156,14 +18068,44 @@ Valid when: }, R"(Get DWARF tag from this debug info node. C API: LLVMGetDINodeTag)") - .def_prop_ro("di_type_name", [](const LLVMMetadataWrapper &self) -> std::string { - self.check_valid(); + .def_prop_ro("di_type_name", [require_di_type_metadata](const LLVMMetadataWrapper &self) -> std::string { + require_di_type_metadata(self, "di_type_name"); size_t len; const char *name = LLVMDITypeGetName(self.m_ref, &len); return std::string(name, len); }, R"(Get name from this debug info type. C API: LLVMDITypeGetName)") + .def_prop_ro("di_type_size_in_bits", [require_di_type_metadata](const LLVMMetadataWrapper &self) -> uint64_t { + require_di_type_metadata(self, "di_type_size_in_bits"); + return LLVMDITypeGetSizeInBits(self.m_ref); + }, R"(Get size in bits from this debug info type. + +C API: LLVMDITypeGetSizeInBits)") + .def_prop_ro("di_type_offset_in_bits", [require_di_type_metadata](const LLVMMetadataWrapper &self) -> uint64_t { + require_di_type_metadata(self, "di_type_offset_in_bits"); + return LLVMDITypeGetOffsetInBits(self.m_ref); + }, R"(Get offset in bits from this debug info type. + +C API: LLVMDITypeGetOffsetInBits)") + .def_prop_ro("di_type_align_in_bits", [require_di_type_metadata](const LLVMMetadataWrapper &self) -> uint32_t { + require_di_type_metadata(self, "di_type_align_in_bits"); + return LLVMDITypeGetAlignInBits(self.m_ref); + }, R"(Get alignment in bits from this debug info type. + +C API: LLVMDITypeGetAlignInBits)") + .def_prop_ro("di_type_line", [require_di_type_metadata](const LLVMMetadataWrapper &self) -> unsigned { + require_di_type_metadata(self, "di_type_line"); + return LLVMDITypeGetLine(self.m_ref); + }, R"(Get line from this debug info type. + +C API: LLVMDITypeGetLine)") + .def_prop_ro("di_type_flags", [require_di_type_metadata](const LLVMMetadataWrapper &self) -> unsigned { + require_di_type_metadata(self, "di_type_flags"); + return LLVMDITypeGetFlags(self.m_ref); + }, R"(Get DI flags from this debug info type. + +C API: LLVMDITypeGetFlags)") .def_prop_ro("di_location_line", [](const LLVMMetadataWrapper &self) -> unsigned { self.check_valid(); return LLVMDILocationGetLine(self.m_ref); @@ -16179,7 +18121,8 @@ Valid when: .def_prop_ro("di_location_scope", [](const LLVMMetadataWrapper &self) -> LLVMMetadataWrapper { self.check_valid(); return LLVMMetadataWrapper(LLVMDILocationGetScope(self.m_ref), - self.m_context_token); + self.m_context_token, self.m_ctx_ref, + self.m_module_token); }, R"(Get scope from this debug location. C API: LLVMDILocationGetScope)") @@ -16188,7 +18131,8 @@ Valid when: self.check_valid(); LLVMMetadataRef ref = LLVMDILocationGetInlinedAt(self.m_ref); if (ref) - return LLVMMetadataWrapper(ref, self.m_context_token); + return LLVMMetadataWrapper(ref, self.m_context_token, self.m_ctx_ref, + self.m_module_token); return std::nullopt; }, R"(Get inlined-at location from this debug location, or None. @@ -16198,7 +18142,8 @@ Valid when: self.check_valid(); LLVMMetadataRef ref = LLVMDIScopeGetFile(self.m_ref); if (ref) - return LLVMMetadataWrapper(ref, self.m_context_token); + return LLVMMetadataWrapper(ref, self.m_context_token, self.m_ctx_ref, + self.m_module_token); return std::nullopt; }, R"(Get file from this debug scope, or None. @@ -16238,7 +18183,8 @@ Valid when: self.check_valid(); LLVMMetadataRef ref = LLVMDIVariableGetFile(self.m_ref); if (ref) - return LLVMMetadataWrapper(ref, self.m_context_token); + return LLVMMetadataWrapper(ref, self.m_context_token, self.m_ctx_ref, + self.m_module_token); return std::nullopt; }, R"(Get file from this debug variable, or None. @@ -16248,7 +18194,8 @@ Valid when: self.check_valid(); LLVMMetadataRef ref = LLVMDIVariableGetScope(self.m_ref); if (ref) - return LLVMMetadataWrapper(ref, self.m_context_token); + return LLVMMetadataWrapper(ref, self.m_context_token, self.m_ctx_ref, + self.m_module_token); return std::nullopt; }, R"(Get scope from this debug variable, or None. @@ -16263,7 +18210,7 @@ Valid when: self.check_valid(); return LLVMMetadataWrapper( LLVMDIGlobalVariableExpressionGetVariable(self.m_ref), - self.m_context_token); + self.m_context_token, self.m_ctx_ref, self.m_module_token); }, R"(Get the DIGlobalVariable from this DIGlobalVariableExpression. C API: LLVMDIGlobalVariableExpressionGetVariable)") @@ -16271,7 +18218,7 @@ Valid when: self.check_valid(); return LLVMMetadataWrapper( LLVMDIGlobalVariableExpressionGetExpression(self.m_ref), - self.m_context_token); + self.m_context_token, self.m_ctx_ref, self.m_module_token); }, R"(Get the DIExpression from this DIGlobalVariableExpression. C API: LLVMDIGlobalVariableExpressionGetExpression)"); @@ -16290,10 +18237,10 @@ Valid when: }, "name"_a, R"(Look up an intrinsic ID by name. - + Args: name: Intrinsic name (e.g., "llvm.memcpy") - + Returns: Intrinsic ID (0 if not found). @@ -16303,7 +18250,7 @@ Valid when: "intrinsic_is_overloaded", [](unsigned id) -> bool { return LLVMIntrinsicIsOverloaded(id); }, "id"_a, R"(Check if an intrinsic is overloaded. - + Overloaded intrinsics require type parameters to get a declaration. C API: LLVMIntrinsicIsOverloaded)"); @@ -16322,7 +18269,7 @@ Valid when: // NOTE: intrinsic type lookup moved to Context.get_intrinsic_type(). // NOTE: cast-opcode and metadata-node mutation helpers moved to Value methods. - // NOTE: debug-location creation moved to Context.create_debug_location(). + // NOTE: debug-location creation is exposed as Context.debug_location(). // NOTE: DISubprogram type replacement moved to Metadata.replace_di_subprogram_type(). // NOTE: debug record traversal moved to Value.first_dbg_record, // Value.last_dbg_record, and DbgRecord.next/prev. diff --git a/tests/regressions/test_api_surface_cleanup.py b/tests/regressions/test_api_surface_cleanup.py deleted file mode 100644 index c580de0..0000000 --- a/tests/regressions/test_api_surface_cleanup.py +++ /dev/null @@ -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", 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") diff --git a/tests/regressions/test_api_ux_cleanup.py b/tests/regressions/test_api_ux_cleanup.py new file mode 100644 index 0000000..80d2438 --- /dev/null +++ b/tests/regressions/test_api_ux_cleanup.py @@ -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") + 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", 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", 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") diff --git a/tests/regressions/test_attribute_kind_lookup.py b/tests/regressions/test_attribute_kind_lookup.py index 3960148..87e0773 100644 --- a/tests/regressions/test_attribute_kind_lookup.py +++ b/tests/regressions/test_attribute_kind_lookup.py @@ -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") diff --git a/tests/regressions/test_binary_iterator_end_guards.py b/tests/regressions/test_binary_iterator_end_guards.py index ab2ccb6..3d09d52 100644 --- a/tests/regressions/test_binary_iterator_end_guards.py +++ b/tests/regressions/test_binary_iterator_end_guards.py @@ -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") diff --git a/tests/regressions/test_binary_iterators.py b/tests/regressions/test_binary_iterators.py index 4953204..9159652 100644 --- a/tests/regressions/test_binary_iterators.py +++ b/tests/regressions/test_binary_iterators.py @@ -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") diff --git a/tests/regressions/test_full_tree_guard_matrix.py b/tests/regressions/test_full_tree_guard_matrix.py index 29e2d8d..84a56bb 100644 --- a/tests/regressions/test_full_tree_guard_matrix.py +++ b/tests/regressions/test_full_tree_guard_matrix.py @@ -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", ) diff --git a/tests/regressions/test_github_issues_11_12_13.py b/tests/regressions/test_github_issues_11_12_13.py new file mode 100644 index 0000000..7f39bb3 --- /dev/null +++ b/tests/regressions/test_github_issues_11_12_13.py @@ -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") diff --git a/tests/regressions/test_memory_module_lifetimes.py b/tests/regressions/test_memory_module_lifetimes.py new file mode 100644 index 0000000..d432f09 --- /dev/null +++ b/tests/regressions/test_memory_module_lifetimes.py @@ -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") diff --git a/tests/regressions/test_metadata_ux_cleanup.py b/tests/regressions/test_metadata_ux_cleanup.py new file mode 100644 index 0000000..b82d3a4 --- /dev/null +++ b/tests/regressions/test_metadata_ux_cleanup.py @@ -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") diff --git a/tests/regressions/test_ollvm_obf_golden_master.py b/tests/regressions/test_ollvm_obf_golden_master.py index ac3397d..66c041f 100644 --- a/tests/regressions/test_ollvm_obf_golden_master.py +++ b/tests/regressions/test_ollvm_obf_golden_master.py @@ -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 diff --git a/tests/regressions/test_transformation_gap_apis.py b/tests/regressions/test_transformation_gap_apis.py index 84e5807..32143a2 100644 --- a/tests/regressions/test_transformation_gap_apis.py +++ b/tests/regressions/test_transformation_gap_apis.py @@ -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 diff --git a/tests/regressions/test_value_kind_guards.py b/tests/regressions/test_value_kind_guards.py index 10da3d4..a5590ab 100644 --- a/tests/regressions/test_value_kind_guards.py +++ b/tests/regressions/test_value_kind_guards.py @@ -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 diff --git a/tests/test_builder_memory.py b/tests/test_builder_memory.py index aea0d49..4da276b 100755 --- a/tests/test_builder_memory.py +++ b/tests/test_builder_memory.py @@ -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") diff --git a/tests/test_examples.py b/tests/test_examples.py index 1392da9..7347b2a 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -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 diff --git a/tests/test_feature_matrix.py b/tests/test_feature_matrix.py index 03a952d..c80f907 100644 --- a/tests/test_feature_matrix.py +++ b/tests/test_feature_matrix.py @@ -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() diff --git a/tests/test_passbuilder.py b/tests/test_passbuilder.py index 34adc62..1ae01aa 100644 --- a/tests/test_passbuilder.py +++ b/tests/test_passbuilder.py @@ -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) diff --git a/tests/test_target_codegen.py b/tests/test_target_codegen.py index d1b8665..d3016fd 100644 --- a/tests/test_target_codegen.py +++ b/tests/test_target_codegen.py @@ -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(";")