Skip to content

Commit 643ec18

Browse files
authored
Make backtrace buffer handling more systematic (#33277)
Increase expressibility of what can be stored in backtrace buffers, while ensuring that the GC can find roots without knowing about the detail. To do this, introduce a new "extended backtrace entry" format which carries along the number of roots and other data in a bitpacked format. This allows the backtrace buffer to be traversed and the roots collected in a general way, without the GC knowing about interpreter frames. Use this to add the module to InterperterIP so that the module of interpreted top level thunks can be known. In the future the extended entry format should allow us to be a lot more flexible with what can be stored in a backtrace. For example, we could * Compress the backtrace cycles of runaway recursive functions so that stack overflows are much more likely to fit in the fixed-size bt_data array. * Integrate external or other types of interpreter frames into the backtrace machinery.
1 parent 876cd55 commit 643ec18

18 files changed

+364
-148
lines changed

Diff for: base/error.jl

+24-10
Original file line numberDiff line numberDiff line change
@@ -62,25 +62,39 @@ rethrow(e) = ccall(:jl_rethrow_other, Bottom, (Any,), e)
6262
struct InterpreterIP
6363
code::Union{CodeInfo,Core.MethodInstance,Nothing}
6464
stmt::Csize_t
65+
mod::Union{Module,Nothing}
6566
end
6667

67-
# convert dual arrays (ips, interpreter_frames) to a single array of locations
68+
# convert dual arrays (raw bt buffer, array of GC managed values) to a single
69+
# array of locations
6870
function _reformat_bt(bt, bt2)
6971
ret = Vector{Union{InterpreterIP,Ptr{Cvoid}}}()
7072
i, j = 1, 1
7173
while i <= length(bt)
7274
ip = bt[i]::Ptr{Cvoid}
73-
if UInt(ip) == (-1 % UInt)
74-
# The next one is really a CodeInfo
75-
push!(ret, InterpreterIP(
76-
bt2[j],
77-
bt[i+2]))
78-
j += 1
79-
i += 3
80-
else
81-
push!(ret, Ptr{Cvoid}(ip))
75+
if UInt(ip) != (-1 % UInt) # See also jl_bt_is_native
76+
# native frame
77+
push!(ret, ip)
8278
i += 1
79+
continue
80+
end
81+
# Extended backtrace entry
82+
entry_metadata = reinterpret(UInt, bt[i+1])
83+
njlvalues = entry_metadata & 0x7
84+
nuintvals = (entry_metadata >> 3) & 0x7
85+
tag = (entry_metadata >> 6) & 0xf
86+
header = entry_metadata >> 10
87+
if tag == 1 # JL_BT_INTERP_FRAME_TAG
88+
code = bt2[j]
89+
mod = njlvalues == 2 ? bt2[j+1] : nothing
90+
push!(ret, InterpreterIP(code, header, mod))
91+
else
92+
# Tags we don't know about are an error
93+
throw(ArgumentError("Unexpected extended backtrace entry tag $tag at bt[$i]"))
8394
end
95+
# See jl_bt_entry_size
96+
j += njlvalues
97+
i += Int(2 + njlvalues + nuintvals)
8498
end
8599
ret
86100
end

Diff for: base/errorshow.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ end
705705
function show(io::IO, ip::InterpreterIP)
706706
print(io, typeof(ip))
707707
if ip.code isa Core.CodeInfo
708-
print(io, " in top-level CodeInfo at statement $(Int(ip.stmt))")
708+
print(io, " in top-level CodeInfo for $(ip.mod) at statement $(Int(ip.stmt))")
709709
else
710710
print(io, " in $(ip.code) at statement $(Int(ip.stmt))")
711711
end

Diff for: base/stacktraces.jl

+8-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module StackTraces
77

88

99
import Base: hash, ==, show
10+
import Core: CodeInfo, MethodInstance
1011

1112
export StackTrace, StackFrame, stacktrace
1213

@@ -52,7 +53,7 @@ struct StackFrame # this type should be kept platform-agnostic so that profiles
5253
"the line number in the file containing the execution context"
5354
line::Int
5455
"the MethodInstance or CodeInfo containing the execution context (if it could be found)"
55-
linfo::Union{Core.MethodInstance, Core.CodeInfo, Nothing}
56+
linfo::Union{MethodInstance, CodeInfo, Nothing}
5657
"true if the code is from C"
5758
from_c::Bool
5859
"true if the code is from an inlined frame"
@@ -118,7 +119,7 @@ end
118119
const top_level_scope_sym = Symbol("top-level scope")
119120

120121
function lookup(ip::Base.InterpreterIP)
121-
if ip.code isa Core.MethodInstance && ip.code.def isa Method
122+
if ip.code isa MethodInstance && ip.code.def isa Method
122123
codeinfo = ip.code.uninferred
123124
func = ip.code.def.name
124125
file = ip.code.def.file
@@ -127,7 +128,7 @@ function lookup(ip::Base.InterpreterIP)
127128
# interpreted top-level expression with no CodeInfo
128129
return [StackFrame(top_level_scope_sym, empty_sym, 0, nothing, false, false, 0)]
129130
else
130-
@assert ip.code isa Core.CodeInfo
131+
@assert ip.code isa CodeInfo
131132
codeinfo = ip.code
132133
func = top_level_scope_sym
133134
file = empty_sym
@@ -206,7 +207,7 @@ function remove_frames!(stack::StackTrace, m::Module)
206207
return stack
207208
end
208209

209-
is_top_level_frame(f::StackFrame) = f.linfo isa Core.CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym)
210+
is_top_level_frame(f::StackFrame) = f.linfo isa CodeInfo || (f.linfo === nothing && f.func === top_level_scope_sym)
210211

211212
function show_spec_linfo(io::IO, frame::StackFrame)
212213
if frame.linfo === nothing
@@ -220,7 +221,7 @@ function show_spec_linfo(io::IO, frame::StackFrame)
220221
:nothing
221222
printstyled(io, Base.demangle_function_name(string(frame.func)), color=color)
222223
end
223-
elseif frame.linfo isa Core.MethodInstance
224+
elseif frame.linfo isa MethodInstance
224225
def = frame.linfo.def
225226
if isa(def, Method)
226227
sig = frame.linfo.specTypes
@@ -243,7 +244,7 @@ function show_spec_linfo(io::IO, frame::StackFrame)
243244
else
244245
Base.show(io, frame.linfo)
245246
end
246-
elseif frame.linfo isa Core.CodeInfo
247+
elseif frame.linfo isa CodeInfo
247248
print(io, "top-level scope")
248249
end
249250
end
@@ -276,7 +277,7 @@ function from(frame::StackFrame, m::Module)
276277
finfo = frame.linfo
277278
result = false
278279

279-
if finfo isa Core.MethodInstance
280+
if finfo isa MethodInstance
280281
frame_m = finfo.def
281282
isa(frame_m, Method) && (frame_m = frame_m.module)
282283
result = nameof(frame_m) === nameof(m)

Diff for: src/gc.c

+35-26
Original file line numberDiff line numberDiff line change
@@ -2059,34 +2059,41 @@ excstack: {
20592059
gc_mark_excstack_t *stackitr = gc_pop_markdata(&sp, gc_mark_excstack_t);
20602060
jl_excstack_t *excstack = stackitr->s;
20612061
size_t itr = stackitr->itr;
2062-
size_t i = stackitr->i;
2062+
size_t bt_index = stackitr->bt_index;
2063+
size_t jlval_index = stackitr->jlval_index;
20632064
while (itr > 0) {
20642065
size_t bt_size = jl_excstack_bt_size(excstack, itr);
2065-
uintptr_t *bt_data = jl_excstack_bt_data(excstack, itr);
2066-
while (i+2 < bt_size) {
2067-
if (bt_data[i] != JL_BT_INTERP_FRAME) {
2068-
i++;
2066+
jl_bt_element_t *bt_data = jl_excstack_bt_data(excstack, itr);
2067+
for (; bt_index < bt_size; bt_index += jl_bt_entry_size(bt_data + bt_index)) {
2068+
jl_bt_element_t *bt_entry = bt_data + bt_index;
2069+
if (jl_bt_is_native(bt_entry))
20692070
continue;
2070-
}
2071-
// found an interpreter frame to mark
2072-
new_obj = (jl_value_t*)bt_data[i+1];
2073-
uintptr_t nptr = 0;
2074-
i += 3;
2075-
if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) {
2076-
stackitr->i = i;
2077-
stackitr->itr = itr;
2078-
gc_repush_markdata(&sp, gc_mark_excstack_t);
2079-
goto mark;
2071+
// Found an extended backtrace entry: iterate over any
2072+
// GC-managed values inside.
2073+
size_t njlvals = jl_bt_num_jlvals(bt_entry);
2074+
while (jlval_index < njlvals) {
2075+
new_obj = jl_bt_entry_jlvalue(bt_entry, jlval_index);
2076+
uintptr_t nptr = 0;
2077+
jlval_index += 1;
2078+
if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) {
2079+
stackitr->itr = itr;
2080+
stackitr->bt_index = bt_index;
2081+
stackitr->jlval_index = jlval_index;
2082+
gc_repush_markdata(&sp, gc_mark_excstack_t);
2083+
goto mark;
2084+
}
20802085
}
20812086
}
2082-
// mark the exception
2087+
// The exception comes last - mark it
20832088
new_obj = jl_excstack_exception(excstack, itr);
20842089
itr = jl_excstack_next(excstack, itr);
2085-
i = 0;
2090+
bt_index = 0;
2091+
jlval_index = 0;
20862092
uintptr_t nptr = 0;
20872093
if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) {
2088-
stackitr->i = i;
20892094
stackitr->itr = itr;
2095+
stackitr->bt_index = bt_index;
2096+
stackitr->jlval_index = jlval_index;
20902097
gc_repush_markdata(&sp, gc_mark_excstack_t);
20912098
goto mark;
20922099
}
@@ -2359,7 +2366,7 @@ mark: {
23592366
if (ta->excstack) {
23602367
gc_setmark_buf_(ptls, ta->excstack, bits, sizeof(jl_excstack_t) +
23612368
sizeof(uintptr_t)*ta->excstack->reserved_size);
2362-
gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0};
2369+
gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0, 0};
23632370
gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack),
23642371
&stackdata, sizeof(stackdata), 1);
23652372
}
@@ -2654,13 +2661,15 @@ static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp
26542661

26552662
static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2)
26562663
{
2657-
size_t n = 0;
2658-
while (n+2 < ptls2->bt_size) {
2659-
if (ptls2->bt_data[n] == JL_BT_INTERP_FRAME) {
2660-
gc_mark_queue_obj(gc_cache, sp, (jl_value_t*)ptls2->bt_data[n+1]);
2661-
n += 2;
2662-
}
2663-
n++;
2664+
jl_bt_element_t *bt_data = ptls2->bt_data;
2665+
size_t bt_size = ptls2->bt_size;
2666+
for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) {
2667+
jl_bt_element_t *bt_entry = bt_data + i;
2668+
if (jl_bt_is_native(bt_entry))
2669+
continue;
2670+
size_t njlvals = jl_bt_num_jlvals(bt_entry);
2671+
for (size_t j = 0; j < njlvals; j++)
2672+
gc_mark_queue_obj(gc_cache, sp, jl_bt_entry_jlvalue(bt_entry, j));
26642673
}
26652674
}
26662675

Diff for: src/gc.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ typedef struct {
153153

154154
// Exception stack data
155155
typedef struct {
156-
jl_excstack_t *s; // Stack of exceptions
157-
size_t itr; // Iterator into exception stack
158-
size_t i; // Iterator into backtrace data for exception
156+
jl_excstack_t *s; // Stack of exceptions
157+
size_t itr; // Iterator into exception stack
158+
size_t bt_index; // Current backtrace buffer entry index
159+
size_t jlval_index; // Index into GC managed values for current bt entry
159160
} gc_mark_excstack_t;
160161

161162
// Module bindings. This is also the beginning of module scanning.

Diff for: src/interpreter-stacktrace.c

+18-8
Original file line numberDiff line numberDiff line change
@@ -390,20 +390,29 @@ JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip)
390390
return enter_interpreter_frame_start <= ip && ip <= enter_interpreter_frame_end;
391391
}
392392

393-
JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining)
393+
JL_DLLEXPORT size_t jl_capture_interp_frame(jl_bt_element_t *bt_entry, uintptr_t sp,
394+
uintptr_t fp, size_t space_remaining)
394395
{
395396
#ifdef FP_CAPTURE_OFFSET
396397
interpreter_state *s = (interpreter_state *)(fp-FP_CAPTURE_OFFSET);
397398
#else
398399
interpreter_state *s = (interpreter_state *)(sp+TOTAL_STACK_PADDING);
399400
#endif
400-
int required_space = 3;
401+
int need_module = !s->mi;
402+
int required_space = need_module ? 4 : 3;
401403
if (space_remaining < required_space)
402-
return 0;
403-
// Sentinel value to indicate an interpreter frame
404-
data[0] = JL_BT_INTERP_FRAME;
405-
data[1] = s->mi ? (uintptr_t)s->mi : s->src ? (uintptr_t)s->src : (uintptr_t)jl_nothing;
406-
data[2] = (uintptr_t)s->ip;
404+
return 0; // Should not happen
405+
size_t njlvalues = need_module ? 2 : 1;
406+
uintptr_t entry_tags = jl_bt_entry_descriptor(njlvalues, 0, JL_BT_INTERP_FRAME_TAG, s->ip);
407+
bt_entry[0].uintptr = JL_BT_NON_PTR_ENTRY;
408+
bt_entry[1].uintptr = entry_tags;
409+
bt_entry[2].jlvalue = s->mi ? (jl_value_t*)s->mi :
410+
s->src ? (jl_value_t*)s->src : (jl_value_t*)jl_nothing;
411+
if (need_module) {
412+
// If we only have a CodeInfo (s->src), we are in a top level thunk and
413+
// need to record the module separately.
414+
bt_entry[3].jlvalue = (jl_value_t*)s->module;
415+
}
407416
return required_space;
408417
}
409418

@@ -419,7 +428,8 @@ JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip)
419428
return 0;
420429
}
421430

422-
JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining)
431+
JL_DLLEXPORT size_t jl_capture_interp_frame(jl_bt_element_t *bt_entry, uintptr_t sp,
432+
uintptr_t fp, size_t space_remaining)
423433
{
424434
// Leave bt_entry[0] as the native instruction ptr
425435
return 0;

Diff for: src/julia.h

+7
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ typedef struct _jl_llvm_functions_t {
236236

237237
typedef struct _jl_method_instance_t jl_method_instance_t;
238238

239+
typedef struct _jl_line_info_node_t {
240+
jl_value_t *method;
241+
jl_sym_t *file;
242+
intptr_t line;
243+
intptr_t inlined_at;
244+
} jl_line_info_node_t;
245+
239246
// This type describes a single function body
240247
typedef struct _jl_code_info_t {
241248
// ssavalue-indexed arrays of properties:

0 commit comments

Comments
 (0)