Asynchronous unwind tables take more space in the runtime image, I'd
estimate something like 80-90% more, as the difference is adding
roughly the same number of CFI directives as for prologues, only a bit
simpler (e.g. `.cfi_offset reg, off` vs. `.cfi_restore reg`). Or even
more, if you consider tail duplication of epilogue blocks.
Asynchronous unwind tables could also restrict code generation to
having only a finite number of frame pointer adjustments (an example
of *not* having a finite number of `SP` adjustments is on AArch64 when
untagging the stack (MTE) in some cases the compiler can modify `SP`
in a loop).
Having the CFI precise up to an instruction generally also means one
cannot bundle together CFI instructions once the prologue is done,
they need to be interspersed with ordinary instructions, which means
extra `DW_CFA_advance_loc` commands, further increasing the unwind
tables size.
That is to say, async unwind tables impose a non-negligible overhead,
yet for the most common use cases (like C++ exceptions), they are not
even needed.
We could, for example, extend the `uwtable` attribute with an optional
value, e.g.
- `uwtable` (default to 2)
- `uwtable(1)`, sync unwind tables
- `uwtable(2)`, async unwind tables
- `uwtable(3)`, async unwind tables, but tracking only a subset of
registers (e.g. CFA and return address)
Or add a new attribute `async_uwtable`.
Other suggestions? Comments?
~chill
--
Compiler scrub, Arm
_______________________________________________
LLVM Developers mailing list
llvm...@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
Thanks for starting the topic. I am very interested in the topic and
would like to see that CFI gets improved.
I have looked into -funwind-tables/-fasynchronous-unwind-tables and
done some relatively simple changes
like (default to -fasynchronous-unwind-tables for aarch64/ppc,
fix -f(no-)unwind-tables/-f(no-)asynchronous-unwind-tables/make
-fno-asynchronous-unwind-tables work with instrumentation,
add `-funwind-tables=1|2 `) but haven't done anything on the IR level.
It's good to see that someone picks up the heavylift work so that I
don't need to do it:)
That said, if you need a reviewer or help on some work items, feel
free to offload some to me.
> Asynchronous unwind tables take more space in the runtime image, I'd
> estimate something like 80-90% more, as the difference is adding
> roughly the same number of CFI directives as for prologues, only a bit
> simpler (e.g. `.cfi_offset reg, off` vs. `.cfi_restore reg`). Or even
> more, if you consider tail duplication of epilogue blocks.
> Asynchronous unwind tables could also restrict code generation to
> having only a finite number of frame pointer adjustments (an example
> of *not* having a finite number of `SP` adjustments is on AArch64 when
> untagging the stack (MTE) in some cases the compiler can modify `SP`
> in a loop).
The restriction on MTE is new to me as I don't know much about MTE yet.
>
> Having the CFI precise up to an instruction generally also means one
> cannot bundle together CFI instructions once the prologue is done,
> they need to be interspersed with ordinary instructions, which means
> extra `DW_CFA_advance_loc` commands, further increasing the unwind
> tables size.
>
> That is to say, async unwind tables impose a non-negligible overhead,
> yet for the most common use cases (like C++ exceptions), they are not
> even needed.
>
> We could, for example, extend the `uwtable` attribute with an optional
> value, e.g.
> - `uwtable` (default to 2)
> - `uwtable(1)`, sync unwind tables
> - `uwtable(2)`, async unwind tables
> - `uwtable(3)`, async unwind tables, but tracking only a subset of
> registers (e.g. CFA and return address)
>
> Or add a new attribute `async_uwtable`.
>
> Other suggestions? Comments?
I have thought about extending uwtable as well. In spirit the idea
looks great to me.
The mode removing most callee-saved registers is useful.
For example, I think linux-perf just uses pc/sp/fp (as how its ORC
unwinder is designed).
My slight concern with uwtable(3) is that the amount of unwind
information is not monotonic.
Since sync->async and the number of registers are two dimensions,
perhaps we should use two function attributes?
>
> ~chill
BTW, are you working on improving the general CFI problems for aarch64?
I tried to understand the implementation limitation in September (in
https://reviews.llvm.org/D109253) but then stopped.
If you have patches, I'll be happy to study them:)
I know there are quite problems like:
(a) .cfi_* directives in prologue are less precise
% cat a.c
void foo() {
asm("" ::: "x23", "x24", "x25");
}
% clang --target=aarch64-linux-gnu a.c -S -o -
...
foo: // @foo
.cfi_startproc
// %bb.0: // %entry
str x25, [sp, #-32]! // 8-byte Folded Spill
stp x24, x23, [sp, #16] // 16-byte Folded Spill
.cfi_def_cfa_offset 32 ////// should be immediately after
the pre-increment str
.cfi_offset w23, -8
.cfi_offset w24, -16
.cfi_offset w25, -32
//APP
//NO_APP
(b) .cfi_* directives (for MachineInstr::FrameDestroy) in epilogue are
generally missing
(c) A basic block following an exit block may have wrong CFI
information (this can be fixed with .cfi_restore)
Most problems apply to all non-x86 targets.
---
Since we are discussing asynchronous unwind tables, may I ask two
slightly off-topic things?
(1) What's your opinion on ld --no-ld-generated-unwind-info?
Mine is https://maskray.me/blog/2020-11-15-explain-gnu-linker-options#no-ld-generated-unwind-info
(2) How should future stack unwinding strategy evolve?
Hardware assisted approach like leveraging shadow call stack?
Making FP more efficient so that user code can leverage
-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer and drop
inefficient (both size and run-time performance) .eh_frame?
Last year I wrote a post
https://maskray.me/blog/2020-11-08-stack-unwinding as I learn stack
unwinding.
I am going to amend it to include my recent thoughts.
I have trouble understanding "min" in the uwtable 2 row.
What does it mean?
As IR attributes, I'd hope the behavior is strictly monotonic in every
dimensions. I.e. If uwtable A provides some information, I'd expect that
uwtable B provides at least the same amount of information if A < B.
We may end up with too many dimensions.
For such rare ones (e.g. the number of registers), I think it is
entirely fine to omit it from IR attributes and make it an
llvm::TargetOptions::* option, like -ffunction-sections. We just lose
fine-grained LTO merge behavior which is probably not needed at all.
Thanks:)
>> Since we are discussing asynchronous unwind tables, may I ask two
>> slightly off-topic things?
>>
>> (1) What's your opinion on ld --no-ld-generated-unwind-info?
>
>I would say, from a design point of view, an unwinder of any kind should
>not analyse and interpret machine
>instructions as it's, in the general case, fragile - that's been my
>experience from developing and maintaining
>an unwinder that analysed prologues/epilogues, over a period of 10+ years,
>each new compiler version required adjustments.
>
>Then, PLT entries are likely to be a special case as they are both tiny and
>extremely unlikely to change between
>different compilers or different compiler versions. In a sense, one can
>treat them as having implicit identical unwind
>table entries (of any unwind table kind) associated with their address
>range, therefore explicit entries in the regular
>unwind tables are superfluous.
Thanks for the comments.
>> (2) How should future stack unwinding strategy evolve?
>Well, that's a good question ... :D
* compact unwind scheme.
* hardware assisted. Piggybacking on security hardening features like shadow call stack. However, this unlikely provides more information about callee-saved registers.
* mainly FP-based. People don't use FP due to performance loss. If `-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer` doesn't hurt performance that much, it may be better than some information in `.eh_frame`. We can use unwind information to fill the gap, e.g. for shrink wrapping.
If we go with the FP route, the expectation from .eh_frame may be lower.
We just need it to fill the gap.
We can probably indicate the intention with a codegen only llvm::TargetOptions option as well.
>~chill
>
>--
>Compiler scrub, Arm
uwtable 1/nounwind 1: <full,no>
uwtable 2/nounwind 1: <min,min>
Why is there a full->min transition for the generated prologue?
On 2021-11-21, Momchil Velikov wrote:
> | nounwind 0 | nounwind 1
>----------+-------------+--------------
>uwtable 0 | <full,no> | <no,no>
>----------+-------------+--------------
>uwtable 1 | <full,no> | <full,no>
>----------+-------------+--------------
>uwtable 2 | <full,min> | <min, min>
>----------+-------------+--------------
>uwtable 3 | <full,full> | <full,full>
>
>where
> - "full" means full unwind info - CFA, CSRs, return address
> - "min" is minimal, sans CSRs,
> - "no" is, well, no unwind info and
> - "<p,e>" is the kind generated for prologues and epilogues, respectively.
uwtable 1/nounwind 1: <full,no>
uwtable 2/nounwind 1: <min,min>
Why is there a full->min transition for the generated prologue?
I wanted to ask why the prologue information has degraded from full to
min when transiting from uwtable 1 to uwtable 2.
I do not understand why moving from uwtable 1 to uwtable 2 is not monotonic.
The argument with keeping <full,no> for "uwtable=1,nounwind=1" as the
current state is fine.
But then why is <min,min> for "unwtable=2,nounwind=1" not a problem
for debugging?
Well, it is, but it's a different use case. A user chooses whatever
suits their needs.
If they need debugging they can choose either "uwtable=1" or
"uiwtable=3". If they need
profiling they can choose "uwtable=2" or "uwtable=3". If they don't
need either, they can choose "uwtable=0".
--
Compiler scrub, Arm
Got it. So there are indeed 3 dimensions as I think.
(a) nounwind: raise exceptions or not
(b) uwtable: generate additional information even if nounwind is
specified: none, sync, async
(c) number of registers: pc(or link register)/sp/(maybe fp), or full
(most changed callee-saved registers)
The uwtable=0,1,2,3 scale combines (b) and (c), but the (b)x(c)
possibilities cannot be linearized.
What I wondered is whether we can make (c) an llvm::TargetOptions::*
option like FunctionSections/DataSections, then (b) can be linearized
(none,sync,async).
The downsize is that in LTO builds, one cannot indicate the intent
that a.o wants a subset of registers while b.o wants a full set.
My feeling is that users wanting mix-and-match (c) behavior is rare,
therefore an llvm::TargetOptions::* option can serve them well.
If the requirement actually becomes real, we can introduce new
function attributes.
If we start with the non-linear uwtable=0,1,2,3, then we cannot fix it
in the future.
Yes, thanks for bringing this up. OpenVMS on x86-64 needs full async
unwind tables as our system allows for exceptions to be handled during
both the prologue and epilogue. We are currently avoiding the poor
quality of prologue/epilogue CFI information at present until we can
move forward to a recent LLVM version (long story) where I think the
support is better.
I like just having a uwtable level (1,2,3,etc).
John
This e-mail (including any attachments) may contain privileged, confidential, proprietary, private, copyrighted, or other legally protected information. The information is intended to be for the use of the individual or entity designated above. If you are not the intended recipient (even if the e-mail address above is yours), please notify us by return e-mail immediately, and delete the message and any attachments. Any disclosure, reproduction, distribution or other use of this message or any attachments by an individual or entity other than the intended recipient is prohibited.
Yes, that would be my preferred option and is what my current change does:
- `uwtable` (default to 2
- `uiwtable(1)` sync unwind tables
- `uwtable(2)` async unwind tables
Right, so the use cases I have in mind are:
* synchronous exceptions: these need non-instruction precise unwind
tables, correct at function calls; generally all the callee-saved
registers need to be described
no `uwtable`, `uwtable(1)` support this case; `uwtable(2)` too, but
is needlessly precise and verbose
* debugging: best debugging experience would be instruction precise
unwind tables,
over the whole function, however, there is a bit of a wiggle room:
- unwind tables may not be instruction precise, essentially at the
same level of precision as the case for exceptions
- unwind tables for epilogues might be missing
This is what we have in most backends now. `uwtable(2)` supports
this case, `uwtable(1)` results in smaller unwind tables, at the cost
of a degraded user experience.
* sampling profilers: for best precision need instruction precise
unwind tables both in the prologue and in the epilogue (which makes
for comparatively large unwind tables)
`uiwtable(2)` supports this case.
In principle, there is no need to describe all the callee-saved
registers, just those needed to get a function's caller, so, whatever
passes for "return address, "SP", and "FP", and others. In the
course of the discussion, we agreed that's best addressed by a
separate mechanism.
~chill