[cfe-dev] Varying per function optimisation based on include path?

John McFarlane via cfe-dev cfe-dev at lists.llvm.org
Tue Aug 20 22:39:52 PDT 2019


I deliberately don't bring up call direction because I don't think it's
important and I want to keep things simple:
- If the code is user code, it should not be inlined.
- If the code is system code (including non-standard 3rd-party
dependencies), it should be inlined.

Take a hypothetical call stack from this example program
<https://wandbox.org/permlink/oI43EGHIERSNpy52>:

?: std::strncmp(....) <- C standard library function, possibly an
intrinsic, inline
?: std::string::operator==(char const*)  <- standard library function,
inline
8: [](std::string const&) {....} <- lambda, don't inline
?: std::find_if(....) <- standard library taking lambda, inline
4: contains_sg15 <- user function containing lambda, don't inline
13: main <- user's main function, don't inline

I think whether to inline the lambda is mildly contentious but I think we
both agree it should not be inlined. The real question is whether to inline
`std::find_if`. I guess you're suggesting we don't inline it. I think -- on
balance -- we probably should.

Let's step through this program and let's assume that I only use the "step
into" functionality of the debugger, because that's an easy way to 'visit'
an entire program. Let's assume `find_if` is a single function.

When I step into the call to `std::find_if`, I think it should skip through
`std::find_if` and take me directly to line 9 in the lambda function in
`contains_sg15`. Then, when I "step into" in the lambda, it'll step over
the `std::string` call and take me directly to line 10. (That's what it
does in GDB at least.) Then when I "step into" again, it *should* take me
back to line 9 in the second lambda invocation.

Now, if that last action doesn't work, I think that's where you have the
concern. I'm not entirely sure what'll happen once `std::find_if` is
inlined. I know that without inlining, "step into" eventually does take me
back to line 9 of the lambda body. If it doesn't, it's going to kick me
back out into the body of `contains_sg15` which is a problem.

In that case, yes, maybe saying that functions which call non-`-isystem`
functions should not be inlined is a rule to consider. But that instantly
makes things more complicated: we now have a new reason why the same
function might be both inlined and not inlined in the same TU. I don't know
if that's a problem. We now send the user into the guts of a standard
library call. That *is* a problem. That code looks frightening and
obfuscated to the average developer.

A workaround to the problem would be to put a breakpoint in the lambda
function. I personally would be perfectly happy with that compromise. We're
looking at a problem which game developers (among others) experience then
in-header functions are defined in the user's TU. In C code and non-modern
C++, the offending code is defined in a separate .cpp file.

Imagine if `std::find_if` could be compiled in a separate TU. And imagine
that TU was compiled with `-O2`. What would happen then? You might
experience the same debugging difficulty. The same workaround, using a
breakpoint, would work there also. We'd at least have parity with
non-modern C++. That's the main aim here: not to improve the state of the
art for C and C++, merely to help C++ catch up with C.

HTH,
John

On Tue, 20 Aug 2019 at 19:34, Ben Craig <ben.craig at ni.com> wrote:

> I think a question was glossed over.  Exactly which directions should be
> inlined…
>
>    1. User callee into user caller (definitely not)
>    2. System callee into system caller (yes)
>    3. User callee into system caller (maybe?)
>    4. System callee into user caller (maybe?)
>
>
>
> Perhaps number 3 should be prohibited because then a breakpoint on “my”
> function would either not get hit, or turn into multiple breakpoints.
>
>
>
> Perhaps number 4 should be prohibited because it makes stepping across
> loop iterations in things like std::transform more difficult.
>
>
>
>
>
> *From:* cfe-dev <cfe-dev-bounces at lists.llvm.org> *On Behalf Of *via
> cfe-dev
> *Sent:* Tuesday, August 20, 2019 12:59 PM
> *To:* arthur.j.odwyer at gmail.com
> *Cc:* jonathanchesterfield at gmail.com; john at mcfarlane.name;
> cfe-dev at lists.llvm.org
> *Subject:* [EXTERNAL] Re: [cfe-dev] Varying per function optimisation
> based on include path?
>
>
>
> Ah, I'd forgotten that Og prefers not to inline.
>
> Distinguishing optimization levels within one translation unit is tricky
> given the current way we build optimization pipelines. They are *not*
> designed to handle function-level differences in optimization levels.
> Trying to (essentially) mix O1 and O2 in the same translation unit is a
> radical departure from how LLVM thinks about optimization.  ('optnone' is a
> special case where passes effectively disable themselves when presented
> with an 'optnone' function. Generalizing that to more optimization levels
> is a seriously invasive proposition.)
>
>
>
> Re the "symbols" confusion, broadly speaking you can separate debug info
> into that which describes the source (types, variables, etc), and that
> which describes the generated code (to a first approximation, the
> instruction<->source mapping).  So the suggestion in this thread is to
> retain the former but not the latter.
>
> In this exercise, if we genuinely want to *prevent* debugging of
> defined-in-system-header functions (which seems like a highly questionable
> feature) it could be done with judicious application of the 'nodebug'
> attribute.  Not hard, really.
>
> --paulr
>
>
>
> *From:* Arthur O'Dwyer [mailto:arthur.j.odwyer at gmail.com
> <arthur.j.odwyer at gmail.com>]
> *Sent:* Tuesday, August 20, 2019 12:20 PM
> *To:* Robinson, Paul
> *Cc:* Jon Chesterfield; Clang Dev; John McFarlane
> *Subject:* Re: [cfe-dev] Varying per function optimisation based on
> include path?
>
>
>
> On Tue, Aug 20, 2019 at 9:42 AM via cfe-dev <cfe-dev at lists.llvm.org>
> wrote:
>
> > In -Og mode, it seems that it would equally make sense to take "a very
> big
> > slice around system headers specifically to avoid" debug symbols for code
> > that users can't debug.
>
>
>
> Our users seem to like to be able to dump their STL containers, which
> definitely requires debug symbols for "code they can't debug."
>
>
>
> Hmm, I may have muddled things up by mentioning "debug symbols" without
> fully understanding what people mean by that phrase precisely. I meant
> "line-by-line debugging information enabling single-step through a bunch of
> templates that the user doesn't care about and would prefer to see inlined
> away." Forget debug symbols and focus on inlining, if that'll help avoid my
> confusion. :)
>
>
>
> OTOH being able to more aggressively optimize system-header code even in
> –Og mode seems reasonable.
>
> OTOOH most of the system-header code is templates or otherwise inlineable
> early, and after inlining the distinction between app and sys code really
> goes away.
>
>
>
> I believe we'd like to get "inlining early," but the problem is that `-Og`
> disables inlining. So there is no "after inlining" at the moment.
>
> Here's a very concrete example: https://godbolt.org/z/5tTgO4
> <https://urldefense.com/v3/__https:/godbolt.org/z/5tTgO4__;!fqWJcnlTkjM!7ZGRlXoS3ERcBoHUI0twkSwgjy1q68aYJaN5WYHvdmN5-ryxMXzEwmUQRCfC$>
>
>
>
> int foo(std::tuple<int, int> t) {
>
>     return std::get<0>(t);
>
> }
>
>
>
> At `-Og` this produces the assembly code
>
>
>
> _Z3fooSt5tupleIJiiEE:
>
>   pushq %rax
>
>   callq
> _ZSt3getILm0EJiiEERNSt13tuple_elementIXT_ESt5tupleIJDpT0_EEE4typeERS4_
>
>   movl (%rax), %eax
>
>   popq %rcx
>
>   retq
>
> _ZSt3getILm0EJiiEERNSt13tuple_elementIXT_ESt5tupleIJDpT0_EEE4typeERS4_:
>
>   jmp _ZSt12__get_helperILm0EiJiEERT0_RSt11_Tuple_implIXT_EJS0_DpT1_EE
>
> _ZSt12__get_helperILm0EiJiEERT0_RSt11_Tuple_implIXT_EJS0_DpT1_EE:
>
>   jmp _ZNSt11_Tuple_implILm0EJiiEE7_M_headERS0_
>
> _ZNSt11_Tuple_implILm0EJiiEE7_M_headERS0_:
>
>   addq $4, %rdi
>
>   jmp _ZNSt10_Head_baseILm0EiLb0EE7_M_headERS0_
>
> _ZNSt10_Head_baseILm0EiLb0EE7_M_headERS0_:
>
>   movq %rdi, %rax
>
>   retq
>
>
>
> I believe that if John McFarlane's proposal were adopted by Clang, so that
> inlining-into-system-functions were allowed at `-Og`, then the resulting
> assembly code would look like this instead, for a much better experience in
> both debugging and runtime performance:
>
>
>
> _Z3fooSt5tupleIJiiEE:
>
>   pushq %rax
>
>   callq
> _ZSt3getILm0EJiiEERNSt13tuple_elementIXT_ESt5tupleIJDpT0_EEE4typeERS4_
>
>   movl (%rax), %eax
>
>   popq %rcx
>
>   retq
>
> _ZSt3getILm0EJiiEERNSt13tuple_elementIXT_ESt5tupleIJDpT0_EEE4typeERS4_:
>
>   leaq 4(%rdi), %rax
>
>   retq
>
>
>
> Notice that we still aren't inlining `std::get` into `foo`, because `foo`
> (as a user function) gets no inlining optimizations at `-Og`. But we do
> inline and collapse the whole chain of function-template helpers into
> `std::get` (because `std::get` is a function *defined* in a system
> header). This inlining creates new optimization opportunities, such as
> combining the `add` and `mov` into a single `lea`.
>
>
>
> HTH,
>
> –Arthur
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20190821/f2b9b77e/attachment.html>


More information about the cfe-dev mailing list