Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What does -fno-common actually do? #324

Open
nivedita76 opened this issue May 18, 2019 · 12 comments
Open

What does -fno-common actually do? #324

nivedita76 opened this issue May 18, 2019 · 12 comments

Comments

@nivedita76
Copy link

nivedita76 commented May 18, 2019

The GCC doc says:

some targets may carry a speed or code size penalty on variable references.

But is x86 actually one of them?

On x86_64 I see zero difference in the code generated. On 32-bit, there is a difference in the code generated by the compiler, but after linking, the two look identical.

On 32-bit the compiler generates
movl x@GOT(%eax), %eax
without -fno-common, vs
leal x@GOTOFF(%eax), %eax
but the linker seems to convert the mov into lea.

@nivedita76
Copy link
Author

That was with -fpie, with -fno-pie even the compiled code is the same.

@nivedita76
Copy link
Author

@javashin
Copy link

i was tempted to add -fno-common to my cflags mix , but really what it does ?

@InBetweenNames
Copy link
Owner

It's for targets where it is beneficial. Really, I want GentooLTO running anywhere Gentoo does :) I know this has impact on at least certain ARM architectures

@InBetweenNames
Copy link
Owner

Also: -fno-common can be helpful to find cases of UB with multiple definitions -- something that is particularly troublesome with LTO. I'm not sure if there's a benefit on x86 architectures -- reading through the mailing list thread today.

@InBetweenNames
Copy link
Owner

@nivedita76
Copy link
Author

nivedita76 commented May 19, 2019

@InBetweenNames Thanks that is an interesting blog.

I'm finding it hard to understand what the semantics are and why it does certain things:
Eg for this code and -fpie:
int a; int b; int f() { return a + b; }
On x86_64 with both -fcommon and -fno-common I get
movl b(%rip), %eax
addl a(%rip), %eax

But the code on x86_32 is different:
With -fcommon
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl a@GOT(%eax), %edx
movl b@GOT(%eax), %eax
movl (%eax), %eax
addl (%edx), %eax

With -fno-common the last 4 instructions change to (it loads the GOT into %edx this time)
movl b@GOTOFF(%edx), %eax
addl a@GOTOFF(%edx), %eax

But I don't understand why it couldn't have generated the same code in the -fcommon case. In 64-bit it assumed that both a and b were addressable relative to %rip in either case, so why wouldn't they be addressable relative to the GOT in either case here? Also, why doesn't it use GOTPC relocations to load a and b, which would save the instruction required to add the $_GLOBAL_OFFSET_TABLE_ to the GOT base register?

If I add -fPIC, the 32-bit code changes to the -fcommon variant for both. For 64-bit the code changes, again to the same in both cases, to
movq b@GOTPCREL(%rip), %rax
movq a@GOTPCREL(%rip), %rdx
movl (%rax), %eax
addl (%rdx), %eax

@InBetweenNames
Copy link
Owner

I'm not entirely sure myself -- my first guess is that the codegen part of the compiler and the linker have historically been separate, so if the compiler needed any "link time" features, such as resolving tentative variable definitions in C, it would need to rely on the linker to do that and generate the definition. So, the codegen part of the compiler would have to assume the worst case scenario, which is that the definition is resolved outside of the TU. But I mean, this is just a guess and I'm probably wrong about that haha.

@nivedita76
Copy link
Author

Playing around with it, I notice at least one subtle case where you can silently get the wrong result. If a global variable is declared as:
int x;
in the executable, and
int x = 2;
in a shared library, without -fno-common, x will end up being initialized to 2. With -fno-common, you don't get any error, but x ends up initialized as 0. You only get an error if the int x; appears in at least two translation units in the executable.

@pchome
Copy link
Contributor

pchome commented May 21, 2019

More info in Common-Variable-Attributes common/nocommon and section attributes.

You can force a variable to be initialized with the -fno-common flag

The common attribute requests GCC to place a variable in “common” storage. The nocommon attribute requests the opposite—to allocate space for it directly.

@nivedita76
Copy link
Author

More info in Common-Variable-Attributes common/nocommon and section attributes.

You can force a variable to be initialized with the -fno-common flag

The common attribute requests GCC to place a variable in “common” storage. The nocommon attribute requests the opposite—to allocate space for it directly.

Yeah I've seen that but trying to understand how it actually impacts code generation. At least on x86_64 it doesn't seem to change anything.

@InBetweenNames
Copy link
Owner

It seems to make a difference on ARM architectures. Notably, the flag does break the C standard a bit, but not in a way that would result in incorrect codegen -- just a multiple definition error at compile time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants