Giter Site home page Giter Site logo

Comments (16)

jeremy-rifkin avatar jeremy-rifkin commented on June 11, 2024

If you click the orange checkmark next to the compiler flags you can see the compiler flags added by CE. Additionally CE does a lot of filtering of assembly to provide something more human-readable. If you turn off filters you can see something more accurate, and if you turn on link to binary it will show the objdump’d code from the final executable.

from compiler-explorer.

jeremy-rifkin avatar jeremy-rifkin commented on June 11, 2024

The printf->puts optimization isn’t done by CE, that’s gcc

from compiler-explorer.

kosak avatar kosak commented on June 11, 2024

The printf->puts optimization isn’t done by CE, that’s gcc

Of course. My point was to compare CE against my computer. And that however CE is running gcc, I find it surprising that I can't run the same version of gcc on my computer and get the same or substantially similar output.

from compiler-explorer.

dkm avatar dkm commented on June 11, 2024

Your function returning a string view is not returning anything, this is UB. Turn the function void and it works (note your main is also not returning anything, should be void also).

https://gcc.godbolt.org/z/Mo1dqzcr6

Probably caused by some optimizers trying to reuse the returned value as main is missing a ret after the call in your faulty case... Definitely not a CE bug.

from compiler-explorer.

kosak avatar kosak commented on June 11, 2024

Respectfully, I think you are missing my point and I don't think this issue should be closed. I'm well aware that it's undefined behavior: I said so in the first sentence of my post. Fixing my code is completely beside the point. I know what's wrong with it.

Some of us use Compiler Explorer to "Explore" the behavior of the "Compiler" in undefined behavior scenarios.

The issue I am trying to draw your attention to is that CE is showing assembly code that it could not possibly be executing -- regardless of how many filter buttons one clicks or unclicks. This diminishes the educational and investigative value of Compiler Explorer -- in this case.

(note your main is also not returning anything, should be void also).

This is not correct. The standard makes it clear that main must be defined to return int, and the standard also allows the programmer to omit the return statement. See 6.9.3.1 subparts (2.1) and (3.4) from e.g. here https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4928.pdf "If control flows off the end of the compound-statement of main, the effect is equivalent to a return with operand 0 (see also 14.4)"

Regardless, people like myself use Compiler Explorer to analyze even buggy programs. Trying to spot bugs in the source is completely beside the point. My issue here is that the assembly doesn't match the execution, and I continue to maintain that that's surprising CE behavior from a UX standpoint.

from compiler-explorer.

partouf avatar partouf commented on June 11, 2024

The assembly code IS showing what it is executing

In the assembly you can see there is no ret opcode at the end of main, which will cause issues depending on what's after main, because the CPU will just continue on executing whatever is there

If you select Link to binary, there's some more clues as to the ordering of the actual binary, but we don't show more than the labels that are involved in the actual user code, because it would be a bad user experience.

If you want to see everything, your best bet is to compile locally and debug the program to see what's after it.

With objdump alone you might not figure it out, because objdump will probably think it's data instead of parsing it as opcodes, but it also depends on how the program is copied into memory.

from compiler-explorer.

jeremy-rifkin avatar jeremy-rifkin commented on June 11, 2024

Hi,
I can clarify better now that I'm at a computer. The flags CE is passing are -g -o /nosym/tmp/compiler-explorer-compiler2024415-7782-uf1da5.2mpt/output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c++17 /nosym/tmp/compiler-explorer-compiler2024415-7782-uf1da5.2mpt/example.cpp. Nothing here would affect the assembly, substance-wise. It's using the same flags for execution minus the -masm=intel -S. Theoretically the fact we compile twice could be problematic for a non-deterministic compiler, but nothing here should be non-deterministic.
If you look at the link to binary objdump output https://gcc.godbolt.org/z/PMsEv5abe you'll see the linker puts main before func as opposed to gcc's output which puts func first. This isn't any inconsistency on CE's behalf, it's just whatever choices gcc/the linker make.
As far as differences between CE and your local gcc, it could be a simple version difference or a difference in how we compile gcc (more info at https://github.com/compiler-explorer/gcc-builder), or it could also be a difference in your local linker.
I'm not sure how CE's gcc could do the printf -> puts optimization while your local gcc doesn't, unless you're using a really old gcc. If you could provide more info on how you're testing this locally I'd be interested to look into that more.

I agree 100% CE is a special tool that we turn to for debugging things, but unfortunately I don't think this is a CE bug. There are just so many factors at play, it'd be impossible for CE to surely show you the same thing you see locally.

from compiler-explorer.

kosak avatar kosak commented on June 11, 2024

Thanks very much for your reply. As for my gcc I was careful to use the same version I picked for CE (11.4.0). Verbose output below, which also shows the same flags, source code, but different execution behavior. I tried to use exactly the same flags but I changed the pathnames and I didn't use -S and -o (because I wanted an executable).

That said, I'm not overly concerned that CE has different behavior than my own compiler (as you said, there could be a difference in compiler build -- though I admit the printf -> puts optimization continues to surprise me. My compiler isn't doing that with those flags). I'm more concerned that on my box, the assembly feels consistent with the execution (print message once, then segfault). Put another way, on my box, I can explain how the assembly leads to the execution.

However on CE I can't explain how the assembly is consistent with the execution. Namely, regardless of where func and main are laid out by the linker, where is the branch instruction that causes one to jump back to the other, or back to itself? Are we to assume that some random piece of data that appears at the end of main just happens to be interpreted as a branch to exactly the right target?

Again, that's the gist of my complaint here... that the assembly CE is showing feels like it can't be the assembly that CE is running, unless one makes some very special assumptions.

I'm using the gcc 11.4.0 that comes out of the box with Ubuntu 22.04.

$ cat example.cpp && g++ -v  && g++ -g -masm=intel -fdiagnostics-color=always -O2 -std=c++17 example.cpp && ./a.out
#include <stdio.h>
#include <string_view>

std::string_view func(const int &) {
    printf("hello\n");
}

int main() {
    int m = 5;
    func(m);
}

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-1ubuntu1~22.04' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04) 
example.cpp: In function ‘std::string_view func(const int&)’:
example.cpp:6:1: warning: no return statement in function returning non-void [-Wreturn-type]
    6 | }
      | ^
hello
Segmentation fault (core dumped)

from compiler-explorer.

jeremy-rifkin avatar jeremy-rifkin commented on June 11, 2024

Thanks, I’ll look more this evening. I’m also surprised by the infinite loop, I can only guess it must be something after func that CE is filtering out of the objdump.

from compiler-explorer.

partouf avatar partouf commented on June 11, 2024

I did some digging. But it doesn't look like we can reproduce the behavior of Ubuntu GCC's version.

The things that we can reproduce is a bunch of extra opcodes and disabling the printf-puts optimization:
-fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection -fno-builtin-printf -fPIE -pie

What we cannot reproduce is because of Ubuntu using __printf_chk instead of printf (probably), this subsequently causes that func() is not inlined and very different assembly is generated.

A comparison:

With CE's gcc (with the extra parameters):

0000000000001060 <main>:
    1060:	f3 0f 1e fa          	endbr64
    1064:	50                   	push   %rax
    1065:	58                   	pop    %rax
    1066:	48 8d 3d 97 0f 00 00 	lea    0xf97(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    106d:	31 c0                	xor    %eax,%eax
    106f:	48 83 ec 08          	sub    $0x8,%rsp
    1073:	e8 d8 ff ff ff       	call   1050 <printf@plt>
    1078:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)
    107f:	00 

0000000000001080 <_start>:
    1080:	f3 0f 1e fa          	endbr64
    1084:	31 ed                	xor    %ebp,%ebp
    1086:	49 89 d1             	mov    %rdx,%r9
    1089:	5e                   	pop    %rsi
    108a:	48 89 e2             	mov    %rsp,%rdx
    108d:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
    1091:	50                   	push   %rax
    1092:	54                   	push   %rsp
    1093:	45 31 c0             	xor    %r8d,%r8d
    1096:	31 c9                	xor    %ecx,%ecx
    1098:	48 8d 3d c1 ff ff ff 	lea    -0x3f(%rip),%rdi        # 1060 <main>
    109f:	ff 15 23 2f 00 00    	call   *0x2f23(%rip)        # 3fc8 <__libc_start_main@GLIBC_2.34>
    10a5:	f4                   	hlt
    10a6:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
    10ad:	00 00 00 

With Ubuntu's GCC:

0000000000001060 <main>:
    1060:	f3 0f 1e fa          	endbr64
    1064:	50                   	push   %rax
    1065:	58                   	pop    %rax
    1066:	48 83 ec 18          	sub    $0x18,%rsp
    106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
    1071:	00 00 
    1073:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
    1078:	31 c0                	xor    %eax,%eax
    107a:	48 8d 7c 24 04       	lea    0x4(%rsp),%rdi
    107f:	e8 fc 00 00 00       	call   1180 <_Z4funcRKi>
    1084:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
    108b:	00 00 00 
    108e:	66 90                	xchg   %ax,%ax

0000000000001090 <_start>:
    1090:	f3 0f 1e fa          	endbr64
    1094:	31 ed                	xor    %ebp,%ebp
    1096:	49 89 d1             	mov    %rdx,%r9
    1099:	5e                   	pop    %rsi
    109a:	48 89 e2             	mov    %rsp,%rdx
    109d:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
    10a1:	50                   	push   %rax
    10a2:	54                   	push   %rsp
    10a3:	45 31 c0             	xor    %r8d,%r8d
    10a6:	31 c9                	xor    %ecx,%ecx
    10a8:	48 8d 3d b1 ff ff ff 	lea    -0x4f(%rip),%rdi        # 1060 <main>
    10af:	ff 15 23 2f 00 00    	call   *0x2f23(%rip)        # 3fd8 <__libc_start_main@GLIBC_2.34>
    10b5:	f4                   	hlt
    10b6:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
    10bd:	00 00 00 

Somewhat further in the binary:

0000000000001180 <_Z4funcRKi>:
    1180:	f3 0f 1e fa          	endbr64
    1184:	50                   	push   %rax
    1185:	58                   	pop    %rax
    1186:	48 8d 35 77 0e 00 00 	lea    0xe77(%rip),%rsi        # 2004 <_IO_stdin_used+0x4>
    118d:	bf 01 00 00 00       	mov    $0x1,%edi
    1192:	31 c0                	xor    %eax,%eax
    1194:	48 83 ec 08          	sub    $0x8,%rsp
    1198:	e8 b3 fe ff ff       	call   1050 <__printf_chk@plt>

00000000000011a0 <_fini>:
    11a0:	f3 0f 1e fa          	endbr64
    11a4:	48 83 ec 08          	sub    $0x8,%rsp
    11a8:	48 83 c4 08          	add    $0x8,%rsp
    11ac:	c3                   	ret

from compiler-explorer.

dkm avatar dkm commented on June 11, 2024

You can reproduce by not using printf but puts directly:

#include <stdio.h>
#include <string_view>

std::string_view func(const int &) {
    puts("hellkkko\n");
}

int main() {
    int m = 5;
    func(m);
}
 ➜  ~ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
 ➜  ~ g++ -O2 -std=c++17 /tmp/toto.cc -o /tmp/toto -g3
 ➜  ~ /tmp/toto|head -n5                              
hellkkko

hellkkko

hellkkko

Using gdb shows that the return from puts jumps at the beg of main, hence the loop. I'm not expert in x86-64 and how it handles stack/return address, but it's probably caused by the function not returning as it should.

7   │ 0000000000001050 <puts@plt>:
  38   │     1050:   f3 0f 1e fa             endbr64 
  39   │     1054:   f2 ff 25 75 2f 00 00    bnd jmp *0x2f75(%rip)        # 3fd0 <puts@GLIBC_2.2.5>
  40   │     105b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  41   │ 
  42   │ Disassembly of section .text:
  43   │ 
  44   │ 0000000000001060 <main>:
  45   │     1060:   f3 0f 1e fa             endbr64 
  46   │     1064:   50                      push   %rax
  47   │     1065:   58                      pop    %rax
  48   │     1066:   48 8d 3d 97 0f 00 00    lea    0xf97(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
  49   │     106d:   48 83 ec 08             sub    $0x8,%rsp
  50   │     1071:   e8 da ff ff ff          call   1050 <puts@plt>
  51   │     1076:   66 2e 0f 1f 84 00 00    cs nopw 0x0(%rax,%rax,1)

Sorry if my initial answer was too short, not my intent (and you're right about void main, my bad).

from compiler-explorer.

partouf avatar partouf commented on June 11, 2024

Using gdb shows that the return from puts jumps at the beg of main, hence the loop. I'm not expert in x86-64 and how it handles stack/return address, but it's probably caused by the function not returning as it should.

_start (the "real" entry point) is right after main, and _start calls main (via glibc)

from compiler-explorer.

dkm avatar dkm commented on June 11, 2024

True, but that does not explain why gcc thinks it's ok to inline func (and ends up not having any ret). See https://gcc.godbolt.org/z/r6jv91b8j where it even discard the return 0 in main. 🤷

from compiler-explorer.

partouf avatar partouf commented on June 11, 2024

True, but that does not explain why gcc thinks it's ok to inline func (and ends up not having any ret). See https://gcc.godbolt.org/z/r6jv91b8j where it even discard the return 0 in main. 🤷

tbh: garbage in => garbage out (https://en.cppreference.com/w/cpp/language/return - Flowing off the end of a value-returning function, except main and specific [coroutines](https://en.cppreference.com/w/cpp/language/coroutines)(since C++20), without a return statement is undefined behavior.)

from compiler-explorer.

kosak avatar kosak commented on June 11, 2024

Aha, interesting! Yeah that particular garbage in => garbage out scenario doesn't surprise me unfortunately. While I'm not a fan of how aggressively modern compilers exploit "undefined behavior" it does unfortunately mean that if func() has undefined behavior (even "harmless" UB like this program), and main() calls func(), then main gets to have undefined behavior too, meaning it can be arbitrarily messed-up.

from compiler-explorer.

dkm avatar dkm commented on June 11, 2024

Yes... But I'm still curious to understand why GCC ends up with this code. I may dig the dumps and ask around (but haven't got any new answer yet...).

from compiler-explorer.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.