Skip to content

stacktraces: follow-up to "Enable method signature printing for inlined frames"#61699

Merged
topolarity merged 10 commits into
JuliaLang:masterfrom
mlechu:debuginfo-pc-fix
Jun 1, 2026
Merged

stacktraces: follow-up to "Enable method signature printing for inlined frames"#61699
topolarity merged 10 commits into
JuliaLang:masterfrom
mlechu:debuginfo-pc-fix

Conversation

@mlechu

@mlechu mlechu commented Apr 30, 2026

Copy link
Copy Markdown
Member

Two changes to #53925:

  • When recursing on linetable in append_lineinfo, we ended up with
    the wrong pc in the column field, since we were using the innermost
    index rather than the one usable with a CodeInstance's .debuginfo.
    Store the optimized pc in the "innermost" frame, since DILocation
    expects inlinee locations to change more quickly than inliners (see
    performance discussion below). Thanks Cody and Jameson for this
    suggestion.
  • Update stacktraces.jl to reconstruct the list of inlined frames upon
    seeing a debuginfo, pc pair. See comment; I don't like having both
    this code and the old fallback lookup implementation, but it seems
    less brittle to just re-create the inlined frame stack than to create it
    and match it with the linear frames array from
    jl_lookup_code_address.

@mlechu mlechu requested a review from aviatesk April 30, 2026 21:07
@mlechu mlechu mentioned this pull request May 1, 2026

@aviatesk aviatesk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the point 2 is fixing a real bug, not an intentional design in #53925. I missed the case where the outer debuginfo has a linetable indirection.

The existing inlined_test just happens to pass by coincidence as every IR PC of f points at to=1, so the column lands on the right edge regardless.
I crafted this reproducer of the bug that surfaces when the caller has several non-inlined statements before an inlined call:

@inline f_inner3(x) = x > 0 ? x : error("neg: $x")
function f_parent3(a, b, c, d)
    s = 0
    @noinline begin   # keep `+` as invokes so codelocs has `to=0` entries
        s += a; s += b; s += c; s += d
    end
    return f_inner3(s)
end
let st = try f_parent3(1, 2, 3, -10) catch; stacktrace(catch_backtrace()) end
    sf = only(filter(sf -> sf.func === :f_inner3 && sf.inlined, st))
    @test sf.linfo isa Core.MethodInstance
    @test sf.linfo.def === which(f_inner3, (Int,))
end

This fails on master, but passes with your patch.

Point 1 also LGTM, so ready to come out of draft from my side.

@mlechu mlechu force-pushed the debuginfo-pc-fix branch from ccc7603 to 34b44e4 Compare May 1, 2026 16:20
@mlechu mlechu marked this pull request as ready for review May 1, 2026 16:20
Comment thread src/stackwalk.c Outdated
if (line != -1) {
if (pc > 0)
jl_safe_fprintf(s, "%s at %s:%d:%d%s\n", func_name, file_name, line, pc, inlined_str);
jl_safe_fprintf(s, "%s at %s:%d (pc: %d)%s\n", func_name, file_name, line, pc, inlined_str);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our frame construction fills in .pc even for C frames, so that may also need follow-up.

topolarity
topolarity previously approved these changes May 1, 2026

@topolarity topolarity left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find. Probably means that we need better testing infra. for the various DebugInfo variants long-term.

framepc may be a slightly better name than origpc

Thanks for the fix-up @mlechu !

@mlechu

mlechu commented May 1, 2026

Copy link
Copy Markdown
Member Author

Failure (OOM in LLVM on 32-bit windows) looks real; the gnuprofiling build shows section .debug_info in sys.so has gotten 3x larger

@mlechu

mlechu commented May 1, 2026

Copy link
Copy Markdown
Member Author

I suspect the cause is storing way more unique locations given that optimized IR usually has more unique indices. For my current use case (byte provenance) it doesn't matter if we use the innermost-linetable indices, but I'm not sure if that's OK for the purposes of the original change.

@topolarity topolarity dismissed their stale review May 7, 2026 14:15

Change / approach is conceptually right, but size regression needs to be resolved before merging

@mlechu mlechu closed this May 20, 2026
@mlechu mlechu reopened this May 20, 2026
@mlechu mlechu force-pushed the debuginfo-pc-fix branch 2 times, most recently from de72d43 to 95ac8f5 Compare May 21, 2026 16:58
topolarity added a commit that referenced this pull request May 21, 2026
…tion (#61817)

This change adds a basic C API for reading `jl_debuginfo_t`, both in its
current form and with byte-precise provenance to be generated by
JuliaLowering.

```
(debuginfo, pc) -> (firstbyte, lastbyte)
(debuginfo, byte) -> (line, column)
(debuginfo, pc) -> (firstline, firstcolumn)
debuginfo -> first_line_of_entire_codeinfo
```

Note that the byte-precise provenance is not yet produced (and I've
marked those branches unreachable). I'm making this change first so I
can use them to try and resolve issues in #61699 and #53925. (Namely,
I'm going to try and compute inlined frames from julia debuginfo
rather than DWARF.)

## Implementation of byte-precise info storage

The API shouldn't depend on how we do this, but I'll describe it here
for reference.

The simplicity of our current `Core.DebugInfo` (ref. #52415) is thanks
to simple line numbers looking the same as indices into the next 
debuginfo, so they can be stored in the same way. Unfortunately, 
byte-precise information breaks this:
from an index, we want to be able to know two byte positions, what lines
they fall on, and how far from the beginning of those lines they are.

I intend to store this info by putting a string in `linetable` like we
do for `di.codelocs`. The compression format is described in julia.h, but
only a couple points are relevant here:
- Like `di.codelocs` today, I made a reasonable attempt to compress it
  (so we don't blow up the sysimg) without trying to be too clever.
- Position lookup for a single IR statement can still be done without
  decompressing everything.

I've already implemented the compression/decompression code (in
JuliaLowering), but it's not part of this PR. This format might be torn
out when we start trying to store AST provenance.

---------

Co-authored-by: Cody Tapscott <topolarity@tapscott.me>
@mlechu mlechu force-pushed the debuginfo-pc-fix branch from 95ac8f5 to b119158 Compare May 22, 2026 20:04
mkitti pushed a commit to mkitti/julia that referenced this pull request May 22, 2026
…tion (JuliaLang#61817)

This change adds a basic C API for reading `jl_debuginfo_t`, both in its
current form and with byte-precise provenance to be generated by
JuliaLowering.

```
(debuginfo, pc) -> (firstbyte, lastbyte)
(debuginfo, byte) -> (line, column)
(debuginfo, pc) -> (firstline, firstcolumn)
debuginfo -> first_line_of_entire_codeinfo
```

Note that the byte-precise provenance is not yet produced (and I've
marked those branches unreachable). I'm making this change first so I
can use them to try and resolve issues in JuliaLang#61699 and JuliaLang#53925. (Namely,
I'm going to try and compute inlined frames from julia debuginfo
rather than DWARF.)

## Implementation of byte-precise info storage

The API shouldn't depend on how we do this, but I'll describe it here
for reference.

The simplicity of our current `Core.DebugInfo` (ref. JuliaLang#52415) is thanks
to simple line numbers looking the same as indices into the next 
debuginfo, so they can be stored in the same way. Unfortunately, 
byte-precise information breaks this:
from an index, we want to be able to know two byte positions, what lines
they fall on, and how far from the beginning of those lines they are.

I intend to store this info by putting a string in `linetable` like we
do for `di.codelocs`. The compression format is described in julia.h, but
only a couple points are relevant here:
- Like `di.codelocs` today, I made a reasonable attempt to compress it
  (so we don't blow up the sysimg) without trying to be too clever.
- Position lookup for a single IR statement can still be done without
  decompressing everything.

I've already implemented the compression/decompression code (in
JuliaLowering), but it's not part of this PR. This format might be torn
out when we start trying to store AST provenance.

---------

Co-authored-by: Cody Tapscott <topolarity@tapscott.me>
@mlechu mlechu force-pushed the debuginfo-pc-fix branch 6 times, most recently from cd05077 to ab6a0c2 Compare May 28, 2026 19:21
@mlechu

mlechu commented May 28, 2026

Copy link
Copy Markdown
Member Author

Approach and PR message updated; this is ready for re-review.

Keno pushed a commit that referenced this pull request May 29, 2026
…61939)

Fix #33457.  Nightly:

```
julia> begin
       # line 2
       [y for _ in 1:10]
       # line 4
       end
ERROR: UndefVarError: `y` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] #2
   @ ./none:-1 [inlined]
 [2] iterate
   @ ./generator.jl:48 [inlined]
 [3] collect(itr::Base.Generator{UnitRange{Int64}, var"#2#3"})
   @ Base ./array.jl:833
 [4] top-level scope
   @ REPL[1]:3
```

This change:
```
ERROR: UndefVarError: `y` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] #12
   @ ./REPL[1]:3 [inlined]
 [2] iterate
   @ ./generator.jl:48 [inlined]
 [3] collect(itr::Base.Generator{UnitRange{Int64}, var"#12#13"})
   @ Base ./array.jl:833
 [4] top-level scope
   @ REPL[1]:3
```

As a sneak peek, this is further improved with #61699:
```
ERROR: UndefVarError: `y` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
 [1] (::var"#12#13")(::Int64)
   @ Main REPL[1]:3 [inlined]
 [2] iterate(::Base.Generator{UnitRange{Int64}, var"#12#13"})
   @ Base generator.jl:48 [inlined]
 [3] collect(itr::Base.Generator{UnitRange{Int64}, var"#12#13"})
   @ Base array.jl:833
 [4] top-level scope
   @ REPL[1]:3
```


I doubt this frame is important (it being a lambda is considered an
implementation detail IIRC) but (1) we shouldn't show users a nonsense
frame,
and (2) this fixes another case where we produce malformed DebugInfo.
Comment thread src/codegen.cpp

@topolarity topolarity left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exciting this worked out in the end!

I've opened JuliaLang/llvm-project#52 to expand the DWARF column to 32-bit, but I think we can land this as-is

Comment thread base/stacktraces.jl
Comment thread base/stacktraces.jl
mlechu and others added 10 commits June 1, 2026 09:59
Co-authored-by: Shuhei Kadowaki <aviatesk@gmail.com>
We want to decode `DebugInfo` rather than using linfo from
     `jl_lookup_code_address`.  This temporarily matches flisp.
We no longer get the dummy `(::Tuple)() at <missing>:0` in the stacktrace since
     we produced the opaque closure by hand with abnormal debuginfo.  Seeing no
     better alternative, still inspect each of the frames to check we don't
     crash, but the absence of the frame means this test probably can't hit the
     original crash anyway.
Avoid construction using Core.OpaqueClosure
@mlechu mlechu force-pushed the debuginfo-pc-fix branch from ab6a0c2 to 85e3619 Compare June 1, 2026 16:59
@mlechu mlechu added the merge me PR is reviewed. Merge when all tests are passing label Jun 1, 2026
@topolarity topolarity merged commit 39bd0b4 into JuliaLang:master Jun 1, 2026
10 of 11 checks passed
@topolarity topolarity removed the merge me PR is reviewed. Merge when all tests are passing label Jun 1, 2026
@aviatesk

aviatesk commented Jun 2, 2026

Copy link
Copy Markdown
Member

Thanks again for fixing this issue and getting it merged. I really appreciate it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants