LTO renaming of constants with inline assembly

I encountered an issue with ThinLTO handling of inline assembly, where the inline assembly referenced a constant that was a local variable. The local var was renamed because it was promoted in ThinLTO mode, but the inline assembly copy was not renamed and we ended up with an undef at link time.

It looks like this is a general problem with inline assembly and LTO. Wondering if it is a known issue. E.g. if I link in LTO mode two files that have inline assembly referencing local constants with the same name, the LTO linking will rename the second. However, the renaming doesn’t propagate to inline assembly, resulting in the wrong output.

For example, let’s say we have two modules with inline assembly that writes a local constant var named “myvar” into the memory pointed to by its parameter “v”, and a simple main that calls each function:

$ cat inlineasm1.c
static const unsigned char attribute((used)) attribute ((aligned (1))) myvar = 1;

void foo(unsigned long int *v) {
asm volatile(“movzbl myvar(%%rip), %%eax\n\t”
“movq %%rax, %0\n\t”
: “=*m” (*v)
:
: “%eax”
);
}

$ cat inlineasm2.c
static const unsigned char attribute((used)) attribute ((aligned (1))) myvar = 2;

void bar(unsigned long int *v) {
asm volatile(“movzbl myvar(%%rip), %%eax\n\t”
“movq %%rax, %0\n\t”
: “=*m” (*v)
:
: “%eax”
);
}

$ cat inlineasm.c
#include <stdio.h>
extern void foo(unsigned long int *v);
extern void bar(unsigned long int *v);
int main() {
unsigned long int f,b;
foo(&f);
bar(&b);
printf(“%lu %lu\n”, f, b);
}

If compiled at -O2 (no LTO) this correctly prints out “1 2”.

However, when linked with LTO, the second copy of local “myvar” is renamed to “myvar.6”. But the inline assembly which is still hidden within a call that hasn’t been lowered, still references “myvar” in that second linked copy in bar(). The output is thus incorrect: “1 1” (or “2 2” if the bar() copy was linked first).

Is this a known issue? Any ideas on how we could handle this?

Thanks,
Teresa

I suspect that the right way to do promotion/renaming of this sort is to rename at the MC layer just before writing the symbol table to the object file.

Peter

I suspect that the right way to do promotion/renaming of this sort is to
rename at the MC layer just before writing the symbol table to the object
file.

I think that is too late - how would the symbols be distinguished in the
LTO case below after the IR is linked but before we renamed the duplicate?

Teresa

I suspect that the right way to do promotion/renaming of this sort is to
rename at the MC layer just before writing the symbol table to the object
file.

I think that is too late - how would the symbols be distinguished in the
LTO case below after the IR is linked but before we renamed the duplicate?

Sorry, wasn't fully awake. I think we could do something along the lines of
the symbol renaming idea, but with directives that limit their scope to
inline asm blocks.

Specifically, we could teach the frontend to produce a mapping from symbol
names to globalvalues, for any internal names with the used attribute, and
attach that mapping to inline asm blocks.

For example, if myvar were renamed to myvar.6, the IR would look like this:

@myvar.6 = global i8 [...]

[...]

call asm("movzbl myvar(%rip), ...", ..., "myvar")(..., i8* @myvar.6)

The backend would produce assembly that would look like this:

.rename myvar, myvar.6
movzbl myvar(%%rip), ...
.norename myvar

The .rename and .norename directives would delimit the scope of the
renaming.

Peter

I suspect that the right way to do promotion/renaming of this sort is to
rename at the MC layer just before writing the symbol table to the object
file.

I think that is too late - how would the symbols be distinguished in the
LTO case below after the IR is linked but before we renamed the duplicate?

Sorry, wasn't fully awake. I think we could do something along the lines
of the symbol renaming idea, but with directives that limit their scope to
inline asm blocks.

Specifically, we could teach the frontend to produce a mapping from symbol
names to globalvalues, for any internal names with the used attribute, and
attach that mapping to inline asm blocks.

For example, if myvar were renamed to myvar.6, the IR would look like this:

@myvar.6 = global i8 [...]

[...]

call asm("movzbl myvar(%rip), ...", ..., "myvar")(..., i8* @myvar.6)

The backend would produce assembly that would look like this:

.rename myvar, myvar.6
movzbl myvar(%%rip), ...
.norename myvar

The .rename and .norename directives would delimit the scope of the
renaming.

That's an interesting idea, thanks. Are .rename and .norename standard
directives? I did some web searches but couldn't find anything concrete on
them (I did find a .rename in some IBM Power documentation, but it seemed
to apply to string constants.

I think for ThinLTO purposes I will limit importing to/from modules with
inline assembly for now to avoid the issue.

Teresa

I suspect that the right way to do promotion/renaming of this sort is
to rename at the MC layer just before writing the symbol table to the
object file.

I think that is too late - how would the symbols be distinguished in the
LTO case below after the IR is linked but before we renamed the duplicate?

Sorry, wasn't fully awake. I think we could do something along the lines
of the symbol renaming idea, but with directives that limit their scope to
inline asm blocks.

Specifically, we could teach the frontend to produce a mapping from
symbol names to globalvalues, for any internal names with the used
attribute, and attach that mapping to inline asm blocks.

For example, if myvar were renamed to myvar.6, the IR would look like
this:

@myvar.6 = global i8 [...]

[...]

call asm("movzbl myvar(%rip), ...", ..., "myvar")(..., i8* @myvar.6)

The backend would produce assembly that would look like this:

.rename myvar, myvar.6
movzbl myvar(%%rip), ...
.norename myvar

The .rename and .norename directives would delimit the scope of the
renaming.

That's an interesting idea, thanks. Are .rename and .norename standard
directives? I did some web searches but couldn't find anything concrete on
them (I did find a .rename in some IBM Power documentation, but it seemed
to apply to string constants.

These would be new directives that we'd need to implement. Perhaps if
they're already being used elsewhere we can come up with a sufficiently
unique name.

I think for ThinLTO purposes I will limit importing to/from modules with

inline assembly for now to avoid the issue.

Sounds reasonable.

Peter

I still wonder if this would be an issue in standard (not thin) LTO?

This test seems to be OK on my (slightly modified) standard LTO flow, but I do wonder for a more general case.

Sergei

Hi Sergei,

The example was with standard (not thin) LTO.

How are the duplicate static myvar symbols handled when they are linked in your modified LTO flow? Normally module linking in LTO will rename the second to avoid a symbol name conflict, which provokes the issue described here.

(I originally started looking into this because I found it in ThinLTO, but I have a ThinLTO-specific workaround proposed in D18986).

Teresa

My asm is obviously different… Let me make sure I got it adopted right…

Teresa,

Yes, I see it now(had to change the test a bit…):

If I use “myvar” explicitly in the asm body:

void foo(unsigned long int *v) {

asm volatile(

“r2 = memw(#myvar)\n\t”

“memw(%0+#0) = r2\n”

: “=*m” (*v)

: “r” (myvar)

: “r2”);

}

I will got into trouble (it is still a valid code even if I want to shoot myself in the foot).

@myvar = internal constant i8 1, align 1

@myvar.1 = internal constant i8 2, align 1

define void @foo(i32* %v) #0 {

%0 = load volatile i8, i8* @myvar, align 1

call void asm sideeffect “r2 = memw(#myvar)\0A\09memw($0+#0) = r2\0A”, "=m,r,~{r2}"(i32 %v, i8 %0) #1, !srcloc !5

define void @bar(i32* %v) #0 {

%0 = load volatile i8, i8* @myvar.1, align 1

call void asm sideeffect “r2 = memw(#myvar)\0A\09memw($0+#0) = r2\0A”, "=m,r,~{r2}"(i32 %v, i8 %0) #1, !srcloc !6

But if I do something like this:

void foo(unsigned long int *v) {

asm volatile(

“r2 = memw(%1)\n\t”

“memw(%0+#0) = r2\n”

: “=*m” (*v)

: “r” (myvar)

: “r2”);

}

Then renaming still takes place, but it is OK.

…so answering my own question – yes, this could be an issue.

Peter’s proposal should address it.

Sergei