mirror of
https://github.com/LLVMParty/llvm-nanobind
synced 2026-06-21 13:43:38 +00:00
Add high-level API for (named) metadata
This commit is contained in:
@@ -61,6 +61,9 @@ More examples worth browsing:
|
||||
- [`examples/optimize_module.py`](examples/optimize_module.py) - optimize a module with a 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
|
||||
@@ -70,7 +73,8 @@ 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
|
||||
- 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 initialization/lookup, data layouts, `Module.emit_object()`, `Module.emit_assembly()`, target-machine emission, and PassBuilder pipeline execution via `Module.optimize()` / `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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -95,7 +95,7 @@ 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
|
||||
|
||||
Factories with a natural owner should be static methods instead (for example,
|
||||
`BinaryManager.from_file()` rather than a module-level binary factory).
|
||||
@@ -124,7 +124,17 @@ This catches errors with clear messages while keeping the API simple.
|
||||
|
||||
---
|
||||
|
||||
### 8. Method Chaining Potential
|
||||
### 8. C API Anchors in Binding Docs
|
||||
|
||||
**Principle**: Every public binding method or property should document the LLVM-C API it uses with a `<sub>C API: ...</sub>` line.
|
||||
|
||||
**Why**: The C API anchor keeps the binding grounded in LLVM's actual surface, makes generated stubs searchable, and lets future maintainers or agents verify behavior against LLVM headers.
|
||||
|
||||
If a method is a pure convenience wrapper, list the primary LLVM-C calls it composes. If LLVM-C lacks a required operation, document that as `<sub>C API limitation</sub>`.
|
||||
|
||||
---
|
||||
|
||||
### 9. Method Chaining Potential
|
||||
|
||||
**Principle**: Methods returning values enable fluent APIs.
|
||||
|
||||
|
||||
@@ -60,6 +60,26 @@ tm = llvm.TargetMachine.host()
|
||||
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
|
||||
@@ -96,7 +116,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`
|
||||
@@ -118,7 +138,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`
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
## Goal
|
||||
|
||||
Make the remaining high-value workflows clean from a Python user's perspective while keeping useful LLVM controls available. This plan covers four areas only:
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -234,6 +235,50 @@ jit.add_symbol("py_func", callback)
|
||||
- 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.
|
||||
- Removed raw APIs stay absent from the public surface.
|
||||
|
||||
## API audit process
|
||||
|
||||
After each phase:
|
||||
@@ -251,6 +296,7 @@ After each phase:
|
||||
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
|
||||
|
||||
@@ -258,6 +304,7 @@ After each phase:
|
||||
- Optimization examples use `mod.optimize(<pass pipeline>)`.
|
||||
- Object/assembly examples use `mod.emit_object()` / `mod.emit_assembly()`.
|
||||
- JIT design and implementation use LLVM-C only.
|
||||
- Metadata examples avoid raw metadata kind IDs.
|
||||
- Low-level APIs remain available when they are useful advanced controls.
|
||||
- Tests pass:
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ 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.
|
||||
4. JIT through LLVM-C ORC LLJIT,
|
||||
5. metadata/debug-info cleanup.
|
||||
|
||||
## Implemented APIs
|
||||
|
||||
@@ -55,9 +56,27 @@ Implemented and tested the active UX work:
|
||||
- [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:
|
||||
|
||||
@@ -92,6 +111,8 @@ Coverage:
|
||||
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
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Attach custom metadata to an instruction and print LLVM IR.
|
||||
|
||||
Run from the repository root with:
|
||||
|
||||
uv run python examples/instruction_metadata.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import llvm
|
||||
|
||||
|
||||
def build_module() -> str:
|
||||
with llvm.create_context() as ctx:
|
||||
i32 = ctx.types.i32
|
||||
fn_type = ctx.types.function(i32, [i32])
|
||||
|
||||
with ctx.create_module("instruction_metadata_example") as mod:
|
||||
fn = mod.add_function("bump", fn_type)
|
||||
entry = fn.append_basic_block("entry")
|
||||
|
||||
with entry.create_builder() as builder:
|
||||
result = builder.add(fn.get_param(0), i32.constant(1), "result")
|
||||
|
||||
# Metadata is addressed by its kind name. No metadata kind ID is
|
||||
# exposed in the Python API.
|
||||
note_text = ctx.md_string("created by instruction_metadata.py")
|
||||
note = ctx.md_node([note_text])
|
||||
result.metadata["example.note"] = note
|
||||
|
||||
# Inspect the attached metadata through the same mapping view.
|
||||
attached_note = result.metadata["example.note"]
|
||||
has_note = "example.note" in result.metadata
|
||||
round_trip_matches = result.metadata.get("example.note") == note
|
||||
note_operand_count = len(attached_note.operands)
|
||||
note_text_value = attached_note.operands[0].string
|
||||
|
||||
builder.ret(result)
|
||||
|
||||
assert mod.verify(), mod.verification_error
|
||||
return (
|
||||
"instruction metadata:\n"
|
||||
f" has example.note: {has_note}\n"
|
||||
f" round trip matches: {round_trip_matches}\n"
|
||||
f" note operands: {note_operand_count}\n"
|
||||
f" note text: {note_text_value}\n"
|
||||
"\nIR:\n"
|
||||
f"{mod}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(build_module(), end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Use Pythonic metadata and debug-info helpers.
|
||||
|
||||
This example shows:
|
||||
|
||||
- instruction metadata by kind name,
|
||||
- named metadata as a module mapping,
|
||||
- module flags as a keyed view,
|
||||
- DIBuilder convenience methods,
|
||||
- builder debug-location scopes.
|
||||
|
||||
Run from the repository root with:
|
||||
|
||||
uv run python examples/metadata_debug_info.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import llvm
|
||||
|
||||
|
||||
def build_module() -> str:
|
||||
with llvm.create_context() as ctx:
|
||||
with ctx.create_module("metadata_debug_info_example") as mod:
|
||||
mod.is_new_dbg_info_format = True
|
||||
i32 = ctx.types.i32
|
||||
fn = mod.add_function("add", ctx.types.function(i32, [i32, i32]))
|
||||
|
||||
with mod.create_dibuilder() as dib:
|
||||
file = dib.file("main.c", ".")
|
||||
compile_unit = dib.compile_unit(
|
||||
language=llvm.DwarfLanguage.C,
|
||||
file=file,
|
||||
producer="llvm-nanobind example",
|
||||
)
|
||||
subprogram = dib.function(
|
||||
fn,
|
||||
name="add",
|
||||
file=file,
|
||||
line=1,
|
||||
return_type=i32,
|
||||
param_types=[i32, i32],
|
||||
)
|
||||
_tmp_var = dib.local_variable(subprogram, "tmp", file, 2, i32)
|
||||
|
||||
# Named metadata is a mapping from name to appendable list.
|
||||
mod.named_metadata["example.compile_units"].append(compile_unit)
|
||||
|
||||
# Module flags are keyed by their string name.
|
||||
mod.module_flags.add(
|
||||
"Example Metadata Version",
|
||||
llvm.ModuleFlagBehavior.Warning,
|
||||
i32.constant(1).as_metadata(),
|
||||
)
|
||||
|
||||
entry = fn.append_basic_block("entry")
|
||||
with entry.create_builder() as builder:
|
||||
with builder.debug_location(line=2, column=5, scope=subprogram):
|
||||
result = builder.add(fn.get_param(0), fn.get_param(1), "result")
|
||||
|
||||
# Metadata kind lookup is internal to the mapping view.
|
||||
result.metadata["example.note"] = ctx.md_node(
|
||||
[ctx.md_string("created by metadata_debug_info.py")]
|
||||
)
|
||||
builder.ret(result)
|
||||
|
||||
dib.finalize()
|
||||
|
||||
assert mod.verify(), mod.verification_error
|
||||
return str(mod)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(build_module(), end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Create module named metadata and print LLVM IR.
|
||||
|
||||
Run from the repository root with:
|
||||
|
||||
uv run python examples/named_metadata.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import llvm
|
||||
|
||||
|
||||
def build_module() -> str:
|
||||
with llvm.create_context() as ctx:
|
||||
with ctx.create_module("named_metadata_example") as mod:
|
||||
tags = mod.named_metadata["example.tags"]
|
||||
tags.append(ctx.md_node([ctx.md_string("frontend"), ctx.md_string("demo")]))
|
||||
tags.append(ctx.md_node([ctx.md_string("purpose"), ctx.md_string("docs")]))
|
||||
|
||||
# Inspect named metadata through the module mapping view.
|
||||
keys = list(mod.named_metadata)
|
||||
operand_count = len(mod.named_metadata["example.tags"])
|
||||
iterated_count = sum(1 for _ in mod.named_metadata["example.tags"])
|
||||
|
||||
first_tag = mod.named_metadata["example.tags"][0]
|
||||
first_tag_strings = [operand.string for operand in first_tag.operands]
|
||||
|
||||
assert keys == ["example.tags"]
|
||||
assert operand_count == 2
|
||||
assert iterated_count == 2
|
||||
assert first_tag_strings == ["frontend", "demo"]
|
||||
assert mod.verify(), mod.verification_error
|
||||
return (
|
||||
"named metadata:\n"
|
||||
f" keys: {', '.join(keys)}\n"
|
||||
f" example.tags operands: {operand_count}\n"
|
||||
f" iterated operands: {iterated_count}\n"
|
||||
f" first tag strings: {', '.join(first_tag_strings)}\n"
|
||||
"\nIR:\n"
|
||||
f"{mod}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(build_module(), end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+27
-27
@@ -28,7 +28,7 @@ def get_di_tag():
|
||||
# Create DIBuilder
|
||||
with mod.create_dibuilder() as dib:
|
||||
# Create file
|
||||
file_md = dib.create_file("metadata.c", ".")
|
||||
file_md = dib.file("metadata.c", ".")
|
||||
|
||||
# Create struct type (DW_TAG_structure_type = 0x13)
|
||||
struct_md = dib.create_struct_type(
|
||||
@@ -60,7 +60,7 @@ def di_type_get_name():
|
||||
# Create DIBuilder
|
||||
with mod.create_dibuilder() as dib:
|
||||
# Create file
|
||||
file_md = dib.create_file("metadata.c", ".")
|
||||
file_md = dib.file("metadata.c", ".")
|
||||
|
||||
# Create struct type with name
|
||||
name = "TestClass"
|
||||
@@ -157,23 +157,23 @@ def test_dibuilder():
|
||||
# Create DIBuilder
|
||||
with mod.create_dibuilder() as dib:
|
||||
# Create file
|
||||
file_md = dib.create_file(filename, ".")
|
||||
file_md = dib.file(filename, ".")
|
||||
|
||||
# Create compile unit
|
||||
compile_unit = dib.create_compile_unit(
|
||||
llvm.DWARFSourceLanguageC,
|
||||
file_md,
|
||||
"llvm-c-test",
|
||||
False,
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
llvm.DWARFEmissionFull,
|
||||
0,
|
||||
False,
|
||||
False,
|
||||
"/",
|
||||
"",
|
||||
compile_unit = dib.compile_unit(
|
||||
language=llvm.DwarfLanguage.C,
|
||||
file=file_md,
|
||||
producer="llvm-c-test",
|
||||
is_optimized=False,
|
||||
flags="",
|
||||
runtime_ver=0,
|
||||
split_name="",
|
||||
kind=llvm.DwarfEmissionKind.Full,
|
||||
dwo_id=0,
|
||||
split_debug_inlining=False,
|
||||
debug_info_for_profiling=False,
|
||||
sys_root="/",
|
||||
sdk="",
|
||||
)
|
||||
|
||||
# Create module
|
||||
@@ -265,7 +265,7 @@ def test_dibuilder():
|
||||
struct_dbg_ptr_ty = dib.create_pointer_type(struct_dbg_ty, 192, 0, 0, "")
|
||||
|
||||
# Add to named metadata
|
||||
mod.add_named_metadata_operand("FooType", struct_dbg_ptr_ty)
|
||||
mod.named_metadata["FooType"].append(struct_dbg_ptr_ty)
|
||||
|
||||
# Create function
|
||||
i64_type = ctx.types.i64
|
||||
@@ -289,7 +289,7 @@ def test_dibuilder():
|
||||
)
|
||||
|
||||
# Create debug location for parameters
|
||||
foo_param_location = ctx.create_debug_location(
|
||||
foo_param_location = ctx.debug_location(
|
||||
42, 0, replaceable_function_md, None
|
||||
)
|
||||
|
||||
@@ -354,7 +354,7 @@ def test_dibuilder():
|
||||
|
||||
# Create another basic block for variables
|
||||
foo_var_block = foo_function.append_basic_block("vars")
|
||||
foo_vars_location = ctx.create_debug_location(
|
||||
foo_vars_location = ctx.debug_location(
|
||||
43, 0, function_md, None
|
||||
)
|
||||
|
||||
@@ -404,7 +404,7 @@ def test_dibuilder():
|
||||
enum_test = dib.create_enumeration_type(
|
||||
namespace, "EnumTest", file_md, 0, 64, 0, enumerators_test, int64_ty
|
||||
)
|
||||
mod.add_named_metadata_operand("EnumTest", enum_test)
|
||||
mod.named_metadata["EnumTest"].append(enum_test)
|
||||
|
||||
# Create UInt128 type and large enumerators
|
||||
uint128_ty = dib.create_basic_type("UInt128", 128, 0, llvm.DIFlagZero)
|
||||
@@ -429,7 +429,7 @@ def test_dibuilder():
|
||||
large_enumerators,
|
||||
uint128_ty,
|
||||
)
|
||||
mod.add_named_metadata_operand("LargeEnumTest", large_enum_test)
|
||||
mod.named_metadata["LargeEnumTest"].append(large_enum_test)
|
||||
|
||||
# Create subrange type with metadata bounds
|
||||
foo_val3 = i64_type.constant(8)
|
||||
@@ -442,7 +442,7 @@ def test_dibuilder():
|
||||
subrange_md_ty = dib.create_subrange_type(
|
||||
file_md, "foo", 42, file_md, 64, 0, 0, int64_ty, lo, hi, strd, bias
|
||||
)
|
||||
mod.add_named_metadata_operand("SubrangeType", subrange_md_ty)
|
||||
mod.named_metadata["SubrangeType"].append(subrange_md_ty)
|
||||
|
||||
# Create set types
|
||||
set_md_ty1 = dib.create_set_type(
|
||||
@@ -451,8 +451,8 @@ def test_dibuilder():
|
||||
set_md_ty2 = dib.create_set_type(
|
||||
file_md, "subrangeset", file_md, 42, 64, 0, subrange_md_ty
|
||||
)
|
||||
mod.add_named_metadata_operand("SetType1", set_md_ty1)
|
||||
mod.add_named_metadata_operand("SetType2", set_md_ty2)
|
||||
mod.named_metadata["SetType1"].append(set_md_ty1)
|
||||
mod.named_metadata["SetType2"].append(set_md_ty2)
|
||||
|
||||
# Create dynamic array type
|
||||
dyn_subscripts = [dib.get_or_create_subrange(0, 10)]
|
||||
@@ -473,7 +473,7 @@ def test_dibuilder():
|
||||
rank_expr,
|
||||
None,
|
||||
)
|
||||
mod.add_named_metadata_operand("DynType", dynamic_array_md_ty)
|
||||
mod.named_metadata["DynType"].append(dynamic_array_md_ty)
|
||||
|
||||
# Create forward declaration
|
||||
struct_p_ty = dib.create_forward_decl(
|
||||
@@ -485,7 +485,7 @@ def test_dibuilder():
|
||||
struct_elts_array = [int64_ty, int64_ty, int32_ty]
|
||||
class_arr = dib.get_or_create_array(struct_elts_array)
|
||||
dib.replace_arrays([struct_p_ty], [class_arr])
|
||||
mod.add_named_metadata_operand("ClassType", struct_p_ty)
|
||||
mod.named_metadata["ClassType"].append(struct_p_ty)
|
||||
|
||||
# Build IR instructions
|
||||
with foo_entry_block.create_builder() as builder:
|
||||
|
||||
+18
-76
@@ -1029,13 +1029,8 @@ class FunCloner:
|
||||
if src.can_use_fast_math_flags:
|
||||
dst.fast_math_flags = src.fast_math_flags
|
||||
|
||||
# Copy instruction metadata
|
||||
ctx = self.module.context
|
||||
all_metadata = src.instruction_get_all_metadata_other_than_debug_loc()
|
||||
for i in range(len(all_metadata)):
|
||||
kind = all_metadata.get_kind(i)
|
||||
md = all_metadata.get_metadata(i)
|
||||
dst.set_metadata(kind, md, ctx)
|
||||
# Copy instruction metadata.
|
||||
src.metadata.copy_to(dst)
|
||||
|
||||
check_value_kind(dst, llvm.ValueKind.Instruction)
|
||||
self.vmap[src] = dst
|
||||
@@ -1239,30 +1234,11 @@ def declare_symbols(src: llvm.Module, m: llvm.Module) -> None:
|
||||
|
||||
cur = next_i
|
||||
|
||||
# Declare named metadata
|
||||
cur_md = src.first_named_metadata
|
||||
end_md = src.last_named_metadata
|
||||
if cur_md is None:
|
||||
if end_md is not None:
|
||||
raise RuntimeError("Range has an end but no beginning")
|
||||
else:
|
||||
while True:
|
||||
name = cur_md.name
|
||||
if m.get_named_metadata(name):
|
||||
raise RuntimeError("Named Metadata Node already cloned")
|
||||
m.add_named_metadata(name)
|
||||
|
||||
next_md = cur_md.next
|
||||
if next_md is None:
|
||||
if cur_md != end_md:
|
||||
raise RuntimeError("")
|
||||
break
|
||||
|
||||
prev_md = next_md.prev
|
||||
if prev_md != cur_md:
|
||||
raise RuntimeError("Next.Previous global is not Current")
|
||||
|
||||
cur_md = next_md
|
||||
# Declare named metadata.
|
||||
for name in src.named_metadata:
|
||||
if m.named_metadata.get(name) is not None:
|
||||
raise RuntimeError("Named Metadata Node already cloned")
|
||||
_ = m.named_metadata[name]
|
||||
|
||||
|
||||
def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
|
||||
@@ -1284,13 +1260,8 @@ def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
|
||||
if init:
|
||||
g.initializer = clone_constant(init, m)
|
||||
|
||||
# Copy global metadata
|
||||
ctx = m.context
|
||||
all_metadata = cur.global_copy_all_metadata()
|
||||
for i in range(len(all_metadata)):
|
||||
kind = all_metadata.get_kind(i)
|
||||
md = all_metadata.get_metadata(i)
|
||||
g.set_metadata(kind, md, ctx)
|
||||
# Copy global metadata.
|
||||
cur.metadata.copy_to(g)
|
||||
|
||||
g.is_global_constant = cur.is_global_constant
|
||||
g.is_thread_local = cur.is_thread_local
|
||||
@@ -1335,13 +1306,8 @@ def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
|
||||
raise RuntimeError("Could not find personality function")
|
||||
fun.personality_fn = p
|
||||
|
||||
# Copy function metadata
|
||||
ctx = m.context
|
||||
all_metadata = cur.global_copy_all_metadata()
|
||||
for i in range(len(all_metadata)):
|
||||
kind = all_metadata.get_kind(i)
|
||||
md = all_metadata.get_metadata(i)
|
||||
fun.set_metadata(kind, md, ctx)
|
||||
# Copy function metadata.
|
||||
cur.metadata.copy_to(fun)
|
||||
|
||||
# Copy prefix and prologue data
|
||||
if cur.has_prefix_data:
|
||||
@@ -1433,37 +1399,13 @@ def clone_symbols(src: llvm.Module, m: llvm.Module) -> None:
|
||||
|
||||
cur = next_i
|
||||
|
||||
# Clone named metadata contents
|
||||
cur_md = src.first_named_metadata
|
||||
end_md = src.last_named_metadata
|
||||
if cur_md is None:
|
||||
if end_md is not None:
|
||||
raise RuntimeError("Range has an end but no beginning")
|
||||
else:
|
||||
while True:
|
||||
name = cur_md.name
|
||||
named_md = m.get_named_metadata(name)
|
||||
if not named_md:
|
||||
raise RuntimeError("Named MD Node must have been declared already")
|
||||
|
||||
operand_count = src.get_named_metadata_num_operands(name)
|
||||
operands = src.get_named_metadata_operands(name)
|
||||
for op in operands:
|
||||
# Convert value to metadata before adding
|
||||
md = op.as_metadata()
|
||||
m.add_named_metadata_operand(name, md)
|
||||
|
||||
next_md = cur_md.next
|
||||
if next_md is None:
|
||||
if cur_md != end_md:
|
||||
raise RuntimeError("Last Named MD Node does not match End")
|
||||
break
|
||||
|
||||
prev_md = next_md.prev
|
||||
if prev_md != cur_md:
|
||||
raise RuntimeError("Next.Previous Named MD Node is not Current")
|
||||
|
||||
cur_md = next_md
|
||||
# Clone named metadata contents.
|
||||
for name in src.named_metadata:
|
||||
named_md = m.named_metadata.get(name)
|
||||
if named_md is None:
|
||||
raise RuntimeError("Named MD Node must have been declared already")
|
||||
for md in src.named_metadata[name]:
|
||||
named_md.append(md)
|
||||
|
||||
|
||||
def echo() -> int:
|
||||
|
||||
+11
-18
@@ -26,7 +26,7 @@ def add_named_metadata_operand():
|
||||
# First convert value to metadata, then create node
|
||||
md = val.as_metadata()
|
||||
md_node = ctx.md_node([md])
|
||||
mod.add_named_metadata_operand("name", md_node)
|
||||
mod.named_metadata["name"].append(md_node)
|
||||
|
||||
return 0
|
||||
except Exception as e:
|
||||
@@ -62,8 +62,7 @@ def set_metadata():
|
||||
md = val.as_metadata()
|
||||
md_node = ctx.md_node([md])
|
||||
|
||||
kind_id = llvm.get_md_kind_id("kind")
|
||||
ret_inst.set_metadata(kind_id, md_node, ctx)
|
||||
ret_inst.metadata["kind"] = md_node
|
||||
|
||||
# Delete the instruction
|
||||
ret_inst.delete_instruction()
|
||||
@@ -79,18 +78,14 @@ def replace_md_operand():
|
||||
try:
|
||||
with llvm.create_context() as ctx:
|
||||
with ctx.create_module("Mod"):
|
||||
# Build MDNode("foo"), then replace operand 0 with MDString("bar").
|
||||
string1_md = ctx.md_string("foo")
|
||||
node_md = ctx.md_node([string1_md])
|
||||
value = node_md.as_value(ctx)
|
||||
# Build MDNode("foo") and inspect operand 0.
|
||||
string_md = ctx.md_string("foo")
|
||||
node_md = ctx.md_node([string_md])
|
||||
|
||||
string2_md = ctx.md_string("bar")
|
||||
value.replace_md_node_operand_with(0, string2_md)
|
||||
|
||||
# Verify replacement took effect.
|
||||
operand = value.get_operand(0)
|
||||
if operand.as_metadata() != string2_md:
|
||||
raise AssertionError("Metadata operand replacement did not apply")
|
||||
# The public Python API keeps Metadata as Metadata; MDNode
|
||||
# children are inspected through Metadata.operands.
|
||||
if node_md.operands[0] != string_md:
|
||||
raise AssertionError("Metadata operand access did not apply")
|
||||
|
||||
return 0
|
||||
except Exception as e:
|
||||
@@ -107,17 +102,15 @@ def is_a_value_as_metadata():
|
||||
i32 = ctx.types.i32
|
||||
val = i32.constant(0, False).as_metadata()
|
||||
md_node = ctx.md_node([val])
|
||||
md_val = md_node.as_value(ctx)
|
||||
|
||||
if not md_val.is_value_as_metadata:
|
||||
if not md_node.operands[0].is_value:
|
||||
raise AssertionError("Expected ValueAsMetadata for value-backed MD")
|
||||
|
||||
# MDNode built from MDString should NOT be ValueAsMetadata.
|
||||
string_md = ctx.md_string("foo")
|
||||
string_node = ctx.md_node([string_md])
|
||||
string_val = string_node.as_value(ctx)
|
||||
|
||||
if string_val.is_value_as_metadata:
|
||||
if string_node.operands[0].is_value:
|
||||
raise AssertionError("Expected non-ValueAsMetadata for string-backed MD")
|
||||
|
||||
return 0
|
||||
|
||||
+1050
-242
File diff suppressed because it is too large
Load Diff
@@ -158,6 +158,7 @@ def test_member_factories_and_builder_navigation() -> None:
|
||||
"get_host_cpu_features",
|
||||
"get_host_cpu_name",
|
||||
"get_inline_asm",
|
||||
"get_md_kind_id",
|
||||
"get_di_node_tag",
|
||||
"get_last_dbg_record",
|
||||
"get_last_enum_attribute_kind",
|
||||
@@ -171,6 +172,8 @@ def test_member_factories_and_builder_navigation() -> None:
|
||||
"replace_md_node_operand_with",
|
||||
"run_passes",
|
||||
"strip_module_debug_info",
|
||||
"NamedMDNode",
|
||||
"ValueMetadataEntries",
|
||||
]
|
||||
for name in removed_globals:
|
||||
assert not hasattr(llvm, name), name
|
||||
@@ -178,11 +181,24 @@ def test_member_factories_and_builder_navigation() -> None:
|
||||
removed_class_methods = {
|
||||
llvm.Context: [
|
||||
"get_diagnostics",
|
||||
"get_md_kind_id",
|
||||
"create_debug_location",
|
||||
"create_enum_attribute",
|
||||
"create_string_attribute",
|
||||
"create_type_attribute",
|
||||
],
|
||||
llvm.Module: ["get_verification_error"],
|
||||
llvm.Module: [
|
||||
"get_verification_error",
|
||||
"first_named_metadata",
|
||||
"last_named_metadata",
|
||||
"get_named_metadata",
|
||||
"add_named_metadata",
|
||||
"get_named_metadata_num_operands",
|
||||
"get_named_metadata_operands",
|
||||
"add_named_metadata_operand",
|
||||
"add_module_flag",
|
||||
"get_module_flag",
|
||||
],
|
||||
llvm.Function: [
|
||||
"get_personality_fn",
|
||||
"set_personality_fn",
|
||||
@@ -198,6 +214,8 @@ def test_member_factories_and_builder_navigation() -> None:
|
||||
],
|
||||
llvm.Attribute: ["kind"],
|
||||
llvm.AttributeAccessor: ["get_enum", "remove_enum"],
|
||||
llvm.DIBuilder: ["create_file", "create_compile_unit"],
|
||||
llvm.Metadata: ["as_value", "string_value", "__len__", "__getitem__", "__iter__"],
|
||||
llvm.Value: [
|
||||
"set_constant",
|
||||
"set_comdat",
|
||||
@@ -225,6 +243,9 @@ def test_member_factories_and_builder_navigation() -> None:
|
||||
"get_callsite_attribute_count",
|
||||
"get_callsite_enum_attribute",
|
||||
"add_callsite_attribute",
|
||||
"set_metadata",
|
||||
"global_copy_all_metadata",
|
||||
"instruction_get_all_metadata_other_than_debug_loc",
|
||||
],
|
||||
}
|
||||
for cls, names in removed_class_methods.items():
|
||||
@@ -265,8 +286,9 @@ def test_member_factories_and_builder_navigation() -> None:
|
||||
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
|
||||
named_md = mod.named_metadata["llvm.nanobind.surface"]
|
||||
assert mod.named_metadata["llvm.nanobind.surface"] is not None
|
||||
assert len(named_md) == 0
|
||||
|
||||
assert not hasattr(mod, "get_or_insert_comdat")
|
||||
comdat = mod.add_comdat("surface_comdat")
|
||||
|
||||
@@ -4,8 +4,8 @@ Full-tree regression matrix for remaining wrapper guard paths.
|
||||
This complements value/type/non-value matrices with:
|
||||
- helper API assertions (phi/switch/indirectbr, call builders, vector_const)
|
||||
- manager state-machine guards (Context/Module/Builder/DIBuilder/Binary)
|
||||
- wrapper lifetime guards (TypeFactory/Attribute/Comdat/NamedMDNode/Use/
|
||||
ValueMetadataEntries)
|
||||
- wrapper lifetime guards (TypeFactory/Attribute/Comdat/NamedMetadataList/Use/
|
||||
MetadataMap)
|
||||
- attribute/operand-bundle/indexed metadata guards
|
||||
- disassembler invalid-context guards
|
||||
"""
|
||||
@@ -166,15 +166,16 @@ def test_attribute_operand_bundle_and_entries_guards():
|
||||
add_inst = b.add(fn.get_param(0), i32.constant(1, False), "sum")
|
||||
b.ret(add_inst)
|
||||
|
||||
md = ctx.md_string("meta")
|
||||
kind = ctx.get_md_kind_id("llvm_nanobind.meta")
|
||||
add_inst.set_metadata(kind, md, ctx)
|
||||
entries = add_inst.instruction_get_all_metadata_other_than_debug_loc()
|
||||
assert len(entries) >= 1
|
||||
_ = entries.get_kind(0)
|
||||
_ = entries.get_metadata(0)
|
||||
assert_out_of_range(lambda: entries.get_kind(len(entries)))
|
||||
assert_out_of_range(lambda: entries.get_metadata(len(entries)))
|
||||
md = ctx.md_node([ctx.md_string("meta")])
|
||||
add_inst.metadata["llvm_nanobind.meta"] = md
|
||||
assert "llvm_nanobind.meta" in add_inst.metadata
|
||||
assert add_inst.metadata["llvm_nanobind.meta"] == md
|
||||
assert add_inst.metadata.get("missing.kind") is None
|
||||
try:
|
||||
_ = add_inst.metadata["missing.kind"]
|
||||
assert False, "Expected KeyError for missing metadata"
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def test_manager_state_machine_guards():
|
||||
@@ -223,7 +224,7 @@ def test_manager_state_machine_guards():
|
||||
|
||||
dm2 = mod.create_dibuilder()
|
||||
dib = dm2.__enter__()
|
||||
_ = dib.create_file("x.c", ".")
|
||||
_ = dib.file("x.c", ".")
|
||||
assert_memory_error(
|
||||
lambda: dm2.dispose(),
|
||||
"cannot call dispose() after __enter__",
|
||||
@@ -283,7 +284,7 @@ def test_lifetime_guards_for_remaining_wrappers():
|
||||
escaped["types"] = ctx.types
|
||||
escaped["attr"] = llvm.Attribute.string(ctx, "k", "v")
|
||||
escaped["comdat"] = mod.add_comdat("C")
|
||||
escaped["named_md"] = mod.add_named_metadata("llvm.nanobind.named")
|
||||
escaped["named_md"] = mod.named_metadata["llvm.nanobind.named"]
|
||||
|
||||
fn_ty = ctx.types.function(i32, [i32], False)
|
||||
fn = mod.add_function("f", fn_ty)
|
||||
@@ -296,20 +297,19 @@ def test_lifetime_guards_for_remaining_wrappers():
|
||||
assert len(uses) >= 1
|
||||
escaped["use"] = uses[0]
|
||||
|
||||
md = ctx.md_string("meta")
|
||||
kind = ctx.get_md_kind_id("llvm_nanobind.lifetime")
|
||||
add_inst.set_metadata(kind, md, ctx)
|
||||
escaped["entries"] = add_inst.instruction_get_all_metadata_other_than_debug_loc()
|
||||
assert len(escaped["entries"]) >= 1
|
||||
md = ctx.md_node([ctx.md_string("meta")])
|
||||
add_inst.metadata["llvm_nanobind.lifetime"] = md
|
||||
escaped["metadata"] = add_inst.metadata
|
||||
assert escaped["metadata"].get("llvm_nanobind.lifetime") == md
|
||||
|
||||
assert_memory_error(lambda: escaped["types"].i32, "typefactory used after context")
|
||||
assert_memory_error(lambda: escaped["attr"].string_kind, "attribute used after context")
|
||||
assert_memory_error(lambda: escaped["comdat"].selection_kind, "comdat used after module")
|
||||
assert_memory_error(lambda: escaped["named_md"].name, "namedmdnode used after context")
|
||||
assert_memory_error(lambda: len(escaped["named_md"]), "named metadata view used after context")
|
||||
assert_memory_error(lambda: escaped["use"].user, "use used after context")
|
||||
assert_memory_error(
|
||||
lambda: escaped["entries"].get_kind(0),
|
||||
"valuemetadataentries used after context",
|
||||
lambda: escaped["metadata"].get("llvm_nanobind.lifetime"),
|
||||
"metadata view used after context",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
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_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_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")
|
||||
@@ -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
|
||||
|
||||
@@ -121,3 +121,37 @@ def test_jit_add_example() -> None:
|
||||
return
|
||||
assert "add_i32(40, 2) = 42" in output
|
||||
assert "call_python(5) = 15" in output
|
||||
|
||||
|
||||
def test_instruction_metadata_example() -> None:
|
||||
output = run_example("examples/instruction_metadata.py")
|
||||
assert "instruction metadata:" in output
|
||||
assert "has example.note: True" in output
|
||||
assert "round trip matches: True" in output
|
||||
assert "note operands: 1" in output
|
||||
assert "note text: created by instruction_metadata.py" in output
|
||||
assert "define i32 @bump" in output
|
||||
assert "%result = add i32" in output
|
||||
assert "!example.note" in output
|
||||
assert "created by instruction_metadata.py" in output
|
||||
|
||||
|
||||
def test_named_metadata_example() -> None:
|
||||
output = run_example("examples/named_metadata.py")
|
||||
assert "named metadata:" in output
|
||||
assert "keys: example.tags" in output
|
||||
assert "example.tags operands: 2" in output
|
||||
assert "iterated operands: 2" in output
|
||||
assert "first tag strings: frontend, demo" in output
|
||||
assert "!example.tags = !{" in output
|
||||
assert '"frontend"' in output
|
||||
assert '"purpose"' in output
|
||||
|
||||
|
||||
def test_metadata_debug_info_example() -> None:
|
||||
output = run_example("examples/metadata_debug_info.py")
|
||||
assert "define i32 @add" in output
|
||||
assert "!example.note" in output
|
||||
assert "!example.compile_units" in output
|
||||
assert "!llvm.module.flags" in output
|
||||
assert "!dbg" in output
|
||||
|
||||
@@ -66,22 +66,19 @@ def test_core_intrinsic_get_type():
|
||||
print("[PASS] intrinsic_get_type works correctly")
|
||||
|
||||
|
||||
def test_core_replace_md_node_operand():
|
||||
"""Test LLVMReplaceMDNodeOperandWith → Value.replace_md_node_operand_with()"""
|
||||
def test_core_metadata_node_operands():
|
||||
"""Test MDNode operand inspection through Metadata.operands."""
|
||||
with llvm.create_context() as ctx:
|
||||
# Create metadata nodes
|
||||
md1 = ctx.md_string("original")
|
||||
md2 = ctx.md_string("replacement")
|
||||
md_node = ctx.md_node([md1])
|
||||
md_node = ctx.md_node([md1, md2])
|
||||
|
||||
# Convert to value to use with replace function
|
||||
md_val = md_node.as_value(ctx)
|
||||
replacement_val = md2.as_value(ctx)
|
||||
assert [operand.string for operand in md_node.operands] == [
|
||||
"original",
|
||||
"replacement",
|
||||
]
|
||||
|
||||
# Replace operand
|
||||
md_val.replace_md_node_operand_with(0, replacement_val.as_metadata())
|
||||
|
||||
print("[PASS] replace_md_node_operand_with works correctly")
|
||||
print("[PASS] metadata node operands work correctly")
|
||||
|
||||
|
||||
def test_debuginfo_global_variable_expression():
|
||||
@@ -90,16 +87,16 @@ def test_debuginfo_global_variable_expression():
|
||||
with ctx.create_module("test") as mod:
|
||||
with mod.create_dibuilder() as dib:
|
||||
# Create necessary debug info
|
||||
file = dib.create_file("test.c", "/path")
|
||||
cu = dib.create_compile_unit(
|
||||
lang=DWARF_LANG_C,
|
||||
file = dib.file("test.c", "/path")
|
||||
cu = dib.compile_unit(
|
||||
language=llvm.DwarfLanguage.C,
|
||||
file=file,
|
||||
producer="test",
|
||||
is_optimized=False,
|
||||
flags="",
|
||||
runtime_ver=0,
|
||||
split_name="",
|
||||
kind=llvm.DWARFEmissionFull,
|
||||
kind=llvm.DwarfEmissionKind.Full,
|
||||
dwo_id=0,
|
||||
split_debug_inlining=True,
|
||||
debug_info_for_profiling=False,
|
||||
@@ -110,6 +107,12 @@ def test_debuginfo_global_variable_expression():
|
||||
i32_ty = dib.create_basic_type(
|
||||
"int", 32, DWARF_ATE_SIGNED, DI_FLAGS_ZERO
|
||||
)
|
||||
assert i32_ty.di_type_name == "int"
|
||||
assert i32_ty.di_type_size_in_bits == 32
|
||||
assert i32_ty.di_type_align_in_bits == 0
|
||||
assert i32_ty.di_type_offset_in_bits == 0
|
||||
assert i32_ty.di_type_line == 0
|
||||
assert i32_ty.di_type_flags == DI_FLAGS_ZERO
|
||||
|
||||
# Create a global variable expression
|
||||
gve = dib.create_global_variable_expression(
|
||||
@@ -144,16 +147,16 @@ def test_debuginfo_class_type():
|
||||
with llvm.create_context() as ctx:
|
||||
with ctx.create_module("test") as mod:
|
||||
with mod.create_dibuilder() as dib:
|
||||
file = dib.create_file("test.cpp", "/path")
|
||||
cu = dib.create_compile_unit(
|
||||
lang=DWARF_LANG_C_PLUS_PLUS,
|
||||
file = dib.file("test.cpp", "/path")
|
||||
cu = dib.compile_unit(
|
||||
language=llvm.DwarfLanguage.CPlusPlus,
|
||||
file=file,
|
||||
producer="test",
|
||||
is_optimized=False,
|
||||
flags="",
|
||||
runtime_ver=0,
|
||||
split_name="",
|
||||
kind=llvm.DWARFEmissionFull,
|
||||
kind=llvm.DwarfEmissionKind.Full,
|
||||
dwo_id=0,
|
||||
split_debug_inlining=True,
|
||||
debug_info_for_profiling=False,
|
||||
@@ -189,16 +192,16 @@ def test_debuginfo_static_member_type():
|
||||
with llvm.create_context() as ctx:
|
||||
with ctx.create_module("test") as mod:
|
||||
with mod.create_dibuilder() as dib:
|
||||
file = dib.create_file("test.cpp", "/path")
|
||||
cu = dib.create_compile_unit(
|
||||
lang=DWARF_LANG_C_PLUS_PLUS,
|
||||
file = dib.file("test.cpp", "/path")
|
||||
cu = dib.compile_unit(
|
||||
language=llvm.DwarfLanguage.CPlusPlus,
|
||||
file=file,
|
||||
producer="test",
|
||||
is_optimized=False,
|
||||
flags="",
|
||||
runtime_ver=0,
|
||||
split_name="",
|
||||
kind=llvm.DWARFEmissionFull,
|
||||
kind=llvm.DwarfEmissionKind.Full,
|
||||
dwo_id=0,
|
||||
split_debug_inlining=True,
|
||||
debug_info_for_profiling=False,
|
||||
@@ -237,16 +240,16 @@ def test_debuginfo_member_pointer_type():
|
||||
with llvm.create_context() as ctx:
|
||||
with ctx.create_module("test") as mod:
|
||||
with mod.create_dibuilder() as dib:
|
||||
file = dib.create_file("test.cpp", "/path")
|
||||
cu = dib.create_compile_unit(
|
||||
lang=DWARF_LANG_C_PLUS_PLUS,
|
||||
file = dib.file("test.cpp", "/path")
|
||||
cu = dib.compile_unit(
|
||||
language=llvm.DwarfLanguage.CPlusPlus,
|
||||
file=file,
|
||||
producer="test",
|
||||
is_optimized=False,
|
||||
flags="",
|
||||
runtime_ver=0,
|
||||
split_name="",
|
||||
kind=llvm.DWARFEmissionFull,
|
||||
kind=llvm.DwarfEmissionKind.Full,
|
||||
dwo_id=0,
|
||||
split_debug_inlining=True,
|
||||
debug_info_for_profiling=False,
|
||||
@@ -418,7 +421,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()
|
||||
|
||||
Reference in New Issue
Block a user