dwblaikie wrote:
> Ah there's a detail about our debugger that turns out I was a bit out of date
> on. At load time the debugger makes a note of function symbols with the same
> address as ICF functions. Stopped in a function, the debugger knows whether
> the function has been involved in ICF without looking at DWARF. But it does
> need the DWARF call site information to determine whether the current
> function was the original callee (ICF "survivor") or not (ICF folded-away).
> _That_ is how call site information is used in relation to ICF in our
> debugger.
>
> > Rather than trying to detect tail calls - can't you rely the call_site to
> > tell you if it's a tail call?
>
> Yes and no. The call site info in the parent frame describes the call to the
> function that does the tail call, not the tail-called function:
>
> ```
> Call graph:
> a() --direct-call-> b() --tail-call-> c()
>
> Stopped in c() our call stack is:
> c()
> a() - Call site: DW_AT_call_origin('b')
> ```
>
> If we ignore analyising the call graph for a moment, there's no way to get at
> the call site info of the call to `c()` in `b()` to see the tail call
> attribute. But yes, if we analyse the call graph we can in some cases work it
> out, though not all (e.g. ambiguous paths).
>
> > until you have a graph from caller to callee following only tail call edges
> > and check that the callee appears nowhere else in that graph
>
> It turns out that our debugger does actually do this kind of call graph
> analysis to reconstruct tail-called frames.
>
> Adding virtual_call_origin lets the debugger determine whether there has been
> a tail call for virtual calls. It's not completely watertight, because it
> fails in some cases such as:
>
> ```
> Call graph:
> a() --virtual-call-> b() --tail-call-> Base::b()
>
> Call stack:
> Base::b()
> a() - Call site: DW_AT_virtual_call_origin('b')
> ```
>
> Here the debugger is going to assume `Base::b` was the intended call target,
> and report no tail call frames. Our debugger folks are happy with the trade
> off for the coverage gained.
Would `DW_AT_call_target` in `a()` tell you the real function being called (the
address of `SomeDerived::b` - might have to jump through some thunks to get
there) - and thus cover this case? (& others, without the need for
`DW_AT_virtual_call_origin`)
> Both ICF and entry value reconstruction are relevant here but I think maybe
> we can think of them separetely. The parent-frame-call-site-info to
> current-frame mismatch can occur due to ICF or tail calls. But the debugger
> already knows whether the current frame has been involved in ICF (see top of
> this comment), and I think all of this comes down to trying to work out if
> there's been a tail call.
>
> > > I wonder, does this only help you detect ICF specifically on the first
> > > step into a call? I can't picture how this would work if you paused
> > > execution in the middle of the code-folded function.
> > > because you could differentiate between the start of the function you're
> > > in and the caller's call_site's call_target
>
> Oh right, ok that makes sense. So it works the same way and runs into the
> same issues with tail calls as direct/static calls do. Right I think I'm
> grasping the trade off here (ignoring performance/code size). The NOP-sled
> implementation tells you which function in the ICF folded group was
> originally called, but doesn't work if there's a tail call to the current
> frame. The current approach using the sybmol table indicates there's been ICF
> but not which function was orignally called, unless the callee is the the IFC
> "survivor". That logic is independant from the current call stack and DWARF,
> so doesn't get confused by tail calls. Going further, maybe these are these
> composable?
>
> I've sent us round in circle a bit here, sorry about that.
>
> To sumarise:
>
> * The debugger wants to understand whether there's been a tail call to the
> current function.
> * It can construct a tail-call-path between the parent frame and current
> frame today unless there's any indirect calls.
Why can't it reconstruct this in the face of indirect calls - if those calls
include `DW_AT_call_target` that resolves to the address of the
> * We're proposing adding DW_AT_virtual_call_origin so the tail call graph can
> be constructed (imperfectly) in the presence of virtual calls, which is
> probably going to be a significant portion of indirect calls in a C++
> codebase.
Perhaps this is the place we should focus - concrete examples of what is/isn't
possible help me feel a bit more grounded in the conversation - sorry for any
other tangents/misleading statements, etc.
So this is the `a() --virtual-call-> b() --tail-call-> Base::b()` example
above? in this case, if `a` is non-tail-calling `SomeDerived::b`, the
`DW_AT_call_target` should have `SomeDerived::b` in it - so you could use that
to determine that `Base::b` wasn't the real target, and so there's been a tail
call here somewhere (& with the call graph analysis, if `SomeDerived::b` only
has one tail call chain that leads to `Base::b`, then you can reconstruct the
call stack correctly - and if there's more than one such chain or possible
chain (tail dynamic call sites, etc) then you bail out and say there could be
missing frames here)
> The NOP-sleds may be composable with rather than a replacement for what we're
> doing. However, either way, I think we'd need to understand the
> size/performance costs to be able to make a call on their use, and don't
> think we're able to tackle that investigation right away.
Understandable - though I'm not sure I've quite understood the
composability/orthogonality yet - but that's on me & probably best set aside
for now - I'll take your word for it.
https://github.com/llvm/llvm-project/pull/167666
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits