Inline function not eventually inlined is removed

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

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/

You're probably looking for some documentation about C inline semantics:
Handling ‘Undefined external’ message for inline function | IAR Systems

Additional notes: I think the -fgnu89-inline & C99 inline semantics were
designed explicitly the way so that vague linkage
(IA-64 gABI Issue 72: COMDAT) 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.)

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.

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.

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.

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

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).

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

> 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.

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*.

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