Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: stacktrace: enable method signature printing for inlined frames #53925

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions base/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,35 @@ up stack frame context information. Returns an array of frame information for al
inlined at that point, innermost function first.
"""
Base.@constprop :none function lookup(pointer::Ptr{Cvoid})
infos = ccall(:jl_lookup_code_address, Any, (Ptr{Cvoid}, Cint), pointer, false)::Core.SimpleVector
infos = @ccall jl_lookup_code_address(pointer::Ptr{Cvoid}, false::Cint)::Core.SimpleVector
pointer = convert(UInt64, pointer)
isempty(infos) && return [StackFrame(empty_sym, empty_sym, -1, nothing, true, false, pointer)] # this is equal to UNKNOWN
res = Vector{StackFrame}(undef, length(infos))
for i in 1:length(infos)
ninfos = length(infos)
res = Vector{StackFrame}(undef, ninfos)
local debuginfo = false
for i = ninfos:-1:1
info = infos[i]::Core.SimpleVector
@assert(length(info) == 6)
func = info[1]::Symbol
file = info[2]::Symbol
linenum = info[3]::Int
linfo = info[4]
if linfo isa Core.CodeInstance
if debuginfo === false
debuginfo = linfo.debuginfo
else
debuginfo = true
end
linfo = linfo.def
elseif debuginfo isa Core.DebugInfo
# TODO lookup a program counter `pc` at the parent frame and use `buildLineInfoNode(debuginfo, nothing, pc)`
Copy link
Member Author

Choose a reason for hiding this comment

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

One clear issue of the current implementation is we can't pinpoint the program counter for the outermost frame, making buildLineInfo unusable and causing lookups to fail except in the simplest cases. Is there a way, within lookup(pointer::Ptr{Cvoid}), to get a pc to feed into Base.IRShow.buildLineInfo(codeinst.debuginfo, nothing, pc)?

if length(debuginfo.edges) == 1 # XXX
debuginfo = debuginfo.edges[1]
linfo = debuginfo.def
else
debuginfo = true
end
end
res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer)
end
return res
Expand Down
2 changes: 1 addition & 1 deletion src/codegen-stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ JL_DLLEXPORT int jl_getFunctionInfo_fallback(jl_frame_t **frames, uintptr_t poin
}

JL_DLLEXPORT void jl_register_fptrs_fallback(uint64_t image_base, const struct _jl_image_fptrs_t *fptrs,
jl_method_instance_t **linfos, size_t n)
jl_code_instance_t **linfos, size_t n)
{
(void)image_base; (void)fptrs; (void)linfos; (void)n;
}
Expand Down
6 changes: 3 additions & 3 deletions src/debug-registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class JITDebugInfoRegistry
struct image_info_t {
uint64_t base;
jl_image_fptrs_t fptrs;
jl_method_instance_t **fvars_linfo;
jl_code_instance_t **fvars_linfo;
size_t fvars_n;
};

Expand Down Expand Up @@ -114,7 +114,7 @@ class JITDebugInfoRegistry
typedef rev_map<uint64_t, objfileentry_t> objfilemap_t;

objectmap_t objectmap{};
rev_map<size_t, std::pair<size_t, jl_method_instance_t *>> linfomap{};
rev_map<size_t, std::pair<size_t, jl_code_instance_t *>> linfomap{};

// Maintain a mapping of unrealized function names -> linfo objects
// so that when we see it get emitted, we can add a link back to the linfo
Expand All @@ -135,7 +135,7 @@ class JITDebugInfoRegistry
libc_frames_t libc_frames{};

void add_code_in_flight(llvm::StringRef name, jl_code_instance_t *codeinst, const llvm::DataLayout &DL) JL_NOTSAFEPOINT;
jl_method_instance_t *lookupLinfo(size_t pointer) JL_NOTSAFEPOINT;
jl_code_instance_t *lookupLinfo(size_t pointer) JL_NOTSAFEPOINT;
void registerJITObject(const llvm::object::ObjectFile &Object,
std::function<uint64_t(const llvm::StringRef &)> getLoadAddress,
std::function<void*(void*)> lookupWriteAddress);
Expand Down
18 changes: 8 additions & 10 deletions src/debuginfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ void JITDebugInfoRegistry::add_code_in_flight(StringRef name, jl_code_instance_t
(**codeinst_in_flight)[mangle(name, DL)] = codeinst;
}

jl_method_instance_t *JITDebugInfoRegistry::lookupLinfo(size_t pointer)
jl_code_instance_t *JITDebugInfoRegistry::lookupLinfo(size_t pointer)
{
jl_lock_profile();
auto region = linfomap.lower_bound(pointer);
jl_method_instance_t *linfo = NULL;
jl_code_instance_t *linfo = NULL;
if (region != linfomap.end() && pointer < region->first + region->second.first)
linfo = region->second.second;
jl_unlock_profile();
Expand Down Expand Up @@ -372,19 +372,17 @@ void JITDebugInfoRegistry::registerJITObject(const object::ObjectFile &Object,
codeinst_in_flight.erase(codeinst_it);
}
}
jl_method_instance_t *mi = NULL;
if (codeinst) {
JL_GC_PROMISE_ROOTED(codeinst);
mi = codeinst->def;
// Non-opaque-closure MethodInstances are considered globally rooted
// through their methods, but for OC, we need to create a global root
// here.
if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure)
mi = (jl_method_instance_t*)jl_as_global_root((jl_value_t*)mi, 1);
if (jl_is_method(codeinst->def->def.value) && codeinst->def->def.method->is_for_opaque_closure)
codeinst = (jl_code_instance_t*)jl_as_global_root((jl_value_t*)codeinst, 1);
}
jl_profile_atomic([&]() JL_NOTSAFEPOINT {
if (mi)
linfomap[Addr] = std::make_pair(Size, mi);
if (codeinst)
linfomap[Addr] = std::make_pair(Size, codeinst);
if (first) {
objectmap[SectionLoadAddr] = {&Object,
(size_t)SectionSize,
Expand Down Expand Up @@ -699,7 +697,7 @@ openDebugInfo(StringRef debuginfopath, const debug_link_info &info) JL_NOTSAFEPO
}
extern "C" JL_DLLEXPORT_CODEGEN
void jl_register_fptrs_impl(uint64_t image_base, const jl_image_fptrs_t *fptrs,
jl_method_instance_t **linfos, size_t n)
jl_code_instance_t **linfos, size_t n)
{
getJITDebugRegistry().add_image_info({(uintptr_t) image_base, *fptrs, linfos, n});
}
Expand Down Expand Up @@ -1249,7 +1247,7 @@ extern "C" JL_DLLEXPORT_CODEGEN int jl_getFunctionInfo_impl(jl_frame_t **frames_

extern "C" jl_method_instance_t *jl_gdblookuplinfo(void *p) JL_NOTSAFEPOINT
{
return getJITDebugRegistry().lookupLinfo((size_t)p);
return getJITDebugRegistry().lookupLinfo((size_t)p)->def;
}

#if defined(_OS_DARWIN_) && defined(LLVM_SHLIB)
Expand Down
4 changes: 2 additions & 2 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ typedef struct {
char *func_name;
char *file_name;
int line;
jl_method_instance_t *linfo;
jl_code_instance_t *linfo;
int fromC;
int inlined;
} jl_frame_t;
Expand Down Expand Up @@ -1751,7 +1751,7 @@ JL_DLLIMPORT void jl_get_llvm_external_fns(void *native_code, arraylist_t *gvs);
JL_DLLIMPORT void jl_get_function_id(void *native_code, jl_code_instance_t *ncode,
int32_t *func_idx, int32_t *specfunc_idx);
JL_DLLIMPORT void jl_register_fptrs(uint64_t image_base, const struct _jl_image_fptrs_t *fptrs,
jl_method_instance_t **linfos, size_t n);
jl_code_instance_t **linfos, size_t n);

JL_DLLIMPORT void jl_init_codegen(void);
JL_DLLIMPORT void jl_teardown_codegen(void) JL_NOTSAFEPOINT;
Expand Down
4 changes: 2 additions & 2 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -2162,7 +2162,7 @@ static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image)
uintptr_t base = (uintptr_t)&s->s->buf[0];
// These will become MethodInstance references, but they start out as a list of
// offsets into `s` for CodeInstances
jl_method_instance_t **linfos = (jl_method_instance_t**)&s->fptr_record->buf[0];
jl_code_instance_t **linfos = (jl_code_instance_t**)&s->fptr_record->buf[0];
uint32_t clone_idx = 0;
for (i = 0; i < img_fvars_max; i++) {
reloc_t offset = *(reloc_t*)&linfos[i];
Expand All @@ -2177,7 +2177,7 @@ static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image)
jl_code_instance_t *codeinst = (jl_code_instance_t*)(base + offset);
assert(jl_is_method(codeinst->def->def.method) && jl_atomic_load_relaxed(&codeinst->invoke) != jl_fptr_const_return);
assert(specfunc ? jl_atomic_load_relaxed(&codeinst->invoke) != NULL : jl_atomic_load_relaxed(&codeinst->invoke) == NULL);
linfos[i] = codeinst->def; // now it's a MethodInstance
linfos[i] = codeinst; // now it's a MethodInstance
void *fptr = fvars.ptrs[i];
for (; clone_idx < fvars.nclones; clone_idx++) {
uint32_t idx = fvars.clone_idxs[clone_idx] & jl_sysimg_val_mask;
Expand Down
64 changes: 61 additions & 3 deletions test/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ can_inline = Bool(Base.JLOptions().can_inline)
for (frame, func, inlined) in zip(trace, [g,h,f], (can_inline, can_inline, false))
@test frame.func === typeof(func).name.mt.name
# broken until #50082 can be addressed
@test frame.linfo.def.module === which(func, (Any,)).module broken=inlined
@test frame.linfo.def === which(func, (Any,)) broken=inlined
@test frame.linfo.specTypes === Tuple{typeof(func), Int} broken=inlined
@test frame.linfo.def.module === which(func, (Any,)).module
@test frame.linfo.def === which(func, (Any,))
@test frame.linfo.specTypes === Tuple{typeof(func), Int}
# line
@test frame.file === Symbol(@__FILE__)
@test !frame.from_c
Expand Down Expand Up @@ -266,3 +266,61 @@ end
@testset "Base.StackTraces docstrings" begin
@test isempty(Docs.undocumented_names(StackTraces))
end

global f_parent1_line::Int, f_inner1_line::Int, f_innermost1_line::Int
function f_parent1(a)
x = a
return begin
@inline f_inner1(x)
end
end; f_parent1_line = (@__LINE__) - 2
function f_inner1(a)
x = a
return @inline f_innermost1(x)
end; f_inner1_line = (@__LINE__) - 1
f_innermost1(x) = x > 0 ? @noinline(sin(x)) : error("x is negative")
f_innermost1_line = (@__LINE__) - 1
let st = try
f_parent1(-1)
catch err
stacktrace(catch_backtrace())
end
@test any(st) do sf
sf.func === :f_parent1 && sf.line == f_parent1_line && sf.linfo isa Core.MethodInstance
end
@test any(st) do sf
sf.func === :f_inner1 && sf.line == f_inner1_line && sf.linfo isa Core.MethodInstance && sf.inlined
end
@test any(st) do sf
sf.func === :f_innermost1 && sf.line == f_innermost1_line && sf.linfo isa Core.MethodInstance && sf.inlined
end
end

global f_parent2_line::Int, f_inner2_line::Int, f_innermost2_line::Int
function f_parent2(a)
x = identity(a)
return begin
@inline f_inner2(x)
end
end; f_parent2_line = (@__LINE__) - 2
function f_inner2(a)
x = identity(a)
return @inline f_innermost2(x)
end; f_inner2_line = (@__LINE__) - 1
f_innermost2(x) = x > 0 ? @noinline(sin(x)) : error("x is negative")
f_innermost2_line = (@__LINE__) - 1
let st = try
f_parent2(-1)
catch err
stacktrace(catch_backtrace())
end
@test any(st) do sf
sf.func === :f_parent2 && sf.line == f_parent2_line && sf.linfo isa Core.MethodInstance
end
@test_broken any(st) do sf
sf.func === :f_inner2 && sf.line == f_inner2_line && sf.linfo isa Core.MethodInstance && sf.inlined
end
@test_broken any(st) do sf
sf.func === :f_innermost2 && sf.line == f_innermost2_line && sf.linfo isa Core.MethodInstance && sf.inlined
end
end