[RFC][LLVM] New Constant type for representing function PLT entries

Hi all,

We would like to propose a new Constant type in LLVM for representing entries in the Procedure Linkage Table (PLT).

The PLT is a data structure used for dispatching position-independent function calls to appropriate functions where the address of the function is not known statically. Right now, if a call is made to a function, it may be lowered to a direct call to the function itself or the PLT entry for that function. LLVM has checks that dictate if the function call should be a direct reference or PLT entry, but we don’t have a way in IR to semantically represent that the PLT entry should be used.

The proposed constant would be analogous to BlockAddress, but instead represent the PLT entry for functions. The usage could look something like:

pltentry(@function)

and would always have the same type as the function. A pltentry would operate exactly like a function, but the main difference is that it’s lowered to the PLT entry (function@plt) on targets that support PLT relocations. The linker can then decide if it should be relaxed into a direct reference or remain a PLT entry.

I have a very rough WIP implementation at https://reviews.llvm.org/D77248.

Thoughts and opinions?

Thanks,

Leonard

Hi, Leonard,

What are the motivating use cases?

-Hal

Hi, Leonard,

What are the motivating use cases?

-Hal

The immediate use case for us specifically would be a way to explicitly request static PLT relocations. We rolled out a new relocation for AArch64 (R_AARCH64_PLT32) which takes the offset between the PLT entry for a function and some other symbol. In IR, we can then explicitly request this relocation with pltentry(@function) - @global and distinguish it from @function - @global which may or may not lower to R_AARCH64_PREL32 if both symbols are dso_local. This is especially useful for the relative vtables ABI where we can benefit in size by not having to emit dso_local stubs for some functions.

In the general case, this just adds a way for semantically representing PLT entries for functions and would ideally add more “correctness”. From an IR perspective, users won’t need to second-guess “what will this function be lowered to”.

    Hi, Leonard,

    What are the motivating use cases?

     -Hal

The immediate use case for us specifically would be a way to explicitly request static PLT relocations. We rolled out a new relocation for AArch64 (R_AARCH64_PLT32 <https://reviews.llvm.org/D81184>) which takes the offset between the PLT entry for a function and some other symbol. In IR, we can then explicitly request this relocation with `pltentry(@function) - @global` and distinguish it from `@function - @global` which may or may not lower to R_AARCH64_PREL32 if both symbols are dso_local. This is especially useful for the relative vtables ABI <https://reviews.llvm.org/D72959> where we can benefit in size by not having to emit dso_local stubs for some functions.

Okay. So the idea is that Clang and other frontends will emit them directly as parts of certain constant expressions inside of certain data structures (e.g., vtables). That makes sense.

-Hal

Hi Leonard,

I haven’t looked at your patch in detail, but I think that this is a step in the right direction. I would like to see new “Constant*”’s that directly map onto the relocations that various object formats use (for example, macho has a relocation for “&g1-&g2+cst”). This would get us to a more principled lowering in many cases as well as make the backend modeling more specific.

-Chris

I do have concerns about the amount of object level modeling that we want to do in the IR though. While it isn’t the highest level IR we’ve managed to mostly avoid these kinds of features/complications in the past. I’m definitely interested in hearing some alternate implementations here and there rather than a full set of constants for relocations. Keeping the IR abstract enough over the object file level other than in generalizable cases still feels like a win.

-eric

I do have concerns about the amount of object level modeling that we want
to do in the IR though. While it isn't the highest level IR we've managed
to mostly avoid these kinds of features/complications in the past. I'm
definitely interested in hearing some alternate implementations here and
there rather than a full set of constants for relocations. Keeping the IR
abstract enough over the object file level other than in generalizable
cases still feels like a win.

-eric

Hi Leonard,

I haven’t looked at your patch in detail, but I think that this is a step
in the right direction. I would like to see new “Constant*”’s that
directly map onto the relocations that various object formats use (for
example, macho has a relocation for “&g1-&g2+cst”). This would get us to a
more principled lowering in many cases as well as make the backend modeling
more specific.

-Chris

Hi all,

We would like to propose a new Constant type in LLVM for representing
entries in the Procedure Linkage Table (PLT).

The PLT is a data structure used for dispatching position-independent
function calls to appropriate functions where the address of the function
is not known statically. Right now, if a call is made to a function, it may
be lowered to a direct call to the function itself or the PLT entry for
that function. LLVM has checks that dictate if the function call should be
a direct reference or PLT entry, but we don’t have a way in IR to
semantically represent that the PLT entry should be used.

The proposed constant would be analogous to BlockAddress, but instead
represent the PLT entry for functions. The usage could look something like:

pltentry(@function)

and would always have the same type as the function. A pltentry would
operate exactly like a function, but the main difference is that it’s
lowered to the PLT entry (function@plt) on targets that support PLT
relocations. The linker can then decide if it should be relaxed into a
direct reference or remain a PLT entry.

I have a very rough WIP implementation at ⚙ D77248 [llvm][IR] Add dso_local_equivalent Constant.

Thoughts and opinions?

Thanks,
Leonard

Note that there is some terminology misnomer or concept confusion here.

A @plt modifer (x86-32, x86-64 and aarch64) in assembly refers to a
function whose address can be insignificant. The assembler produces an
R_386_PLT32/R_X86_64_PLT32/R_AARCH64_PLT32 relocation which will be
resolved by the linker to either:

* the definition (non-preemptible (logical AND (non-ifunc or ld.lld -z ifunc-noplt is specified)))
* a PLT entry (other cases)

The address can be insignificant: ultimately the program will call the
function. There is no difference if the program calls the function
directly or calls through one PLT entry in any module (executable or a
shared object).

R_386_PC32/R_X86_64_PC32/R_AARCH64_PREL32

The proposd 'pltentry' syntax is an IR level concept to lower the
address of constant to use the @plt modifier.

A Procedure Linkage Table (PLT) entry may or may not be created by the
linker - if it is not necessary, the linker will not create it. I think
we do need some way to represent this in IR, but I hope we can find a
better name for this concept. Many will think "PLT is a pure linker
synthesized entry - why do we bother calling some compiler-generated
stuff PLT". The @plt assembly syntax is a bit unfortunate as well - I
suggested it anyway in ⚙ D81184 [lld][ELF][AArch64] Handle R_AARCH64_PLT32 relocation because its x86
counterpart had used this concept for many years.

The compiler should not assume that a Function will be lowered to the
PLT entry for it, so we can use this instead to semantically
represent in LLVM that a PLT should be used.

The "PLT entry" part of the sentence is not entirely correct.

+1 to Eric’s point. We could get into a state where we must get the relocation right in the IR to get a good (or worse, any) lowering.

I’d rather have a canonical representation, relocation friendly, that we try to keep and back ends know how to lower well.

–renato

+1 to Eric's point. We could get into a state where we must get the relocation right in the IR to get a good (or worse, any) lowering.

I'd rather have a canonical representation, relocation friendly, that we try to keep and back ends know how to lower well.

--renato

FWIW, I think that we all agree to that. I don't see that what's being proposed changes this general philosophy. The general problem is: what happens when that lowering has multiple good options, and the ABI requires particular choices under certain circumstances?

Thanks again,

Hal

FWIW, I think that we all agree to that. I don’t see that what’s being proposed changes this general philosophy. The general problem is: what happens when that lowering has multiple good options, and the ABI requires particular choices under certain circumstances?

If this is decided from the front-end and needs to survive all the way to the back-end, why not just add a generic function attribute?

Perhaps I’m interpreting this the wrong way, but adding a special (independent) entry to the IR that hints at an ABI correctness seems fragile to me.

–reanto

    FWIW, I think that we all agree to that. I don't see that what's
    being proposed changes this general philosophy. The general
    problem is: what happens when that lowering has multiple good
    options, and the ABI requires particular choices under certain
    circumstances?

If this is decided from the front-end and needs to survive all the way to the back-end, why not just add a generic function attribute?

But it's not a property of the functions, as I understand it, it's a function of place where the function is referenced. In this case, maybe we could consider it a property of the vtable itself, and some attribute on the array/global would work?

Perhaps I'm interpreting this the wrong way, but adding a special (independent) entry to the IR that hints at an ABI correctness seems fragile to me.

This is a first-class IR entity. It seems the opposite of fragile. The interesting question is going to be: do we have a wile bunch of other code that generally handles constant expressions that needs to learn about this new kind of function reference?

-Hal

But it’s not a property of the functions, as I understand it, it’s a function of place where the function is referenced. In this case, maybe we could consider it a property of the vtable itself, and some attribute on the array/global would work?

I see.

This is a first-class IR entity. It seems the opposite of fragile.

I didn’t mean passes could remove them, like metadata. I mean the front-end will have to assume all entries point to actual functions that don’t get deleted half-way through (LTO? global analysis?), meaning the PLT entry will have to be taught to some optimisations anyway.

The interesting question is going to be: do we have a wile bunch of other code that generally handles constant expressions that needs to learn about this new kind of function reference?

Indeed.

From: llvm-dev <llvm-dev-bounces@lists.llvm.org> On Behalf Of Fangrui
Song via llvm-dev
Sent: Thursday, August 20, 2020 10:18 PM
To: Leonard Chan <leonardchan@google.com>
Cc: llvm-dev <llvm-dev@lists.llvm.org>
Subject: [EXT] Re: [llvm-dev] [RFC][LLVM] New Constant type for
representing function PLT entries

A @plt modifer (x86-32, x86-64 and aarch64) in assembly refers to a
function whose address can be insignificant. The assembler produces an
R_386_PLT32/R_X86_64_PLT32/R_AARCH64_PLT32 relocation which will be
resolved by the linker to either:

* the definition (non-preemptible (logical AND (non-ifunc or ld.lld -z ifunc-
noplt is specified)))
* a PLT entry (other cases)

The address can be insignificant: ultimately the program will call the
function. There is no difference if the program calls the function
directly or calls through one PLT entry in any module (executable or a
shared object).

R_386_PC32/R_X86_64_PC32/R_AARCH64_PREL32

Ignoring the ELF-specific bits, in essence, it's some dso-local function that's functionally equivalent to the actual resolved function at runtime. The address may or may not be equal to that of the resolved function.

Maybe it would make sense to introduce a GlobalValue to represent this, along the lines of GlobalIFunc? I guess the end result isn't a lot different from the original proposal: you still end up with a Constant that represents the PLT entry. But I think it would fit more smoothly into existing optimizations that understand GlobalValues. And it would make it clear that importing one from another IR module might be a non-trivial operation. (I don't think we actually do cross-DSO optimizations at the moment, but it's not outside the realm of possibility.)

-Eli

> From: llvm-dev <llvm-dev-bounces@lists.llvm.org> On Behalf Of Fangrui
> Song via llvm-dev
> Sent: Thursday, August 20, 2020 10:18 PM
> To: Leonard Chan <leonardchan@google.com>
> Cc: llvm-dev <llvm-dev@lists.llvm.org>
> Subject: [EXT] Re: [llvm-dev] [RFC][LLVM] New Constant type for
> representing function PLT entries
>
> A @plt modifer (x86-32, x86-64 and aarch64) in assembly refers to a
> function whose address can be insignificant. The assembler produces an
> R_386_PLT32/R_X86_64_PLT32/R_AARCH64_PLT32 relocation which will be
> resolved by the linker to either:
>
> * the definition (non-preemptible (logical AND (non-ifunc or ld.lld -z ifunc-
> noplt is specified)))
> * a PLT entry (other cases)
>
> The address can be insignificant: ultimately the program will call the
> function. There is no difference if the program calls the function
> directly or calls through one PLT entry in any module (executable or a
> shared object).
>
> R_386_PC32/R_X86_64_PC32/R_AARCH64_PREL32

Ignoring the ELF-specific bits, in essence, it's some dso-local function that's functionally equivalent to the actual resolved function at runtime. The address may or may not be equal to that of the resolved function.

Agree.

Maybe it would make sense to introduce a GlobalValue to represent this, along the lines of GlobalIFunc? I guess the end result isn't a lot different from the original proposal: you still end up with a Constant that represents the PLT entry. But I think it would fit more smoothly into existing optimizations that understand GlobalValues. And it would make it clear that importing one from another IR module might be a non-trivial operation. (I don't think we actually do cross-DSO optimizations at the moment, but it's not outside the realm of possibility.)

-Eli

A new subclass of GlobalIndirectSymbol? Looks fine. Do you think the
name "plt" should be moved from the name of the subclass to an
argument of the syntax (like alias/ifunc)?

Sure, I also don’t like unnecessary complexity. Let me try to motivate this along two different points: expressivity, and simplicity.

On Expressivity, LLVM IR in general provides an abstract representation that allows frontends to generate target independent(ish!) IR, instead of having to understand all of the target details, e.g. you shouldn’t have to know the target to get an integer add. However, the model is really that we provide both a target independent model and a target-specific model. You can see this in calling conventions, you can see this in target-specific intrinsics, you can see this in inline assembly. In my opinion these are all good examples of LLVM providing target-independent abstractions, while still allowing frontends to get to the full capability of the hardware/architecture/target. Making addressing modes for relocations consistent with that seems like a good thing.

On simplicity, I’ve mentioned before that the right thing is to redesign ConstantExpr entirely, eliminating most of the operations. “Trapping” constants (like divide) is a huge bug in the representation, and is only there for historical reasons. When you get beyond the fundamental integer and FP constants, the only reason we have aggregates (ConstantArray) and ConstantExpr is to enable global variable initializers that contain relocations. Things like constants (and particularly constant exprs) in PHI nodes are persistent problems at the LLVM IR level that would be better defined away. As such, a reasonable design for constants would be “fundamental constants within function bodies; possibly using an MLIR like representation to define away even these” and “global variable initializers”. Note that there is no specific reason that “global variable initializers” be Value*’s.

In any case, I think the first thing is motivation alone. Doing so would be a stepping stone to get rid of all the crazy backend logic that tries to match "gv-gv+cst” etc, instead allowing this to be done on the IR level.

-Chris

I don’t think this is a good idea. GlobalValues are meant to represent “symbols”, and a PLT entry is not a symbol. Furthermore, this would be a special type of GlobalIndirectSymbol that would not require the referent to be defined (as GlobalAlias and GlobalIFunc do). So I don’t see this fitting smoothly into existing code, in fact I suspect that more code would need to be adjusted to handle PLT entries as a special case than if we just made it a Constant.

Fundamentally this is more similar to ConstantExpr than anything under GlobalValue, but since the operand is required to be a GlobalValue and there would be no corresponding Instruction, the right place to put it is under Constant IMHO.

Peter

The IR concept could be generalized so it doesn't actually depend on the object format having a PLT. For example, it could be used to refer to an import thunk on Windows. So I'm not sure we want to name the IR concept after a "PLT".

LLVM IR currently has the dso_local concept, so maybe we want to leverage that in the name. Maybe something like GlobalDSOLocalFunc? (Not sure I'm really happy with that exact name, but hopefully the idea makes sense.)

-Eli

Thanks for the responses! I’m going to see if I can summarize the concerns and ideas people have (for my own clarity) and see where we can go on from there. Folks seem to be on board with the idea of introducing some new IR entity that (after linking) could be a reference into the PLT, but some kinks need to be worked out first:

Naming (Thanks for clarifications maskray@. I mixed up some terminology and concepts.): Because the PLT is primarily the concern of the linker, the naming probably shouldn’t be directly tied to “PLT”. The initial proposal was for something that matched the @plt modifier on x86, so that’s what inspired the naming. The intended behavior of this IR level change is that at least on x86 or aarch64, the resolved constant could be lowered to something that has the @plt syntax, but I suppose other targets could have their own meaning for “the address of this function is insignificant.”

Abstraction: The IR representation of this probably shouldn’t be too strictly mapped to object file representations. It’s useful to have an IR pattern that can be mapped to relocations on different binary formats, but we don’t want to introduce a state where we have new Constants for individual relocations. The IR-entity should remain abstract enough that it’s not tied to a specific relocation, but it can still be lowered appropriately by different backends.

As an update to the proposal, instead of pltentry(@func), we can call it something like unnamedfunc(@func) and everywhere it’s used, it means: “The value used here is functionally equivalent to the original function, but may not be a reference to the original function. The address of this value is insignificant.” This is leveraged from unnamed_addr where the address of a global variable is insignificant, but this would instead be tied to instances where the function is used rather than be attached to the function declaration/definition. unnamedfunc(@func) could be lowered to a direct reference (func), the @plt modifier on x86/aarch64 (func@plt), a thunk, or anything that’s equivalent to the resolved function at runtime.

Implementation-wise, I imagine we don’t want this as a subclass of GlobalValue. As Peter suggested, this may not eventually lower to a symbol. If it were a GlobalValue, that would also imply linkage types and visibility would also apply to it which might not make sense. A GlobalValue also seems to imply a module-level entity when this would primarily be used on individual locations where a function would normally be used.

Is there anything else that should be addressed? Hopefully this addresses some concerns.

Does anyone have more comments? Otherwise, I’ll go ahead and update my patch to reflect my update.

Seems fine. I’m not that attached to my GlobalValue idea.

-Eli