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

Reply via email to