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

JuliaLang / julia / #38011

17 Feb 2025 06:24AM UTC coverage: 20.248% (-5.6%) from 25.839%
#38011

push

local

web-flow
bpart: Track whether any binding replacement has happened in image modules (#57433)

This implements the optimization proposed in #57426 by keeping track of
whether any bindings were replaced in image modules (excluding `Main` as
facilitated by #57426). In addition, we augment serialization to keep
track of whether a method body contains any GlobalRefs that point to a
loaded (system or package) image. If both of these flags are true, we
can skip scanning the body of the method, since we know that we neither
need to add any additional backedges nor were any of the referenced
bindings invalidated. The performance impact on end-to-end load time is
small, but measurable. Overall `@time using ModelingToolkit`
consistently improves about 5% using this PR. However, I should note
that using time is still about 40% slower than 1.11. This is not
necessarily an Apples-to-Apples comparison as there were substantial
other changes on 1.12 (as well as current load-time-tunings targeting
older versions), but I wanted to put the number context.

2 of 15 new or added lines in 5 files covered. (13.33%)

2655 existing lines in 108 files now uncovered.

9867 of 48731 relevant lines covered (20.25%)

107722.08 hits per line

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

59.04
/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
import Core: CodeInfo, MethodInstance, CodeInstance
11
using Base.IRShow: normalize_method_name, append_scopes!, LineInfoNode
12

13
export StackTrace, StackFrame, stacktrace
14

15
"""
16
    StackFrame
17

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

20
- `func::Symbol`
21

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

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

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

29
- `file::Symbol`
30

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

33
- `line::Int`
34

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

37
- `from_c::Bool`
38

39
  True if the code is from C.
40

41
- `inlined::Bool`
42

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

45
- `pointer::UInt64`
46

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

49
"""
50
struct StackFrame # this type should be kept platform-agnostic so that profiles can be dumped on one machine and read on another
51
    "the name of the function containing the execution context"
34✔
52
    func::Symbol
53
    "the path to the file containing the execution context"
54
    file::Symbol
55
    "the line number in the file containing the execution context"
56
    line::Int
57
    "the CodeInstance or CodeInfo containing the execution context (if it could be found), \
58
     or nothing (for example, if the inlining was a result of macro expansion)."
59
    linfo::Union{Core.MethodInstance, Core.CodeInstance, Method, CodeInfo, Nothing}
60
    "true if the code is from C"
61
    from_c::Bool
62
    "true if the code is from an inlined frame"
63
    inlined::Bool
64
    "representation of the pointer to the execution context as returned by `backtrace`"
65
    pointer::UInt64  # Large enough to be read losslessly on 32- and 64-bit machines.
66
end
67

68
StackFrame(func, file, line) = StackFrame(Symbol(func), Symbol(file), line,
×
69
                                          nothing, false, false, 0)
70

71
"""
72
    StackTrace
73

74
An alias for `Vector{StackFrame}` provided for convenience; returned by calls to
75
`stacktrace`.
76
"""
77
const StackTrace = Vector{StackFrame}
78

79
const empty_sym = Symbol("")
80
const UNKNOWN = StackFrame(empty_sym, empty_sym, -1, nothing, true, false, 0) # === lookup(C_NULL)
81

82

83
#=
84
If the StackFrame has function and line information, we consider two of them the same if
85
they share the same function/line information.
86
=#
87
function ==(a::StackFrame, b::StackFrame)
×
88
    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
×
89
end
90

91
function hash(frame::StackFrame, h::UInt)
×
92
    h += 0xf4fbda67fe20ce88 % UInt
×
93
    h = hash(frame.line, h)
×
94
    h = hash(frame.file, h)
×
95
    h = hash(frame.func, h)
×
96
    h = hash(frame.from_c, h)
×
97
    h = hash(frame.inlined, h)
×
98
    return h
×
99
end
100

101
"""
102
    lookup(pointer::Ptr{Cvoid}) -> Vector{StackFrame}
103

104
Given a pointer to an execution context (usually generated by a call to `backtrace`), looks
105
up stack frame context information. Returns an array of frame information for all functions
106
inlined at that point, innermost function first.
107
"""
108
Base.@constprop :none function lookup(pointer::Ptr{Cvoid})
20✔
109
    infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector
20✔
110
    pointer = convert(UInt64, pointer)
20✔
111
    isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
20✔
112
    res = Vector{StackFrame}(undef, length(infos))
20✔
113
    for i in 1:length(infos)
20✔
114
        info = infos[i]::Core.SimpleVector
24✔
115
        @assert(length(info) == 6)
24✔
116
        func = info[1]::Symbol
24✔
117
        file = info[2]::Symbol
24✔
118
        linenum = info[3]::Int
24✔
119
        linfo = info[4]
24✔
120
        res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer)
24✔
121
    end
28✔
122
    return res
20✔
123
end
124

125
const top_level_scope_sym = Symbol("top-level scope")
126

127
function lookup(ip::Base.InterpreterIP)
1✔
128
    code = ip.code
1✔
129
    if code === nothing
1✔
130
        # interpreted top-level expression with no CodeInfo
131
        return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)]
×
132
    end
133
    # prepare approximate code info
134
    if code isa MethodInstance && (meth = code.def; meth isa Method)
1✔
135
        func = meth.name
×
136
        file = meth.file
×
137
        line = meth.line
×
138
        codeinfo = meth.source
×
139
    else
140
        func = top_level_scope_sym
1✔
141
        file = empty_sym
1✔
142
        line = Int32(0)
1✔
143
        if code isa Core.CodeInstance
1✔
144
            codeinfo = code.inferred::CodeInfo
×
145
            def = code.def
×
146
            if isa(def, Core.ABIOverride)
×
147
                def = def.def
×
148
            end
149
            if isa(def, MethodInstance) && isa(def.def, Method)
×
150
                meth = def.def
×
151
                func = meth.name
×
152
                file = meth.file
×
153
                line = meth.line
×
154
            end
155
        else
156
            codeinfo = code::CodeInfo
1✔
157
        end
158
    end
159
    def = (code isa CodeInfo ? StackTraces : code) # Module just used as a token for top-level code
1✔
160
    pc::Int = max(ip.stmt + 1, 0) # n.b. ip.stmt is 0-indexed
1✔
161
    scopes = LineInfoNode[]
1✔
162
    append_scopes!(scopes, pc, codeinfo.debuginfo, def)
1✔
163
    if isempty(scopes)
1✔
164
        return [StackFrame(func, file, line, code, false, false, 0)]
×
165
    end
166
    inlined = false
1✔
167
    scopes = map(scopes) do lno
1✔
168
        if inlined
1✔
169
            def = lno.method
×
170
            def isa Union{Method,Core.CodeInstance,MethodInstance} || (def = nothing)
×
171
        else
172
            def = codeinfo
1✔
173
        end
174
        sf = StackFrame(normalize_method_name(lno.method), lno.file, lno.line, def, false, inlined, 0)
1✔
175
        inlined = true
1✔
176
        return sf
1✔
177
    end
178
    return scopes
1✔
179
end
180

181
"""
182
    stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false]) -> StackTrace
183

184
Return a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace
185
doesn't return C functions, but this can be enabled.) When called without specifying a
186
trace, `stacktrace` first calls `backtrace`.
187
"""
188
Base.@constprop :none function stacktrace(trace::Vector{<:Union{Base.InterpreterIP,Ptr{Cvoid}}}, c_funcs::Bool=false)
1✔
189
    stack = StackTrace()
11✔
190
    for ip in trace
1✔
191
        for frame in lookup(ip)
21✔
192
            # Skip frames that come from C calls.
193
            if c_funcs || !frame.from_c
50✔
194
                push!(stack, frame)
7✔
195
            end
196
        end
25✔
197
    end
21✔
198
    return stack
1✔
199
end
200

201
Base.@constprop :none function stacktrace(c_funcs::Bool=false)
×
202
    stack = stacktrace(backtrace(), c_funcs)
×
203
    # Remove frame for this function (and any functions called by this function).
204
    remove_frames!(stack, :stacktrace)
×
205
    # also remove all of the non-Julia functions that led up to this point (if that list is non-empty)
206
    c_funcs && deleteat!(stack, 1:(something(findfirst(frame -> !frame.from_c, stack), 1) - 1))
×
207
    return stack
×
208
end
209

210
"""
211
    remove_frames!(stack::StackTrace, name::Symbol)
212

213
Takes a `StackTrace` (a vector of `StackFrames`) and a function name (a `Symbol`) and
214
removes the `StackFrame` specified by the function name from the `StackTrace` (also removing
215
all frames above the specified function). Primarily used to remove `StackTraces` functions
216
from the `StackTrace` prior to returning it.
217
"""
218
function remove_frames!(stack::StackTrace, name::Symbol)
×
219
    deleteat!(stack, 1:something(findlast(frame -> frame.func == name, stack), 0))
×
220
    return stack
×
221
end
222

223
function remove_frames!(stack::StackTrace, names::Vector{Symbol})
×
224
    deleteat!(stack, 1:something(findlast(frame -> frame.func in names, stack), 0))
×
225
    return stack
×
226
end
227

228
"""
229
    remove_frames!(stack::StackTrace, m::Module)
230

231
Return the `StackTrace` with all `StackFrame`s from the provided `Module` removed.
232
"""
233
function remove_frames!(stack::StackTrace, m::Module)
×
234
    filter!(f -> !from(f, m), stack)
×
235
    return stack
×
236
end
237

238
is_top_level_frame(f::StackFrame) = f.linfo isa CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym)
×
239

240
function frame_method_or_module(lkup::StackFrame)
241
    code = lkup.linfo
88✔
242
    code isa Method && return code
88✔
243
    code isa Module && return code
×
244
    mi = frame_mi(lkup)
88✔
245
    mi isa MethodInstance || return nothing
88✔
246
    return mi.def
88✔
247
end
248

249
function frame_mi(lkup::StackFrame)
250
    code = lkup.linfo
131✔
251
    code isa Core.CodeInstance && (code = code.def)
131✔
252
    code isa Core.ABIOverride && (code = code.def)
131✔
253
    code isa MethodInstance || return nothing
131✔
254
    return code
131✔
255
end
256

257
function show_spec_linfo(io::IO, frame::StackFrame)
62✔
258
    linfo = frame.linfo
62✔
259
    if linfo === nothing
62✔
260
        if frame.func === empty_sym
11✔
261
            print(io, "ip:0x", string(frame.pointer, base=16))
×
262
        elseif frame.func === top_level_scope_sym
11✔
263
            print(io, "top-level scope")
1✔
264
        else
265
            Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
10✔
266
        end
267
    elseif linfo isa CodeInfo
51✔
268
        print(io, "top-level scope")
8✔
269
    elseif linfo isa Module
43✔
270
        Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true)
×
271
    else
272
        if linfo isa Union{MethodInstance, CodeInstance}
43✔
273
            def = frame_method_or_module(frame)
86✔
274
            if def isa Module
43✔
275
                Base.show_mi(io, linfo, #=from_stackframe=#true)
×
276
            else
277
                show_spec_sig(io, def, frame_mi(frame).specTypes)
43✔
278
            end
279
        else
280
            m = linfo::Method
×
281
            show_spec_sig(io, m, m.sig)
×
282
        end
283
    end
284
end
285

286
function show_spec_sig(io::IO, m::Method, @nospecialize(sig::Type))
43✔
287
    if get(io, :limit, :false)::Bool
86✔
288
        if !haskey(io, :displaysize)
162✔
289
            io = IOContext(io, :displaysize => displaysize(io))
43✔
290
        end
291
    end
292
    argnames = Base.method_argnames(m)
86✔
293
    argnames = replace(argnames, :var"#unused#" => :var"")
43✔
294
    if m.nkw > 0
43✔
295
        # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...; kw_args)
296
        kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+m.nkw) ]
13✔
297
        uw = Base.unwrap_unionall(sig)::DataType
3✔
298
        pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(m.nkw+2):end]...}, sig)
3✔
299
        kwnames = argnames[2:(m.nkw+1)]
6✔
300
        for i = 1:length(kwnames)
3✔
301
            str = string(kwnames[i])::String
13✔
302
            if endswith(str, "...")
13✔
303
                kwnames[i] = Symbol(str[1:end-3])
×
304
            end
305
        end
23✔
306
        Base.show_tuple_as_call(io, m.name, pos_sig;
3✔
307
                                demangle=true,
308
                                kwargs=zip(kwnames, kwarg_types),
309
                                argnames=argnames[m.nkw+2:end])
310
    else
311
        Base.show_tuple_as_call(io, m.name, sig; demangle=true, argnames)
40✔
312
    end
313
end
314

UNCOV
315
function show(io::IO, frame::StackFrame)
×
UNCOV
316
    show_spec_linfo(io, frame)
×
UNCOV
317
    if frame.file !== empty_sym
×
UNCOV
318
        file_info = basename(string(frame.file))
×
UNCOV
319
        print(io, " at ")
×
UNCOV
320
        print(io, file_info, ":")
×
UNCOV
321
        if frame.line >= 0
×
UNCOV
322
            print(io, frame.line)
×
323
        else
324
            print(io, "?")
×
325
        end
326
    end
UNCOV
327
    if frame.inlined
×
328
        print(io, " [inlined]")
×
329
    end
330
end
331

332
function Base.parentmodule(frame::StackFrame)
333
    linfo = frame.linfo
131✔
334
    if linfo isa CodeInstance
131✔
335
        linfo = linfo.def
23✔
336
        if isa(linfo, Core.ABIOverride)
23✔
337
            linfo = linfo.def
×
338
        end
339
    end
340
    if linfo isa MethodInstance
131✔
341
        def = linfo.def
91✔
342
        if def isa Module
91✔
343
            return def
×
344
        else
345
            return (def::Method).module
91✔
346
        end
347
    elseif linfo isa Method
40✔
348
        return linfo.module
×
349
    elseif linfo isa Module
40✔
350
        return linfo
×
351
    else
352
        # The module is not always available (common reasons include
353
        # frames arising from the interpreter)
354
        nothing
×
355
    end
356
end
357

358
"""
359
    from(frame::StackFrame, filter_mod::Module) -> Bool
360

361
Return whether the `frame` is from the provided `Module`
362
"""
363
function from(frame::StackFrame, m::Module)
×
364
    return parentmodule(frame) === m
×
365
end
366

367
end
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