[llvm-dev] Inline function not eventually inlined is removed

475 views
Skip to first unread message

Mariusz Sikora via llvm-dev

unread,
Jul 30, 2021, 12:16:52 PM7/30/21
to llvm...@lists.llvm.org
Hello,

I'm trying to understand why LLVM-12 is removing function which is marked inline despite the fact it was not inlined inside caller. Caller function still has a call to inline function and compilation is failing because of a lack of the symbol.

Looking at debug logs I see:

Inliner visiting SCC: sort: 1 call sites.
      Analyzing call of calculate... (caller:sort)
      .
      Cost: 960
      Threshold: 487
    NOT Inlining (cost=960, threshold=487), Call:   call void @calculate(i32* %a, i32* %b)

Code:
int global = 0;
void inline calculate(int a[100], int b[100]) {
    int i;
#pragma unroll
    for (i = 0; i < 50; i++) {
        a[i] = b[i] + a[i];
    }
}

int sort(int a[100], int b[100]) {
    calculate(a, b);
    return a[20] + b[30] + global;
}

cli: clang -O3 -c inline1.c -o inline1_clang.o

ll file:
; Function Attrs: nounwind uwtable
 define dso_local i32 @sort(i32* %a, i32* %b) local_unnamed_addr #0 {
entry:
   tail call void @calculate(i32* %a, i32* %b)
   %arrayidx = getelementptr inbounds i32, i32* %a, i64 20
   %0 = load i32, i32* %arrayidx, align 4, !tbaa !2
   %arrayidx1 = getelementptr inbounds i32, i32* %b, i64 30
   %1 = load i32, i32* %arrayidx1, align 4, !tbaa !2
   %add = add nsw i32 %1, %0
   %2 = load i32, i32* @global, align 4, !tbaa !2
   %add2 = add nsw i32 %add, %2
   ret i32 %add2
}

; Function Attrs: inlinehint nounwind uwtable
 declare dso_local void @calculate(i32*, i32*) local_unnamed_addr #1

Thanks
Mariusz Sikora

David Blaikie via llvm-dev

unread,
Jul 30, 2021, 12:46:25 PM7/30/21
to Mariusz Sikora, llvm-dev

_______________________________________________
LLVM Developers mailing list
llvm...@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev

Fangrui Song via llvm-dev

unread,
Jul 30, 2021, 1:16:08 PM7/30/21
to David Blaikie, llvm-dev, Mariusz Sikora
On 2021-07-30, David Blaikie via llvm-dev wrote:
>You're probably looking for some documentation about C inline semantics:
>https://www.iar.com/knowledge/support/technical-notes/compiler/linker-error-undefined-external-for-inline-functions/

Additional notes: I think the -fgnu89-inline & C99 inline semantics were
designed explicitly the way so that vague linkage
(https://itanium-cxx-abi.github.io/cxx-abi/abi/prop-72-comdat.html) can
be avoided. The C inline behaviors are like always explicit
instantiation in C++.

(Seems that GNU has extensions for weak symbols on the a.out binary
format. Otherwise, if a binary format has neither weak symbol nor
COMDAT, vague linkage is not representable.)

Mariusz Sikora via llvm-dev

unread,
Aug 2, 2021, 1:05:50 PM8/2/21
to Fangrui Song, llvm-dev
I'm just trying to understand is this _Code_ undefined behavior or this is a bug in LLVM? Because why LLVM is removing functions without inlining it? For example GCC is not removing function event after inlining it.
--
Pozdrawiam
Mariusz Sikora

Fāng-ruì Sòng via llvm-dev

unread,
Aug 2, 2021, 2:17:31 PM8/2/21
to Mariusz Sikora, llvm-dev
On Mon, Aug 2, 2021 at 10:05 AM Mariusz Sikora <msik...@gmail.com> wrote:
>
> I'm just trying to understand is this _Code_ undefined behavior or this is a bug in LLVM? Because why LLVM is removing functions without inlining it? For example GCC is not removing function event after inlining it.

This is a user error. You may be looking for -fgnu89-inline & C99
inline documentation.
An extern inline definition is needed.

Inlining can paper over the user error. clang decides that inlining is
not good because the trip count is too large.
If you decrease the number of iterations, you may find that clang
inlines the function and the linker error goes away.

David Blaikie via llvm-dev

unread,
Aug 2, 2021, 3:18:27 PM8/2/21
to Mariusz Sikora, llvm-dev
Looks like both Clang and GCC discard the inline function definition even if the function is not inlined and the definition is needed (because C requires there be a separate non-inline definition for correctness): https://godbolt.org/z/hPjv1d1db

This code is probably (in C++ standard terminology, I'm not sure what terminology the C standard uses) "invalid, no diagnostic required" - that's usually the language for stuff that can cause linker errors like this.

On Mon, Aug 2, 2021 at 10:05 AM Mariusz Sikora <msik...@gmail.com> wrote:

via llvm-dev

unread,
Aug 2, 2021, 3:38:33 PM8/2/21
to dbla...@gmail.com, msik...@gmail.com, llvm...@lists.llvm.org

Clarification: According to the link provided earlier,

https://www.iar.com/knowledge/support/technical-notes/compiler/linker-error-undefined-external-for-inline-functions/
you need (exactly one) extern _declaration_ of the inline function, to keep it from vanishing.

 

inline int foo() { stuff; }

extern inline int foo(); // not a definition

int bar() { return foo(); }

 

--paulr

David Blaikie via llvm-dev

unread,
Aug 2, 2021, 3:44:18 PM8/2/21
to Paul Robinson, llvm-dev, Mariusz Sikora
I guess that somehow turns this inline declaration into the strong definition (despite the extern declaration coming after the inline definition). eg, that definition is emitted even without the call:

inline int foo() { }
extern inline int foo();
int bar() { }

But yeah, some way of specifying an extern definition (either separately, or by promoting the prior inline definition to an extern inline definition) is necessary. C inline is quirky (well, compared to C++ - they're probably all a bit quirky, just a matter of what you're used to).

David Chisnall via llvm-dev

unread,
Aug 4, 2021, 7:46:46 AM8/4/21
to llvm...@lists.llvm.org

On 02/08/2021 18:05, Mariusz Sikora via llvm-dev wrote:
> I'm just trying to understand is this _Code_ undefined behavior or
this is a bug in LLVM? Because why LLVM is removing functions without
inlining it? For example GCC is not removing function event after
inlining it.

C++ `inline` means 'the definition is provided in line with the
declaration, the compiler and linker are responsible for ensuring that
exactly one definition exists in the final binary'

C `inline` means 'this definition is provided in line with a declaration
and may be used by the compiler in preference to one that a linker finds'

C `inline extern` means 'a definition of this may appear in line with
the declaration but please provide a canonical definition here for when
the compiler decides not to emit it'

C `inline static` means 'a definition exists here inline and it is not
an error if this is not used. If it is, then it is private to this
compilation unit and it is not an error for the same static function to
exist in multiple compilation units'.

*None* of these say anything about whether the compiler is required to
inline the function, but they all specify what must happen to the
original definition:

- C++ `inline`: Must exist in at least one compilation unit and the
linker must discard duplicates.
- C `inline`: Must be eliminated
- C `inline extern`: must be emitted, the linker should error if two
definitions of the same inline extern function exist in different
compilation units.
- C `inline static`: It must be emitted if references to it exist in
the object code but it may be eliminated if it is unused (including if
all uses of it are inlined).

Clang is generating IR that makes LLVM do exactly what the language
semantics require: eliminate the definition.

The `inline` keyword is probably the most confusingly named keyword in
C/C++, though `static` comes close. The general rule of thumb for C is:

- If you think you mean `inline` you probably mean `inline static`.
- If you're really sure you mean `inline`, you almost certainly mean
`__attribute__((always_inline))` or `__forceinline` (depending on
whether you're writing GNU or Microsoft-flavoured C)
- If you're not sure but think you might mean `inline`, you really
mean to be writing C++ and not C.

David

Nick Desaulniers via llvm-dev

unread,
Aug 9, 2021, 2:16:38 PM8/9/21
to David Chisnall, llvm...@lists.llvm.org, clang-built-linux

Also, note that the meaning of `extern inline` changed between C90 and
C99; it's one of the few semantic changes that are drastic changes
that I know of between C standard revisions. You can get the previous
behavior by either compiling with -std=c89/c90/gnu89, -fgnu89-inline,
or using __attribute__((gnu_inline)) on function definitions. The
behavior of the prior standard and gnu_inline was to NOT emit any
symbol; a definition was provided only for the purposes of inline
substitution.

We've actually used this in the Linux kernel to provide two
definitions of a function; one in C for inlining, one in assembler to
avoid stack protectors and other compiler instrumentation (such as
coverage and sanitizers), though now we have better constructs for
describing these intents.

> - C `inline static`: It must be emitted if references to it exist in
> the object code but it may be eliminated if it is unused (including if
> all uses of it are inlined).
>
> Clang is generating IR that makes LLVM do exactly what the language
> semantics require: eliminate the definition.
>
> The `inline` keyword is probably the most confusingly named keyword in
> C/C++, though `static` comes close. The general rule of thumb for C is:
>
> - If you think you mean `inline` you probably mean `inline static`.
> - If you're really sure you mean `inline`, you almost certainly mean
> `__attribute__((always_inline))` or `__forceinline` (depending on
> whether you're writing GNU or Microsoft-flavoured C)

It's also useful to note that always (in always_inline) doesn't mean
always. The machinery handling inline substitution can still bail. Use
-Rpass-missed=inline (or maybe -Rpass-missed=always-inline) to learn
*why*.

> - If you're not sure but think you might mean `inline`, you really
> mean to be writing C++ and not C.
>
> David

--
Thanks,
~Nick Desaulniers

Joerg Sonnenberger via llvm-dev

unread,
Aug 9, 2021, 5:28:07 PM8/9/21
to llvm...@lists.llvm.org
On Mon, Aug 09, 2021 at 11:16:19AM -0700, Nick Desaulniers via llvm-dev wrote:
> Also, note that the meaning of `extern inline` changed between C90 and
> C99; it's one of the few semantic changes that are drastic changes
> that I know of between C standard revisions.

No, it didn't. C90 has no inline. The change was between the language
extensions of many compilers and the new standard. That's quite a
different thing.

Joerg

Reply all
Reply to author
Forward
0 new messages