Complete the debuginfo wrappers#549
Draft
maleadt wants to merge 27 commits into
Draft
Conversation
Introduces the `DIBuilder` type wrapping `LLVMDIBuilderRef`, with `allow_unresolved` kwarg, do-block form, `dispose`, `finalize!`, and `finalize_subprogram!`. Follows the existing `IRBuilder` pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the first DIBuilder factory helpers, along with the DIModule and DINamespace metadata types (both register as scopes). The compileunit! signature mirrors PR #152's compilationunit! with the producer/split-name/emission-kind/sysroot/sdk knobs exposed as kwargs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two new DILocalScope subtypes and the matching lexicalblock!/ lexicalblockfile! builder methods. Tested alongside DILocation for nested scopes and inlined_at chains. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the full set of type constructors exposed by llvm-c/DebugInfo.h: basic, derived (pointer/typedef/qualified/artificial/member/bitfield/ static/member-pointer/inheritance/reference), composite (struct/union/ class/array/vector/enumeration/forward-decl/replaceable), and subroutine. Also adds getorcreatesubrange! and registers DIEnumerator and DISubrange as metadata types. align(::DIType) and tag(::DINode) accessors round out the read API. LLVM-21-only additions (settype!, subrangetype!, dynamicarraytype!, enumeratorarb!) are gated behind @static if version() >= v"21". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds DIBuilder methods for creating subprograms, local/parameter variables, DIExpression and DIGlobalVariableExpression metadata, and the global-variable forward declaration. Registers DIExpression and DIGlobalVariableExpression as new metadata types and exposes the variable/expression accessors on DIGlobalVariableExpression. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds declare_before!, declare_at_end!, value_before!, value_at_end! with LLVM-19-aware dispatch: intrinsic-based on LLVM ≤ 18 (returning Instruction) and record-based on LLVM ≥ 19 (returning a new DbgRecord wrapper). LLVM 20+ adds DILabel, label!, label_before!, label_at_end!. debuglocation / debuglocation! on Instruction wraps LLVMInstructionGet/SetDebugLoc and mirrors the existing IRBuilder helpers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds wrappers for the four DIImportedEntity flavors, DIMacro / DIMacroFile with macro!/tempmacrofile!, and registers the matching metadata types. Label support was added in step 6 together with the other LLVM-20-gated record helpers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds temporary_mdnode, dispose_temporary, and replace_all_uses_with! for cycle-breaking in recursive debug-info types. replacearrays! (rebinding a composite type's elements) and replacetype! (rewriting a DISubProgram's type) are gated behind LLVM ≥ 21, where they were first exposed through the C API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the five functions that were missed in the earlier passes: getorcreatearray!, getorcreatetypearray!, objcivar!, objcproperty!, and debug_metadata_version(::Module) for the per-module DWARF version. Brings our coverage of llvm-c/DebugInfo.h to 100%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously users had to call LLVM.finalize! explicitly before dispose, per the C API's stated contract. That's a footgun: forgetting it leaks temporary metadata. dispose now calls LLVMDIBuilderFinalize first (idempotent in LLVM), making the do-block pattern just-work. finalize! stays callable for the rare case where the module has to be consumed before dispose runs. finalize_subprogram! is no longer marked @public — it's an advanced streaming helper, not a typical user API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DIBuilder::finalize() already iterates AllSubprograms and calls finalizeSubprogram on each (DIBuilder.cpp:102). Unlike finalize!, the subprogram-level call is not needed for correctness — only as an optimization/streaming hint. Updated docstring to say so. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The common case (e.g. Julia code) has linkage_name empty or equal to name, and scope_line equal to line. The C API requires both positionally; Julia can do better by defaulting linkage_name="" (LLVM falls back to name) and scope_line=line. Call sites previously reading subprogram!(dib, scope, "add", "add", file, 1, stype, 1) now read subprogram!(dib, scope, "add", file, 1, stype). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds consttype!, volatiletype!, lvaluereferencetype!, and rvaluereferencetype! as thin aliases for qualifiedtype! / referencetype! with the relevant DW_TAG. Spares users from remembering the four DWARF tag numbers that cover 95% of the qualified-type / C++ reference use cases. DW_TAG constants themselves aren't re-exported — that's a separate, principled job for all enums/constants in llvm-c. The wrappers hardcode the numeric tags internally so they remain usable today. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches existing LLVM.jl precedent: multi-word descriptive names use underscores (function_type, return_type, called_type, value_type, fast_math, replace_metadata_uses!, parameter_attributes), while only LLVM-jargon acronyms stay compressed (addrspace, callconv, datalayout). The DIBuilder factories I originally landed as basictype!, pointertype!, structtype!, compileunit!, autovariable!, etc. broke that rule; rename them to basic_type!, pointer_type!, struct_type!, compile_unit!, auto_variable!, etc. Also folds the metadata-replace helper into the existing replace_uses! (wrapping LLVMReplaceAllUsesWith on Value) as a Metadata method — same semantics, cleaner dispatch than a parallel replace_all_uses_with!. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, `dispose` (and `finalize!`) unconditionally called
`LLVMDIBuilderFinalize`, which in assertion-enabled LLVM aborts with
"creating type nodes without a CU is not supported" when no compile unit
was registered.
Track CU registration in a `has_compile_unit::Ref{Bool}` field on
DIBuilder, set by `compile_unit!`. Finalization is skipped when the flag
is false; in release LLVM the C call also early-returns in that case, so
this only changes behavior under assertions.
`flags` clashes with the `flags=LLVMDIFlags` kwarg used on every type factory on the same page; `cmdline` better conveys what the string is (command-line options embedded verbatim in the emitted debug info).
LLVM 20 added a required `Implicit::LLVMBool` parameter to `LLVMDIBuilderCreateObjectPointerType` that controls whether the resulting type also carries `DI_FLAG_ARTIFICIAL`. Expose it as an `implicit::Bool=true` kwarg on LLVM 20+ (preserving the LLVM ≤ 19 implicit-artificial behavior) and keep the two-argument call on older LLVMs.
The C entry points (`LLVMDIBuilderInsertDeclare*` / `LLVMDIBuilderInsertDbgValue*`) unconditionally `unwrap<DILocalVariable>` the var argument; passing a `DIGlobalVariable` is undefined behavior (asserts in debug LLVM, silent misbehavior in release). Reflect the actual constraint in the Julia signatures; the docstrings already said DILocalVariable.
Previously `subroutine_type!` took a single `Vector{<:Metadata}` whose
0th entry doubled as the return type -- a convention inherited straight
from the C API and easy to forget. Accept the return type as a separate
positional argument (with `nothing` for `void`) and let the parameter
vector mean what it says.
`LLVMDIBuilderCreateStaticMemberType` unconditionally `cast<Constant>`s its `ConstantVal` operand and aborts on null (unlike the C++ entry point, which calls `getConstantOrNull`). Make `constant_val` a required positional argument so the foot-gun isn't reachable from the default- argument path.
Passing `nothing` (the old default) was silently forwarded as C_NULL, which LLVM segfaults on -- acknowledged by an `XXX:` comment in the previous implementation. Make the scope required and throw an `UndefRefError` from the explicit-`nothing` overload so the failure mode is a Julia exception instead of a native crash. The test that relied on the null-scope path is updated to attach a real subprogram scope via a small DIBuilder.
Several testsets exercised patterns that LLVM silently tolerates in
release builds but rejects under assertions:
- `lexical_block!` and `label!` were called with a `DICompileUnit`
scope. LLVM's `getNonCompileUnitScope` drops CU scopes to null on
these entry points, then asserts on the null. Nest both under a
real `DISubprogram` scope instead.
- `replaceable_composite_type!` was created and never RAUW'd in the
type-constructors sweep; finalize then asserts on the unresolved
temporary. It remains exercised in the mutation testset, where it
is properly replaced.
Also add an `LLVM.verify(mod) === nothing` check after the end-to-end
build in the instruction-level-insertion testset -- a stronger
structural check than the existing `isa`/`occursin` assertions.
The underlying C entry points clone the input DIType with an extra flag set, so the return kind is always whatever kind was passed in, not specifically DIDerivedType. Wrap through Metadata(...)::DIType so a DIBasicType stays a DIBasicType.
LLVMDIBuilderCreateObjCProperty returns a DIObjCProperty (metadata kind 27), not a DIDerivedType. The old wrapper would trip typecheck and, without it, emit an unidentified metadata kind on the first use.
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## master #549 +/- ##
==========================================
+ Coverage 85.91% 86.01% +0.10%
==========================================
Files 47 47
Lines 2953 3140 +187
==========================================
+ Hits 2537 2701 +164
- Misses 416 439 +23
... and 2 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Docstrings placed inside @static if branches aren't picked up by Documenter's checkdocs: the docstring ends up attached to a block expression rather than the function binding, so `@docs LLVM.foo!` reports "no docs found". Split every version-gated factory into an outer, unconditional docstring on the bare name and version-selected implementations below, and document the concrete DI*Type subtypes (which have no standalone docstrings because they're generated in an @eval loop) so cross-references to them resolve too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Subsumes #152