Skip to content

clang++ generates invalid code with -finline-functions #126370

@igormunkin

Description

@igormunkin

We faced an issue with function inlining optimizations in scope of this PR: tarantool/msgpuck#41. You can find the reproducer we reduced from the original issue with @Gerold103 here, but let's proceed with the one, provided by @Gumix here. TBH, we don't know, whether both issues have the same root cause, but the latter reproducer is smaller at least.

So, we have the source file a.c with the following contents:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

static uint64_t unused_func(uint8_t type, const char *data) {
	switch (type) {
	case 8: {
		struct cast { uint8_t val; };
		return ((struct cast *)data)->val;
	}
	case 16: {
		struct cast { uint16_t val; };
		uint16_t val = ((struct cast *)data)->val;
		return __builtin_bswap16(val);
	}
	case 32: {
		struct cast { uint32_t val; };
		uint32_t val = ((struct cast *)data)->val;
		return __builtin_bswap32(val);
	}
	case 64: {
		struct cast { uint64_t val; };
		uint64_t val = ((struct cast *)data)->val;
		return __builtin_bswap64(val);
	}
	}
	abort();
}

//__attribute__((noinline))
static void store_0_to_buf(void *buf) {
	struct cast { uint8_t val; };
	((struct cast *)buf)->val = 0;
}

int main() {
	char buf_a[2][8];
	char buf_b[2][8];
	store_0_to_buf(buf_a[0]);
	store_0_to_buf(buf_a[1]);
	store_0_to_buf(buf_b[0]);
	store_0_to_buf(buf_b[1]);

	for (int ai = 0; ai < 2; ++ai) {
		for (int bi = 0; bi < 2; ++bi) {
			const char *data_a = buf_a[ai];
			const char *data_b = buf_b[bi];
			struct cast { uint8_t val; };
			uint8_t ca = ((struct cast *) data_a)->val;
			uint8_t cb = ((struct cast *) data_b)->val;
			if (ca != cb) {
				uint64_t a = unused_func(ca, data_a + 1);
				uint64_t b = unused_func(cb, data_b + 1);
				printf("??? %d\n", a > b);
			}
			printf("ok ");
		}
	}
}

Here is the version of the Clang I use on my machine:

$ clang++ --version
clang version 19.1.7
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm/19/bin
Configuration file: /etc/clang/x86_64-pc-linux-gnu-clang++.cfg
$ clang --version
clang version 19.1.7
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm/19/bin
Configuration file: /etc/clang/x86_64-pc-linux-gnu-clang.cfg

When building the source code above with the different optimization flags, I get the following results:

$ clang++ a.c -O2
clang++: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
$ ./a.out
$ clang++ a.c -O2 -fno-inline-functions
clang++: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
$ ./a.out
ok ok ok ok 
$ clang++ a.c -O3 
clang++: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
$ ./a.out
ok ok ok ok 
$ # --- mind using clang instead of clang++ below ---
$ clang a.c -O2
$ ./a.out
ok ok ok ok 
$ clang a.c -O2 -fno-inline-functions
$ ./a.out
ok ok ok ok
$ clang a.c -O3 
$ ./a.out      
ok ok ok ok 

As you can see, there is nothing printed when a.c is compiled via clang++ with -O2. Running the binary, compiled with -fsanitize=undefined, works fine and yields no UB issues.

Here are the links to the resulting asm, generated by godbolt:

I agree with assumptions, got by inlining zero-initializing routine, and, ergo, agree with the code, emitted for -O3. But I don't get how empty main can be generated for the loop, that calls the functions with side effects.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions