[llvm-dev] RFC: [DebugInfo] Improving Debug Information in LLVM to Recover Optimized-out Function Parameters

Francis Visoiu Mistrih via llvm-dev llvm-dev at lists.llvm.org
Wed Feb 13 20:44:23 PST 2019


[+ Quentin]

Sorry for the late reply.

> On Feb 13, 2019, at 9:09 AM, Nikola Prica <nikola.prica at rt-rk.com> wrote:
> On 12.02.2019. 18:06, Adrian Prantl wrote:
>> [+ some folks more knowledgable about the Machine layer than me.]
> That would be useful for us too! :)
>>> On Feb 12, 2019, at 5:07 AM, Nikola Prica <nikola.prica at rt-rk.com> wrote:
>>> Hi,
>>> I am one of the authors of this feature. On Phabricator, we agreed to
>>> take discussion whether encoding this in IR and threading it through the
>>> compiler or performing a late MIR analysis is the better approach.
>>> Regarding late MIR pass approach, it would need to go back from call
>>> instruction by recognizing parameter's forwarding instructions and
>>> interpret them. We could interpret register moves, immediate moves and
>>> some of stack object loadings. There would be need to write
>>> interpretation of various architectures instructions. We were not
>>> positive about completeness of such recognition.
>> So you're saying that in late MIR, the target-specific MachineInstructions don't have enough generic meta information to understand where data was copied/loaded from in a target-independent way? Would it be easier in earlier pre-regalloc MIR, or does that have the same problem because the instructions are already target-specific?
> It has enough generic meta information for some kind of instructions but
> not for all. MachineInstr has bits for isMoveImm, isMoveReg and mayLoad
> that can be useful for recognizing some kind of parameter loading
> instructions. But we're not quite sure whether it is enough for
> recognizing all of them. For example there is no support for recognizing
> X86::LEA instructions with such mechanism (and there is significant
> number of such parameter loading instructions). This instruction made us
> give up with late MIR pass approach because we were not sure about
> various architectures complexities. As a result from tacking it from
> ISEL phase we were able to get entry values from some LEA instructions.
> This is very important for this approach. Currently, for various
> architectures, this approach gives us more information about values
> loaded into parameter forwarding registers than late MIR pass would give
> use (because of lack of special instruction interpretation).

I discussed this with Jessica offline today, and all this seems correct. We don’t have enough generic information to gather this in MIR. I agree that taking the MIR approach sounds attractive since we don’t have to carry all the debug instructions around, but it seems to me that a lot of these would either need special handling for every instruction that has no metadata (in this case instructions like LEA).

> But nevertheless, in the end, we lose some information through the
> optimization pipeline, and in order to salvage some information we
> implemented target specific machine instruction interpreter. For example
> situations like:
> %vreg = LEA <smt>
> $rdi = COPY %vreg
> call foo
> *DBG_CALLSITEPARAM $rdi, , %vreg
> ==== replace %vreg with $rdi ====
> %rdi = LEA <smt>
> $rdi = COPY %rdi
> call foo
> *DBG_CALLSITEPARAM $rdi, , %rdi
> ==== delete redudant instruction identities ====
> %rdi = LEA <smt>
> call foo
> *DBG_CALLSITEPARAM $rdi, , %rdi
> In order to salvage this we go backward from call instruction and try to
> interpret instruction that loads value to $rdi.
> This salvaging part could be used for interpreting in late MIR pass but
> it would need extension for other architecture specific instructions.
> But with current approach that starts tracking parameter forwarding
> instructions from ISEL phase we are able to cover some of them without
> such interpretor.

I don’t have an opinion on this, but it sounds like this is going to grow into the full-MIR solution that Adrian was suggesting from the beginning.

> ISEL phase is important for matching DICallSiteParam metadata to
> respective DBG_CALLSITEPARAM. It also recognizes COPY instructions that
> are part of calling sequence but do not forward any argument (for
> example for variadic, C calling convention, functions we have copy to AL
> register). If we are able to dispatch such non transferring argument
> copy instructions from calling convention and we potentially drop IR
> metadata about call site parameters, we might be able to do all
> necessary parameter's tracking in some separate MIR pass (not very late,
> somewhere after ISEL).
>>> However, such analysis
>>> might not be complete as current approach. It would not be able to
>>> produce ‘DW_OP_entry_values’ in ‘DW_TAG_call_site_value’ expression of
>>> call site parameter as a late pass.
>>> As example think of callee function that forwards its argument in
>>> function call in entry block and never uses that argument again:
>>> %vreg = COPY $rsi;             ->
>>> …. <no use of $rsi nor %vreg>  ->   …<no use of $rsi nor %vreg>
>>> $rsi = COPY $vreg;             ->   call foo
>>> call foo                       ->
>> I'm not sure I can follow this example (yet). Is this about creating a call site parameter for an argument of the call to foo, or is it meant to illustrate that the call to foo() makes it hard to write a backwards analysis for inserting a call site parameter for a call site below the call to foo()?
>>>> This is the case from ‘VirtRegMap’ pass, but I think it can happen
>>> elsewhere.
>> As I'm not super familiar with the various MIR passes: Is VirtRegMap the pass inserting the vreg copy from $rsi here? Does it do something else?
> Oh, I didn't explain it fully. In virtual register rewriter, for
> previous example %vreg gets replaced with $rsi and we get two 'identity
> copies' ($rsi = COPY $rsi) that get deleted. Such situation is special
> for call at entry block whose argument is callee's argument that is used
> only at that place. For example like:
> baa(int a) {
>  foo(a);
>  <code that does not use 'a' variable>;
> }
> Variable 'a' is dead after 'foo' call and there is no interest in
> preserving it in further flow of function. At that point we lose
> information about parameter forwarding instruction. No instruction, no
> way to interpret it. In order to track such situations we need
> DBG_CALLSITEPARAM instructions to track parameter transferring register
> through the backend. For situations like this we use DICallSiteParam in
> order to find it in previous frame. One late pass wont do the trick.
> Call at entry block that implicitly re-forwards argument is special
> situation and can possibly be handled with emitting DW_OP_entry_value
> for call site parameter value expression.
> Also, it is worth mentioning that for situations where we could have
> call of 'foo(a)' nested at some machine block, parameter loading
> instruction can be in different block than the call of 'foo(a)'. Such
> situations would not be handled so easily by late pass.
>>> Recreation of this might be possible when function ‘foo’ is
>>> in current compilation module, but we are not sure if it is possible for
>>> external modules calls. In order to follow such cases we need
>>> DBG_CALLSITEPARAM that can track such situation.
>> What information exactly would you need about foo's implementation that you cannot get from just knowing the calling convention?
> Having in mind previous example where we have just call of fucntion foo,
> we would need to know how many arguments does 'foo' have and through
> which registers are they forwarded. I'm not sure how would we get such
> information?

I believe you would have to use the IR Function attached to the MachineFunction and the target calling convention generated from TableGen. This is what targets already implement in [Target]ISelLowering::LowerCall.



>>> Since after ISEL phase we have explicit pseudo COPY instructions that
>>> forward argument to another function frame, it came naturally to
>>> recognize such instructions at this stage. There we can say with 100%
>>> certainty that those instruction indeed forward function arguments.
>> My main motivation for this discussion is to minimize the added complexity of the feature. For example, if we figure out that we can get by without introducing new IR constructs (that optimization authors would need to be taught about, that would need to be supported in all three instruction selectors) and can get by with only adding new MIR instructions, that would be a win. However, if we can prove that deferring the analysis to a later stage would result in inferior quality then the extra maintenance burden of new IR constructs may be the right tradeoff.
>>> Thanks for taking the time to walk me through your thought process.
>> -- adrian
> In general we use DICallSiteParam as a backup solution (when we lose
> track of location loaded int parameter forwarding register) for
> representing value at entry point. As a consequence we are able to
> produce 'DW_OP_entry_values' in 'DW_AT_call_site_value'(GCC generates
> such expressions) which allow us to go 2 or more frames behind. I've
> showed one example for calls at entry block that could also produce such
> expressions without DICallSiteParam, but that is the only case that I
> can think of now. But since it is a backup when something fails it could
> at some point be removed once it is no longer needed as backup.
> Currently it gives use more information about call site forwarding values.
> Thanks for time! It is our common goal to make this right!
> --Nikola

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20190213/5a9177a0/attachment-0001.html>

More information about the llvm-dev mailing list