diff --git a/README.md b/README.md index 28b15f5..ac51873 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/devdocs/api-design-philosophy.md b/devdocs/api-design-philosophy.md index cb26da0..b50bb02 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 ``` --- @@ -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 `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 2bdcd70..7418a4d 100644 --- a/devdocs/api-reference.md +++ b/devdocs/api-reference.md @@ -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` diff --git a/devdocs/api-ux-cleanup/plan.md b/devdocs/api-ux-cleanup/plan.md index 410941c..1e3e3fe 100644 --- a/devdocs/api-ux-cleanup/plan.md +++ b/devdocs/api-ux-cleanup/plan.md @@ -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()`. - 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: diff --git a/devdocs/api-ux-cleanup/progress.md b/devdocs/api-ux-cleanup/progress.md index d2c2ddc..b4e0e99 100644 --- a/devdocs/api-ux-cleanup/progress.md +++ b/devdocs/api-ux-cleanup/progress.md @@ -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 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/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/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/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/src/llvm-nanobind.cpp b/src/llvm-nanobind.cpp index 1b25f13..0ae44c6 100644 --- a/src/llvm-nanobind.cpp +++ b/src/llvm-nanobind.cpp @@ -255,6 +255,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; @@ -3262,6 +3267,8 @@ struct LLVMValueWrapper { // LLVMMetadataWrapper LLVMMetadataWrapper as_metadata() const; + LLVMMetadataMapWrapper metadata() const; + // 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) @@ -4791,6 +4798,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(); @@ -6696,6 +6709,9 @@ 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; + // ========================================================================= // Module API Refactor: Methods moved from global functions // ========================================================================= @@ -7153,6 +7169,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); @@ -9660,11 +9679,13 @@ struct LLVMMetadataWrapper; struct LLVMDIBuilderWrapper : NoMoveCopy { LLVMDIBuilderRef m_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; std::shared_ptr m_module_token; LLVMDIBuilderWrapper(LLVMModuleRef mod, std::shared_ptr module_token) - : m_module_token(std::move(module_token)) { + : m_ctx_ref(mod ? LLVMGetModuleContext(mod) : nullptr), + m_module_token(std::move(module_token)) { m_ref = LLVMCreateDIBuilder(mod); } @@ -9694,6 +9715,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, @@ -9701,6 +9725,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, @@ -9732,6 +9766,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 // ========================================================================= @@ -9829,6 +9873,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, @@ -10055,11 +10108,13 @@ struct LLVMDIBuilderWrapper : NoMoveCopy { struct LLVMMetadataWrapper { LLVMMetadataRef m_ref = nullptr; + LLVMContextRef m_ctx_ref = nullptr; std::shared_ptr m_context_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 token, + LLVMContextRef ctx = nullptr) + : m_ref(ref), m_ctx_ref(ctx), m_context_token(std::move(token)) {} void check_valid() const { if (!m_ref) @@ -10068,6 +10123,77 @@ struct LLVMMetadataWrapper { throw LLVMMemoryError("Metadata used after context was destroyed"); } + 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); + } + return result; + } + // Convert metadata to value (requires context) LLVMValueWrapper as_value(LLVMContextWrapper &ctx) const { check_valid(); @@ -10089,6 +10215,430 @@ struct LLVMMetadataWrapper { } }; +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 LLVMMetadataRef normalize_metadata_for_attachment( + LLVMContextRef ctx, const LLVMMetadataWrapper &md) { + md.check_valid(); + 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; + + LLVMMetadataMapWrapper() = default; + LLVMMetadataMapWrapper(LLVMValueRef value, LLVMContextRef ctx, + std::shared_ptr token) + : m_value_ref(value), m_ctx_ref(ctx), m_context_token(std::move(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 (!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); + } + + 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); + } + } + 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); + 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(); + 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); + } + 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 (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); + } + + 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) { @@ -10103,7 +10653,18 @@ 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); +} + +inline LLVMMetadataMapWrapper LLVMValueWrapper::metadata() const { + check_valid(); + LLVMContextRef ctx_ref = context_for_metadata_value(m_ref); + return LLVMMetadataMapWrapper(m_ref, ctx_ref, m_context_token); } // Implementation of replace_md_node_operand_with - needs LLVMMetadataWrapper @@ -10155,7 +10716,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 @@ -10169,7 +10730,7 @@ LLVMContextWrapper::md_node(const Iterable &mds) { 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( @@ -10185,7 +10746,37 @@ inline LLVMMetadataWrapper LLVMContextWrapper::create_debug_location( 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(); + 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(); + LLVMMetadataRef inlined = nullptr; + if (inlined_at) { + inlined_at->check_valid(); + 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 @@ -10231,7 +10822,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); +} + +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 @@ -10246,6 +10847,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, @@ -10254,7 +10880,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_module_token, m_ctx_ref); +} + +inline LLVMMetadataWrapper +LLVMDIBuilderWrapper::file(const std::string &filename, + const std::string &directory) { + return create_file(filename, directory); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_compile_unit( @@ -10273,7 +10905,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_module_token, m_ctx_ref); +} + +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( @@ -10287,7 +10933,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10299,7 +10945,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10311,7 +10957,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_module_token, m_ctx_ref); } // Function & Subroutine Creation @@ -10330,7 +10976,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_subroutine_type( @@ -10347,7 +10993,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_module_token, m_ctx_ref); +} + +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_module_token, m_ctx_ref); + fn.set_subprogram(wrapper); + return wrapper; } // Type Creation @@ -10359,7 +11042,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_pointer_type( @@ -10371,7 +11054,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_vector_type( @@ -10390,7 +11073,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_typedef( @@ -10405,7 +11088,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_struct_type( @@ -10433,7 +11116,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_enumeration_type( @@ -10457,7 +11140,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_forward_decl( @@ -10473,7 +11156,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10490,7 +11173,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_subrange_type( @@ -10514,7 +11197,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_set_type( @@ -10529,7 +11212,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_dynamic_array_type( @@ -10560,7 +11243,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_module_token, m_ctx_ref); } // Variable & Expression @@ -10576,7 +11259,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_auto_variable( @@ -10592,7 +11275,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_module_token, m_ctx_ref); +} + +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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10613,7 +11315,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10624,14 +11326,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_module_token, m_ctx_ref); } 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_module_token, m_ctx_ref); } // Label Methods @@ -10644,7 +11346,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_module_token, m_ctx_ref); } inline void @@ -10707,7 +11409,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_array( @@ -10721,7 +11423,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_array( } return LLVMMetadataWrapper( LLVMDIBuilderGetOrCreateArray(m_ref, elem_refs.data(), elem_refs.size()), - m_module_token); + m_module_token, m_ctx_ref); } // Enumerator Methods @@ -10732,7 +11434,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10743,7 +11445,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_module_token, m_ctx_ref); } // ObjC & Inheritance Methods @@ -10759,7 +11461,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_objc_ivar( @@ -10776,7 +11478,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_inheritance( @@ -10790,7 +11492,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_module_token, m_ctx_ref); } // Import & Macro Methods @@ -10813,7 +11515,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10835,7 +11537,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_temp_macro_file( @@ -10847,7 +11549,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_macro( @@ -10860,7 +11562,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_macro( (LLVMDWARFMacinfoRecordType)macro_type, name.c_str(), name.size(), value.c_str(), value.size()), - m_module_token); + m_module_token, m_ctx_ref); } // Missing DIBuilder Method Implementations @@ -10886,7 +11588,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_union_type( @@ -10910,7 +11612,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_array_type( @@ -10929,7 +11631,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10938,7 +11640,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10947,13 +11649,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_null_ptr_type() { check_valid(); return LLVMMetadataWrapper(LLVMDIBuilderCreateNullPtrType(m_ref), - m_module_token); + m_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_bit_field_member_type( @@ -10970,7 +11672,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -10978,7 +11680,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_type_array( @@ -10992,7 +11694,7 @@ inline LLVMMetadataWrapper LLVMDIBuilderWrapper::get_or_create_type_array( } return LLVMMetadataWrapper(LLVMDIBuilderGetOrCreateTypeArray( m_ref, type_refs.data(), type_refs.size()), - m_module_token); + m_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_lexical_block_file( @@ -11003,7 +11705,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper LLVMDIBuilderWrapper::create_imported_declaration( @@ -11024,7 +11726,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_module_token, m_ctx_ref); } inline LLVMMetadataWrapper @@ -11038,7 +11740,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_module_token, m_ctx_ref); } // Utility Methods @@ -11092,7 +11794,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_module_token, m_ctx_ref); } // Create static member type debug info @@ -11113,7 +11815,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_module_token, m_ctx_ref); } // Create member pointer type debug info (C++ pointer-to-member) @@ -11129,7 +11831,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_module_token, m_ctx_ref); } // Insert declare record before an instruction @@ -11579,6 +12281,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) @@ -12362,6 +13084,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, @@ -12969,7 +13760,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. @@ -13249,17 +14040,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. @@ -13279,6 +14059,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. @@ -13370,12 +14159,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, @@ -13708,7 +14491,7 @@ Valid when: 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, @@ -13720,7 +14503,7 @@ Valid when: // ===================================================================== .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)") @@ -13750,7 +14533,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)") @@ -13813,6 +14596,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. @@ -14494,42 +15301,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, @@ -14703,40 +15474,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, @@ -14751,7 +15488,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 @@ -14759,7 +15496,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. @@ -14767,9 +15504,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 @@ -14795,40 +15532,25 @@ 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, @@ -15176,10 +15898,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. @@ -15203,8 +15921,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)"); @@ -15411,25 +16130,25 @@ initialize_all_asm_printers(), and initialize_all_asm_parsers().)"); // 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)"); @@ -15495,7 +16214,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, @@ -15625,7 +16344,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 @@ -15636,11 +16355,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. @@ -15674,7 +16393,7 @@ This initializes the native target and native ASM printer internally. 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. @@ -15832,12 +16551,12 @@ objects are pinned by the JIT to keep callbacks alive. .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. @@ -15950,9 +16669,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. @@ -16250,17 +16969,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 // ========================================================================== @@ -16304,15 +17012,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, @@ -16345,6 +17058,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, @@ -16432,6 +17155,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, @@ -16625,7 +17355,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. @@ -16647,9 +17377,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. @@ -16665,9 +17395,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. @@ -16681,9 +17411,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. @@ -16696,9 +17426,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. @@ -16742,6 +17472,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; }) @@ -16751,10 +17499,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. @@ -16765,6 +17509,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(); @@ -16772,14 +17550,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); @@ -16795,7 +17603,7 @@ 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); }, R"(Get scope from this debug location. C API: LLVMDILocationGetScope)") @@ -16804,7 +17612,7 @@ 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); return std::nullopt; }, R"(Get inlined-at location from this debug location, or None. @@ -16814,7 +17622,7 @@ 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); return std::nullopt; }, R"(Get file from this debug scope, or None. @@ -16854,7 +17662,7 @@ 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); return std::nullopt; }, R"(Get file from this debug variable, or None. @@ -16864,7 +17672,7 @@ 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); return std::nullopt; }, R"(Get scope from this debug variable, or None. @@ -16879,7 +17687,7 @@ Valid when: self.check_valid(); return LLVMMetadataWrapper( LLVMDIGlobalVariableExpressionGetVariable(self.m_ref), - self.m_context_token); + self.m_context_token, self.m_ctx_ref); }, R"(Get the DIGlobalVariable from this DIGlobalVariableExpression. C API: LLVMDIGlobalVariableExpressionGetVariable)") @@ -16887,7 +17695,7 @@ Valid when: self.check_valid(); return LLVMMetadataWrapper( LLVMDIGlobalVariableExpressionGetExpression(self.m_ref), - self.m_context_token); + self.m_context_token, self.m_ctx_ref); }, R"(Get the DIExpression from this DIGlobalVariableExpression. C API: LLVMDIGlobalVariableExpressionGetExpression)"); @@ -16906,10 +17714,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). @@ -16919,7 +17727,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)"); @@ -16938,7 +17746,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 index c580de0..8ef1fc2 100644 --- a/tests/regressions/test_api_surface_cleanup.py +++ b/tests/regressions/test_api_surface_cleanup.py @@ -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") 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_metadata_ux_cleanup.py b/tests/regressions/test_metadata_ux_cleanup.py new file mode 100644 index 0000000..2611f71 --- /dev/null +++ b/tests/regressions/test_metadata_ux_cleanup.py @@ -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") 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_examples.py b/tests/test_examples.py index 065a523..73002e0 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -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 diff --git a/tests/test_feature_matrix.py b/tests/test_feature_matrix.py index 03a952d..f194325 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, @@ -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()