• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / 1652

20 Apr 2026 08:38PM UTC coverage: 77.962% (+0.3%) from 77.623%
1652

push

buildkite

web-flow
codegen: Propagate `ipo_purity_bits` to LLVM function attributes (#61394)

Translate Julia's inferred effects (consistent, effect_free, nothrow,
terminates, notaskstate) into LLVM function attributes so that
middle-end passes like GVN, LICM, and DSE can exploit them.

The key design insight is that GC interactions don't need to be visible
before GC lowering. Call-site declarations get optimistic memory
attributes (e.g. memory(argmem: read)) that enable pre-GC optimizations,
then LateLowerGCFrame widens them to memory(readwrite) before safepoint
analysis so post-GC passes see correct semantics.

Attributes added:
- nounwind: for nothrow functions (with uwtable(async) on definitions
  to preserve .eh_frame for stack scanning)
- mustprogress: for terminating functions
- willreturn: for nothrow+terminating functions
- memory(argmem: read): for consistent+effect_free functions with no
  user-facing pointer arguments (call-site declarations only)
- readnone on gcstack param: for notaskstate functions, so LICM can
  hoist pure calls past heap stores
- "julia.safepoint" marker: on all call-site declarations, used by
  LateLowerGCFrame to identify and widen optimistic attrs

LateLowerGCFrame strips all optimistic attributes (memory effects,
readnone on gcstack) from both call instructions and function
declarations before safepoint analysis runs.

Previously explored in #47844

Developed with Claude

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Gabriel Baraldi <28694980+gbaraldi@users.noreply.github.com>

65490 of 84002 relevant lines covered (77.96%)

23535049.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.31
/base/stacktraces.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
"""
4
Tools for collecting and manipulating stack traces. Mainly used for building errors.
5
"""
6
module StackTraces
7

8

9
import Base: hash, ==, show
10

11
using Core: CodeInfo, MethodInstance, CodeInstance
12
using Base.IRShow
13

14
export StackTrace, StackFrame, stacktrace
15

16
"""
17
    StackFrame
18

19
Stack information representing execution context, with the following fields:
20

21
- `func::Symbol`
22

23
  The name of the function containing the execution context.
24

25
- `linfo::Union{Method, Core.MethodInstance, Core.CodeInstance, Core.CodeInfo, Nothing}`
26

27
  The Method, MethodInstance, CodeInstance, or CodeInfo containing the execution context (if it could be found),
28
     or nothing (for example, if the inlining was a result of macro expansion).
29

30
- `file::Symbol`
31

32
  The path to the file containing the execution context.
33

34
- `line::Int`
35

36
  The line number in the file containing the execution context.
37

38
- `from_c::Bool`
39

40
  True if the code is from C.
41

42
- `inlined::Bool`
43

44
  True if the code is from an inlined frame.
45

46
- `pointer::UInt64`
47

48
  Representation of the pointer to the execution context as returned by `backtrace`.
49

50
- `pc::Int`
51

52
  1-based statement index within the frame's `CodeInfo`, recovered from the DWARF
53
  column emitted by codegen. `0` when unavailable (e.g. for C frames or frames
54
  without enriched debug info). Used internally by `StackTraces.lookup` to resolve
55
  the `MethodInstance` for inlined frames; also surfaced in low-level safe
56
  backtrace output (e.g. on segfault) but not in Julia's default `show` for
57
  `StackFrame`.
58
"""
59
struct StackFrame # this type should be kept platform-agnostic so that profiles can be dumped on one machine and read on another
60
    "the name of the function containing the execution context"
21,376✔
61
    func::Symbol
62
    "the path to the file containing the execution context"
63
    file::Symbol
64
    "the line number in the file containing the execution context"
65
    line::Int
66
    "the CodeInstance or CodeInfo containing the execution context (if it could be found), \
67
     or nothing (for example, if the inlining was a result of macro expansion)."
68
    linfo::Union{Core.MethodInstance, Core.CodeInstance, Method, CodeInfo, Nothing}
69
    "true if the code is from C"
70
    from_c::Bool
71
    "true if the code is from an inlined frame"
72
    inlined::Bool
73
    "representation of the pointer to the execution context as returned by `backtrace`"
74
    pointer::UInt64  # Large enough to be read losslessly on 32- and 64-bit machines.
75
    "1-based statement index (PC) within the frame's CodeInfo, or 0 if unavailable"
76
    pc::Int
77
end
78

79
StackFrame(func, file, line, linfo, from_c, inlined, pointer) =
11,324✔
80
    StackFrame(func, file, line, linfo, from_c, inlined, pointer, 0)
81
StackFrame(func, file, line) = StackFrame(Symbol(func), Symbol(file), line,
30✔
82
                                          nothing, false, false, 0, 0)
83

84
"""
85
    StackTrace
86

87
An alias for `Vector{StackFrame}` provided for convenience; returned by calls to
88
`stacktrace`.
89
"""
90
const StackTrace = Vector{StackFrame}
91

92
const empty_sym = Symbol("")
93
const UNKNOWN = StackFrame(empty_sym, empty_sym, -1, nothing, true, false, 0) # === lookup(C_NULL)
94

95

96
#=
97
If the StackFrame has function and line information, we consider two of them the same if
98
they share the same function/line information.
99
=#
100
function ==(a::StackFrame, b::StackFrame)
101
    return a.line == b.line && a.from_c == b.from_c && a.func == b.func && a.file == b.file && a.inlined == b.inlined # excluding linfo and pointer
271,734✔
102
end
103

104
function hash(frame::StackFrame, h::UInt)
105
    h ⊻= 0xf4fbda67fe20ce88 % UInt
674✔
106
    h = hash(frame.line, h)
1,963,409✔
107
    h = hash(frame.file, h)
1,963,409✔
108
    h = hash(frame.func, h)
1,963,409✔
109
    h = hash(frame.from_c, h)
1,963,409✔
110
    h = hash(frame.inlined, h)
1,963,409✔
111
    return h
543✔
112
end
113

114
# Decode a single codeloc entry, advancing one level of inlining. Returns
115
# `(line, to, next_pc)` where `to` is the 1-based index into `debuginfo.edges`
116
# for the inlined call directly at `pc` (0 if none), and `next_pc` is the PC
117
# within `debuginfo.edges[to]`'s CodeInfo. To traverse nested inlinings, the
118
# caller must follow the edge and call this again with `(edges[to], next_pc)`.
119
debuginfo_codeloc(debuginfo::Core.DebugInfo, pc::Integer) =
808✔
120
    @ccall jl_uncompress1_codeloc(debuginfo.codelocs::Any, pc::Csize_t)::NTuple{3,Int32}
121

122
"""
123
    lookup(pointer::Ptr{Cvoid})::Vector{StackFrame}
124

125
Given a pointer to an execution context (usually generated by a call to `backtrace`), looks
126
up stack frame context information. Returns an array of frame information for all functions
127
inlined at that point, innermost function first.
128
"""
129
Base.@constprop :none function lookup(pointer::Ptr{Cvoid})
7,882✔
130
    infos = @ccall jl_lookup_code_address(pointer::Ptr{Cvoid}, false::Cint)::Core.SimpleVector
7,882✔
131
    pointer = convert(UInt64, pointer)
7,882✔
132
    isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
7,882✔
133
    ninfos = length(infos)
7,882✔
134
    res = Vector{StackFrame}(undef, ninfos)
7,882✔
135
    local debuginfo = false
7,882✔
136
    local parent_pc = 0
7,882✔
137
    for i = ninfos:-1:1
15,589✔
138
        info = infos[i]::Core.SimpleVector
10,327✔
139
        @assert length(info) == 7 "corrupt return from jl_lookup_code_address"
10,327✔
140
        func = info[1]::Symbol
10,327✔
141
        file = info[2]::Symbol
10,327✔
142
        linenum = info[3]::Int
10,327✔
143
        linfo = info[4]
10,327✔
144
        pc = info[7]::Int
10,327✔
145
        if linfo isa Core.CodeInstance
10,327✔
146
            if debuginfo === false
2,856✔
147
                debuginfo = linfo.debuginfo
2,856✔
148
            else
149
                debuginfo = true
×
150
            end
151
            linfo = linfo.def
2,856✔
152
        elseif debuginfo isa Core.DebugInfo && parent_pc > 0
7,471✔
153
            # Use the parent frame's PC to look up which inlining edge of the
154
            # current `debuginfo` corresponds to this frame's call site.
155
            _, to::Int, _ = debuginfo_codeloc(debuginfo, parent_pc)
808✔
156
            if to > 0 && to <= length(debuginfo.edges)
808✔
157
                debuginfo = debuginfo.edges[to]::Core.DebugInfo
465✔
158
                def = debuginfo.def
465✔
159
                if !(def isa Symbol)
465✔
160
                    linfo = def
465✔
161
                end
162
            else
163
                debuginfo = true
343✔
164
            end
165
        end
166
        parent_pc = pc
10,327✔
167
        res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer, pc)
10,327✔
168
    end
12,772✔
169
    return res
7,882✔
170
end
171

172
const top_level_scope_sym = Symbol("top-level scope")
173

174
function lookup(ip::Base.InterpreterIP)
431✔
175
    code = ip.code
431✔
176
    if code === nothing
431✔
177
        # interpreted top-level expression with no CodeInfo
178
        return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)]
×
179
    end
180
    # prepare approximate code info
181
    if code isa MethodInstance && (meth = code.def; meth isa Method)
431✔
182
        func = meth.name
×
183
        file = meth.file
×
184
        line = meth.line
×
185
        codeinfo = meth.source
×
186
    else
187
        func = top_level_scope_sym
431✔
188
        file = empty_sym
431✔
189
        line = Int32(0)
431✔
190
        if code isa Core.CodeInstance
431✔
191
            codeinfo = code.inferred::CodeInfo
×
192
            def = code.def
×
193
            if isa(def, Core.ABIOverride)
×
194
                def = def.def
×
195
            end
196
            if isa(def, MethodInstance)
×
197
                let meth = def.def
×
198
                    if isa(meth, Method)
×
199
                        func = meth.name
×
200
                        file = meth.file
×
201
                        line = meth.line
×
202
                    end
203
                end
204
            end
205
        else
206
            codeinfo = code::CodeInfo
431✔
207
        end
208
    end
209
    def = (code isa CodeInfo ? StackTraces : code) # Module just used as a token for top-level code
431✔
210
    pc::Int = max(ip.stmt + 1, 0) # n.b. ip.stmt is 0-indexed
431✔
211
    scopes = IRShow.LineInfoNode[]
431✔
212
    IRShow.append_scopes!(scopes, pc, codeinfo.debuginfo, def)
862✔
213
    if isempty(scopes)
431✔
214
        return [StackFrame(func, file, line, code, false, false, 0)]
×
215
    end
216
    res = Vector{StackFrame}(undef, length(scopes))
431✔
217
    inlined = false
431✔
218
    def_local = def
431✔
219
    for i in eachindex(scopes)
853✔
220
        lno = scopes[i]
1,498✔
221
        if inlined
1,498✔
222
            def_local = lno.method
1,067✔
223
            def_local isa Union{Method,Core.CodeInstance,MethodInstance} || (def_local = nothing)
1,067✔
224
        else
225
            def_local = codeinfo
431✔
226
        end
227
        res[i] = StackFrame(IRShow.normalize_method_name(lno.method), lno.file, lno.line,
1,498✔
228
            def_local, false, inlined, 0)
229
        inlined = true
1,498✔
230
    end
2,565✔
231
    return res
431✔
232
end
233

234
"""
235
    stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false])::StackTrace
236

237
Return a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace
238
doesn't return C functions, but this can be enabled.) When called without specifying a
239
trace, `stacktrace` first calls `backtrace`.
240
"""
241
Base.@constprop :none function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false)
335✔
242
    stack = StackTrace()
2,062✔
243
    for ip in trace
250✔
244
        for frame in lookup(ip)
3,660✔
245
            # Skip frames that come from C calls.
246
            if c_funcs || !frame.from_c
9,442✔
247
                push!(stack, frame)
2,075✔
248
            end
249
        end
4,884✔
250
    end
3,660✔
251
    return stack
250✔
252
end
253

254
Base.@constprop :none function stacktrace(c_funcs::Bool=false)
255
    stack = stacktrace(backtrace(), c_funcs)
18✔
256
    # Remove frame for this function (and any functions called by this function).
257
    remove_frames!(stack, :stacktrace)
258
    # also remove all of the non-Julia functions that led up to this point (if that list is non-empty)
259
    c_funcs && deleteat!(stack, 1:(something(findfirst(frame -> !frame.from_c, stack), 1) - 1))
260
    return stack
261
end
262

263
"""
264
    remove_frames!(stack::StackTrace, name::Symbol)
265

266
Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and
267
removes the `StackFrame` specified by the function name from the `StackTrace` (also removing
268
all frames above the specified function). Primarily used to remove `StackTraces` functions
269
from the `StackTrace` prior to returning it.
270
"""
271
function remove_frames!(stack::StackTrace, name::Symbol)
×
272
    deleteat!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0))
×
273
    return stack
×
274
end
275

276
function remove_frames!(stack::StackTrace, names::Vector{Symbol})
×
277
    deleteat!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0))
×
278
    return stack
×
279
end
280

281
"""
282
    remove_frames!(stack::StackTrace, m::Module)
283

284
Return the `StackTrace` with all `StackFrame`s from the provided `Module` removed.
285
"""
286
function remove_frames!(stack::StackTrace, m::Module)
287
    filter!(f -> !from(f, m), stack)
3✔
288
    return stack
×
289
end
290

291
is_top_level_frame(f::StackFrame) = f.linfo isa CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym)
18✔
292

293
function frame_method_or_module(lkup::StackFrame)
294
    code = lkup.linfo
3,271✔
295
    code isa Method && return code
3,271✔
296
    code isa Module && return code
967✔
297
    mi = frame_mi(lkup)
3,283✔
298
    mi isa MethodInstance || return nothing
3,283✔
299
    return mi.def
3,259✔
300
end
301

302
function frame_mi(lkup::StackFrame)
303
    code = lkup.linfo
6,530✔
304
    code isa Core.CodeInstance && (code = code.def)
6,530✔
305
    code isa Core.ABIOverride && (code = code.def)
6,530✔
306
    code isa MethodInstance || return nothing
6,542✔
307
    return code
6,518✔
308
end
309

310
function show_spec_linfo(io::IO, frame::StackFrame)
5,137✔
311
    linfo = frame.linfo
5,137✔
312
    if linfo === nothing
5,137✔
313
        if frame.func === empty_sym
1,547✔
314
            print(io, "ip:0x", string(frame.pointer, base=16))
1✔
315
        elseif frame.func === top_level_scope_sym
1,546✔
316
            print(io, "top-level scope")
67✔
317
        else
318
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
1,479✔
319
        end
320
    elseif linfo isa CodeInfo
3,590✔
321
        print(io, "top-level scope")
331✔
322
    elseif linfo isa Module
3,259✔
323
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
×
324
    else
325
        if linfo isa Union{MethodInstance, CodeInstance}
3,259✔
326
            def = frame_method_or_module(frame)
6,518✔
327
            if def isa Module
3,259✔
328
                Base.show_mi(io, linfo::MethodInstance, #=from_stackframe=#true)
×
329
            elseif linfo isa CodeInstance && linfo.owner !== nothing
3,259✔
330
                show_custom_spec_sig(io, linfo.owner, linfo, frame)
×
331
            else
332
                # Equivalent to the default implementation of `show_custom_spec_sig`
333
                # for `linfo isa CodeInstance`, but saves an extra dynamic dispatch.
334
                mi = frame_mi(frame)::MethodInstance
3,259✔
335
                show_spec_sig(io, def::Method, mi.specTypes)
3,259✔
336
            end
337
        else
338
            m = linfo::Method
×
339
            show_spec_sig(io, m, m.sig)
×
340
        end
341
    end
342
end
343

344
# Can be extended by compiler packages to customize backtrace display of custom code instance frames
345
function show_custom_spec_sig(io::IO, @nospecialize(owner), linfo::CodeInstance, frame::StackFrame)
346
    mi = Base.get_ci_mi(linfo)
×
347
    m = mi.def::Method # the case ::Module is handled in show_spec_linfo
×
348
    return show_spec_sig(io, m, mi.specTypes)
×
349
end
350

351
function show_spec_sig(io::IO, m::Method, @nospecialize(sig::Type))
3,259✔
352
    if get(io, :limit, :false)::Bool
10,460✔
353
        if !haskey(io, :displaysize)
2,446✔
354
            io = IOContext(io, :displaysize => displaysize(io))
274✔
355
        end
356
    end
357
    argnames = Base.method_argnames(m)
6,518✔
358
    argnames = replace(argnames, :var"#unused#" => :var"")
3,259✔
359
    if m.nkw > 0
3,259✔
360
        # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...; kw_args)
361
        kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+m.nkw) ]
376✔
362
        uw = Base.unwrap_unionall(sig)::DataType
307✔
363
        pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(m.nkw+2):end]...}, sig)
307✔
364
        kwnames = argnames[2:(m.nkw+1)]
614✔
365
        for i = 1:length(kwnames)
322✔
366
            str = string(kwnames[i])::String
376✔
367
            if endswith(str, "...")
376✔
368
                kwnames[i] = Symbol(str[1:end-3])
×
369
            end
370
        end
445✔
371
        Base.show_tuple_as_call(io, m.name, pos_sig;
307✔
372
                                demangle=true,
373
                                kwargs=zip(kwnames, kwarg_types),
374
                                argnames=argnames[m.nkw+2:end])
375
    else
376
        Base.show_tuple_as_call(io, m.name, sig; demangle=true, argnames)
2,952✔
377
    end
378
end
379

380
function show(io::IO, frame::StackFrame)
448✔
381
    show_spec_linfo(io, frame)
448✔
382
    if frame.file !== empty_sym
448✔
383
        file_info = basename(string(frame.file))
435✔
384
        print(io, " at ")
435✔
385
        print(io, file_info, ":")
435✔
386
        if frame.line >= 0
435✔
387
            print(io, frame.line)
426✔
388
        else
389
            print(io, "?")
9✔
390
        end
391
    end
392
    if frame.inlined
448✔
393
        print(io, " [inlined]")
122✔
394
    end
395
end
396

397
function Base.parentmodule(frame::StackFrame)
398
    linfo = frame.linfo
9,882✔
399
    if linfo isa CodeInstance
9,882✔
400
        linfo = linfo.def
×
401
        if isa(linfo, Core.ABIOverride)
×
402
            linfo = linfo.def
×
403
        end
404
    end
405
    if linfo isa MethodInstance
9,882✔
406
        def = linfo.def
6,037✔
407
        if def isa Module
6,037✔
408
            return def
×
409
        else
410
            return (def::Method).module
6,037✔
411
        end
412
    elseif linfo isa Method
3,845✔
413
        return linfo.module
×
414
    elseif linfo isa Module
3,845✔
415
        return linfo
×
416
    else
417
        # The module is not always available (common reasons include
418
        # frames arising from the interpreter)
419
        nothing
1,689✔
420
    end
421
end
422

423
"""
424
    from(frame::StackFrame, filter_mod::Module)::Bool
425

426
Return whether the `frame` is from the provided `Module`
427
"""
428
function from(frame::StackFrame, m::Module)
×
429
    return parentmodule(frame) === m
×
430
end
431

432
end  # module StackTraces
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc