[PATCH] D19995: Optimize access to global variable references in PIE mode when linker supports copy relocations for PIE

Sriraman Tallam via llvm-commits llvm-commits at lists.llvm.org
Fri May 6 10:37:59 PDT 2016


tmsriram added a comment.

In http://reviews.llvm.org/D19995#423481, @rafael wrote:

> Sorry for the long reply, but I think it is critical we get this right.
>
> When a variable/function is defined in a shared library, either the library or the main program has to use a got. The other one can then use direct accesses.


This seems partially correct as it does not addess this case. Let's say the shared library and the executable define the global variable but initialize it differently.  Then, if the shared library does not use the GOT it is not possible to suddenly create a copy relocation in the shared library (that is already built first and is also used by other executables) and you are now accessing a global variable with the incorrect initialization.  The global definition in the executable must take precedence and that is not happening here.

> In every non-elf system, access from inside the library are direct (lea foo(%rip)) and from the main binary indirect (mov foo at GOTPCREL(%rip)). The rationale being that access from within the library is more common and normally the library wants the guarantee that its own functions/variables are not preempted. I think it is critical to support something like this in ELF.

> 

> On ELF, quite unfortunately IMHO, the default is to allow preemption. If something can be preempted, you need to use a got, if you need to use a got the main executable may as well create a copy relocation and at least it will be able to use a lea.


Right, this is the rationale for this patch.  It is also based on the simple observation that the executable's definition takes precedence.  Another way of looking at this - this is what exactly happens in non-PIE mode.  Why not preserve the same behavior in PIE mode?

> So the first question is: In your testcase/benchmark, is preemption required or is the variable/function more frequently accessed from the main binary than from the library that defines it? If not, you would probably get even better performance by disallowing preemption and have the library access its own symbols directly and main binary uses a got.


For us atleast, we have the case that most of the globals end up in the executable and we do not want to conservatively use the GOT  because it was declared external in the modules accessing it.  In non-PIE mode, which has the identical problem, copy relocations are used.  Extend that to PIE mode.

I understand that the extra option '-mpiecopyrelocs' makes this complicated.  This is only to support linkers that do not support copy relocations for PIE yet. At some point, when linkers support this, this option can be deleted and this behavior made the default.

> If you do have a case where the main binary is the one with the most frequent accesses (or preemption is required), I think that is fine. With ELF we should be able to support both cases, we just have to be really careful.


Yes, this is our case and that is why we are really interested in solving this.

> Right now the llvm support for preemption is also really bad. We will pretend the symbol cannot be preempted and then at the last minute use a got.


For global variables atleast, with ELF and X86, llvm support seems identical to GCC support regarding symbol preemption.

> So lets start from the IR up and see how we can represent all cases. It would be particularly desirable that:

> 

> - The access type can be decided from looking just at the GV, not the module or codegen flags. The codegen flag would be just for "mov $foo or lea foo(%rip)", not for using a GOT or not.

> - The IR linker just works.

> - The same IR has the same semantics in COFF, MachO and ELF.

> - Simpler IR is used for the common cases.

> - Keep the property that one only has to look at the linkage to decide if a function can be inlined or not (thanks to John McCall for suggesting this one).

> - A visibility attribute in C ends up with that visibility in the .o.

> 

>   Lets first consider a how to design a IR that provides that and then see how to map C and command line options to it.  We need IR to cover 4 cases

> - Direct access to definition.

> - GOT access to definition.

> - Direct access to declaration.

> - GOT access to declaration.

> 

>   I propose that the IR we use is:

> 

>   For ``` @a = global i32 42 define i32* @f() { ret i32* @a } ``` we know @a cannot be preempted, so we produce

> 

>   ``` leaq a(%rip), %rax ```

> 

>   For ``` @a = external global i32 define i32* @f() { ret i32* @a } ``` we don't know where @a is, so we produce ``` movq a at GOTPCREL(%rip), %rax ```

> 

>   For ``` @a = preemptable global i32 42 define i32* @f() { ret i32* @a } ``` we have a new linkage type to mark a definition as preemptable.  GVs with preemptable linkage must have default visibility. We produce: ``` movq a at GOTPCREL(%rip), %rax ```

> 

>   For ``` @a = external_local global i32 define i32* @f() { ret i32* @a } ``` We have a new linkage for assuming a declaration is in the current dso.  We produce ``` leaq a(%rip), %rax ```

> 

>   For ``` @a = protected external_local global i32 define i32* @f() {  ret i32* @a } ``` We produce ``` leaq a(%rip), %rax .protected a ``` Declarations with non-default visibility are required to be external_local.

> 

>   New, lets see how the various file formats would map from C to the above:

> 

>   ELF:

> - If there is a non-default visibility attribute:

>   - Declarations become extern_local if the visibility is not default

>   - Use the visibility

> - -fPIC. Symbols can be preemepted, so use the preemptable linkage.

> - No option or -fPIE: Symbols cannot be preempted:


Maybe you covered this case else where, but we are really interested in extern symbol access with -fPIE.  Whether it is really external or not at link time is the right question.  It is hard to answer that at compile time and too conservative to commit to GOT access.

>   - A dllimport declartion is external, others are external_local.

> 

>     MachO:

> - -fPIC: Just like today.

> - static (is that supported?): declarations are external_local.

> 

>   COFF:

> - A dllimport declaration is external, others are external_local.

> 

>   Sorry, I know that this is a lot more work, but I am willing to help since we have to fix this representation deficiency once and for all. I think the steps would be

> - Introduce the preeptible and extern_local linkages and verifier checks for them.

> - Codegen them as defined above, regardless of -relocation-model.

> - Change clang to use the new translation.

> - Upgrade declarations with non default visibility to extern_local.

> - Verify that all non default visibility declarations are extern_local.

> - Consolidate all of the pic/pie into a single option that says if the code is position independent or not. The only difference is using "mov $foo" or "lea foo(%rip)"



http://reviews.llvm.org/D19995





More information about the llvm-commits mailing list