Issue 126370
Summary Clang generates invalid code with `-finline-functions`
Labels clang
Assignees
Reporter 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](https://github.com/tarantool/msgpuck/pull/41#issuecomment-2638311985), but let's proceed with the one, provided by @Gumix [here](https://github.com/tarantool/msgpuck/pull/41#issuecomment-2638329954). 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:
```cpp
#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:
* `-O2`: https://godbolt.org/z/ov839cs8x
* `-O2 -fno-inline-functions`: https://godbolt.org/z/43r1P1fPz
* `-O3`: https://godbolt.org/z/jdKKdfh6c

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. 

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

Reply via email to