This is an archive of the discontinued LLVM Phabricator instance.

Re-enable a hook in MCELFObjectTargetWriter to allow target-specific relocation table sorting and use this hook for Mips.
ClosedPublic

Authored by vstefanovic on Feb 4 2015, 10:58 AM.

Details

Summary

Some targets (ie. Mips) have additional rules for ordering the relocation table
entries. Allow them to override generic sortRelocs(), which sorts entries by
Offset.
Then override this function for Mips, to emit HI16 and GOT16 relocations against
the local symbol in pair with the corresponding LO16 relocation.

Diff Detail

Repository
rL LLVM

Event Timeline

vstefanovic retitled this revision from to Re-enable a hook in MCELFObjectTargetWriter to allow target-specific relocation table sorting and use this hook for Mips..
vstefanovic updated this object.
vstefanovic edited the test plan for this revision. (Show Details)
vstefanovic added reviewers: rafael, dsanders.
vstefanovic set the repository for this revision to rL LLVM.
petarj added subscribers: Unknown Object (MLST), petarj.Feb 4 2015, 4:18 PM

FYI, this is also needed for assembling Linux for MIPS with the IAS.

rafael edited edge metadata.Feb 5 2015, 8:57 AM

Please check the excellent description Simon gave of what are the restrictions in the thread about r205200.

From that thread, my understanding of what is odd about mips objects in this case is that a a lo relocation (R_MIPS_LO16, R_MIPS16_LO16, R_MICROMIPS_LO16, R_MICROMIPS_HI0_LO16) matches *all* preceding hi/got relocations (R_MIPS_GOT16, R_MIPS16_GOT16, R_MICROMIPS_GOT16, R_MIPS_HI16, R_MIPS16_HI16, R_MICROMIPS_HI16).

Now, what is the semantic of the assembly file? From this code it looks like it is that a lo relocation would match *any* hi relocation with the same symbol if one is present. The search is done on an unordered relocation array, so which one it matches is undefined. This would still be an issue since the mips output will now be non-deterministic, no?

It that is really the desired behavior, you could just

  • Sort by symbol.
  • If same symbol, sort by type to put HI before LO
  • sort by whatever else to make the output deterministic.

The current implementation is also N^2.

It should handle (and test) all relevant relocations.
It should include a large comment explaining

  • What are the semantics of the .o (i.e., how will the linker handle them. Something along the lines of Simon's description)
  • What are the semantics of the .s files.
  • What the code is doing to match one to the other.

Hi Rafael, thanks for the link and the review.

About the relocations that must match another:

-mips32, mips64:
R_MIPS_HI16 and local R_MIPS_GOT16 must match R_MIPS_LO16 against the same
symbol.

-micromips:
R_MICROMIPS_HI16 and local R_MICROMIPS_GOT16 must match R_MICROMIPS_LO16
against the same symbol.

-mips16:
R_MIPS16_HI16 and local R_MIPS16_GOT16 must match R_MIPS16_LO16 against the same
symbol.

Now, since mips16 and micromips are a work in progress, I would rather skip
handling them for now (ie. return generic sortRelocs() and add a TODO for the
HI16/GOT16 exceptions). E.g., instead of R_MICROMIPS_LO16, llvm currently
generates R_MIPS_LO16, so I can't even add a micromips test that will pass at
the moment.

For the deterministic output - in the examples I ran, when entering sortRelocs()
relocs were sorted by offset in the ascending order. But I will add a call to
generic sortRelocs() at the beginning of the function, to make the output
deterministic for sure.

And, apart from HI16/GOT16, I would like to sort relocs by offset - like other
architectures and mips gcc do.

A quote from binutils source (binutils/bfd/elfxx-mips.c):

The ABI requires that the *LO16 immediately follow the *HI16.
However, as a GNU extension, we permit an arbitrary number of
*HI16s to be associated with a single *LO16.

The logic I used in the code below is the simplest I came up with to obey the
rule above: for every HI16 / local GOT16 relocation at the given offset, pair it
with the first found LO16 relocation against the same symbol, starting from
offset + 4 and ending at offset -4. (Wrap around reloc table size.)

GCC does it differently; here is a comment about it from
binutils/gas/config/tc-mips.c:

When several %lo()s match a particular %got() or %hi(), we use the
following rules to distinguish them:

  (1) %lo()s with smaller offsets are a better match than %lo()s with
      higher offsets.

  (2) %lo()s with no matching %got() or %hi() are better than those
      that already have a matching %got() or %hi().

  (3) later %lo()s are better than earlier %lo()s.
These rules are applied in order.

Thus, for this example:

lui    $2, %hi(func2)
lui    $2, %hi(func2)
addiu  $2, $2, %lo(func2)
addiu  $2, $2, %lo(func2)

the code below sorts the table like this:
Offset Info Type Sym.Value Sym. Name
00000000 00000605 R_MIPS_HI16 00000000 func2
00000004 00000605 R_MIPS_HI16 00000000 func2
00000008 00000606 R_MIPS_LO16 00000000 func2
0000000c 00000606 R_MIPS_LO16 00000000 func2

and this is what gcc does:
Offset Info Type Sym.Value Sym. Name
00000004 00000605 R_MIPS_HI16 00000000 func2
00000008 00000606 R_MIPS_LO16 00000000 func2
00000000 00000605 R_MIPS_HI16 00000000 func2
0000000c 00000606 R_MIPS_LO16 00000000 func2

So, at least for consistency reasons, maybe I should change this code to behave
like gcc. What do you think?

About the relocations that must match another:

-mips32, mips64:
R_MIPS_HI16 and local R_MIPS_GOT16 must match R_MIPS_LO16 against the same
symbol.

-micromips:
R_MICROMIPS_HI16 and local R_MICROMIPS_GOT16 must match R_MICROMIPS_LO16
against the same symbol.

-mips16:
R_MIPS16_HI16 and local R_MIPS16_GOT16 must match R_MIPS16_LO16 against the same
symbol.

And it is not possible for a file to mix relocations from these sets? I.E, no file will ever have a R_MIPS16_LO16 and a R_MIPS_HI16? If that is not the case I think there is a bug in gold :-)

Now, since mips16 and micromips are a work in progress, I would rather skip
handling them for now (ie. return generic sortRelocs() and add a TODO for the
HI16/GOT16 exceptions). E.g., instead of R_MICROMIPS_LO16, llvm currently
generates R_MIPS_LO16, so I can't even add a micromips test that will pass at
the moment.

That is fine. An assert would be better than a comment to make sure it gets implemented :-)

For the deterministic output - in the examples I ran, when entering sortRelocs()
relocs were sorted by offset in the ascending order. But I will add a call to
generic sortRelocs() at the beginning of the function, to make the output
deterministic for sure.

Yes, I honestly cannot remember where the non determinism was coming from, but I do remember the original function sorting only by Offest and we having issues.

And, apart from HI16/GOT16, I would like to sort relocs by offset - like other
architectures and mips gcc do.

MC suffer a lot from trying to produce object files that look like what gas produces. I know this is mostly my fault, but if I can help us move to a point where every 'if' in the writer has a good justification that would be awesome.

A quote from binutils source (binutils/bfd/elfxx-mips.c):

The ABI requires that the *LO16 immediately follow the *HI16.
However, as a GNU extension, we permit an arbitrary number of
*HI16s to be associated with a single *LO16.

Check. It seems to be what gold implements.

The logic I used in the code below is the simplest I came up with to obey the
rule above: for every HI16 / local GOT16 relocation at the given offset, pair it
with the first found LO16 relocation against the same symbol, starting from
offset + 4 and ending at offset -4. (Wrap around reloc table size.)

That is something that needs to be explicitly documented, since it is the semantic of the of assembly file, which is potentially an user input.

GCC does it differently; here is a comment about it from
binutils/gas/config/tc-mips.c:

When several %lo()s match a particular %got() or %hi(), we use the
following rules to distinguish them:

  (1) %lo()s with smaller offsets are a better match than %lo()s with
      higher offsets.

  (2) %lo()s with no matching %got() or %hi() are better than those
      that already have a matching %got() or %hi().

  (3) later %lo()s are better than earlier %lo()s.
These rules are applied in order.

Thus, for this example:

lui    $2, %hi(func2)
lui    $2, %hi(func2)
addiu  $2, $2, %lo(func2)
addiu  $2, $2, %lo(func2)

the code below sorts the table like this:
Offset Info Type Sym.Value Sym. Name
00000000 00000605 R_MIPS_HI16 00000000 func2
00000004 00000605 R_MIPS_HI16 00000000 func2
00000008 00000606 R_MIPS_LO16 00000000 func2
0000000c 00000606 R_MIPS_LO16 00000000 func2

and this is what gcc does:
Offset Info Type Sym.Value Sym. Name
00000004 00000605 R_MIPS_HI16 00000000 func2
00000008 00000606 R_MIPS_LO16 00000000 func2
00000000 00000605 R_MIPS_HI16 00000000 func2
0000000c 00000606 R_MIPS_LO16 00000000 func2

So, at least for consistency reasons, maybe I should change this code to behave
like gcc. What do you think?

I agree. We should implement the same *semantics* as gas.

In the above comment, what does "match", "earlier" and "in order" mean?

vstefanovic edited edge metadata.
vstefanovic removed rL LLVM as the repository for this revision.
vstefanovic set the repository for this revision to rL LLVM.

A new version of the patch is uploaded.

(Some comments disappeared from the previous message.)

And it is not possible for a file to mix relocations from these sets? I.E, no file will ever have a R_MIPS16_LO16 and a R_MIPS_HI16? If that is not the case I think there is a bug in gold :-)

Well, you can have this in a .S file:

lui  $2,%hi(sym1)

.set mips16

li   $2,%lo(sym1)

Then relocations would be:
00000000 00000605 R_MIPS_HI16 00000000 sym1
00000004 00000669 R_MIPS16_LO16 00000000 sym1

so the first relocation wouldn't match the second.

In the above comment, what does "match", "earlier" and "in order" mean?

'match' for a *HI16 relocation is a matching LO16 relocation, ie. the one
against the same symbol and with the appropriate *LO16 relocation type.
'earlier' means smaller offset.
'in order' is similar to our cmpRel() sorting function - apply the first rule,
and if the output isn't deterministic yet, apply the second one, etc.

rafael added inline comments.Mar 13 2015, 8:56 AM
lib/Target/Mips/MCTargetDesc/MipsELFObjectWriter.cpp
29 ↗(On Diff #21582)

Please use the same names for arguments and fields so that you have
... : foo(foo), bar(bar) ... {

33 ↗(On Diff #21582)

Can this be a

const ELFRelocationEntry &Reloc;

?

39 ↗(On Diff #21582)

Why do you need to store this?

278 ↗(On Diff #21582)

Why is that?

279 ↗(On Diff #21582)

Distributing the ! is probably more readable:

Reloc.Type != ... && Reloc.Type !=

also, put a

unsigned Type = Roloc.Type;

at the start of the function so you don't have to type" Reloc." all the time.

285 ↗(On Diff #21582)

Use an earlier return

if (MCELF::GetBinding(SD) != ELF::STB_LOCAL)

return ELF::R_MIPS_NONE;
296 ↗(On Diff #21582)

Start variable names with an upper case letter.

309 ↗(On Diff #21582)

Why -1?
This looks like a leftover from the sort function not considering a relocation own offset to break ties.

335 ↗(On Diff #21582)

This wording is a bit soft.

Assembly is user input, and if gnu as matches a %lo and a %hi differently from what we do we would be producing a .o with different semantics.

344 ↗(On Diff #21582)

This description makes it sound like that given

lui $2, %hi(func2)
lui $2, %hi(func2)
addiu $2, $2, %lo(func2)
addiu $2, $2, %lo(func2)

the second %hi would be matched with the second %lo, but in a previous comment you said that gas produces

00000004 00000605 R_MIPS_HI16 00000000 func2
00000008 00000606 R_MIPS_LO16 00000000 func2
00000000 00000605 R_MIPS_HI16 00000000 func2
0000000c 00000606 R_MIPS_LO16 00000000 func2

That is, the second %hi (offset 4) is matched with the first lo (offset 8).

353 ↗(On Diff #21582)

Please git-clang-format the patch.

366 ↗(On Diff #21582)

Why is this first check for I and I-1 necessary? The main loop should handle it, no?

test/MC/Mips/sort-relocation-table.s
6 ↗(On Diff #21582)

Can you add a comment about the gnu as target and command line that can be used to compare the result of this file?

vstefanovic added inline comments.Mar 13 2015, 5:25 PM
lib/Target/Mips/MCTargetDesc/MipsELFObjectWriter.cpp
29 ↗(On Diff #21582)

OK.

33 ↗(On Diff #21582)

It can be const, but '&' would bring down the last step, which copies sorted MipsRelocs back to Relocs.

39 ↗(On Diff #21582)

It isn't necessary, I can always call getMatchingLoType() instead, which I did in the updated patch.

278 ↗(On Diff #21582)

Just to skip calling Asm.getSymbolData() for the relocations that will return R_MIPS_NONE anyway. I removed it.

285 ↗(On Diff #21582)

OK.

309 ↗(On Diff #21582)

'-1' simplifies the sort function.

If omitting '-1', the rules for entries with the same SortOffset would be:
-if one is %hi, that one should be first;
-if both are %hi, sort them by Offset (in descending order).

So '-1' practically applies the first rule, and the sorting function only needs to handle the second case.

335 ↗(On Diff #21582)

How about:

​// When there is more than one way to sort the table while respecting the ABI
​// rule, we try to implement the same semantics as gnu as, ie. we try to match
// a HI and a LO the same way gnu as does.
344 ↗(On Diff #21582)

That's right, because the second %hi is already followed by a %lo. So the sentence about finding the matching %lo applies only to the first %hi, which gets matched with the second %lo.
I tried to be clearer in the next paragraph, with "don't look for a 'better' match for the HIs that are already followed with a matching LO".

353 ↗(On Diff #21582)

Sure.

366 ↗(On Diff #21582)

It finds 'non-free' %lo's, ie. the ones that are already preceded with a %hi. Then it's easier for the main loop to find a 'free' %lo for a %hi, which are preferred over the 'non-free' ones. As an addition, it remembers %hi's with a matched %lo so the main loop can early exit for them.

test/MC/Mips/sort-relocation-table.s
6 ↗(On Diff #21582)

Added.

Why -1?
This looks like a leftover from the sort function not considering a relocation own offset to break ties.

'-1' simplifies the sort function.

If omitting '-1', the rules for entries with the same SortOffset would be:
-if one is %hi, that one should be first;
-if both are %hi, sort them by Offset (in descending order).

So '-1' practically applies the first rule, and the sorting function only needs to handle the second case.

But it cant introduce a wrong result if another relocation is 1 byte
away, no? We just know that that is not possible? If so please add a
comment about that.

Assembly is user input, and if gnu as matches a %lo and a %hi differently from what we do we would be producing a .o with different semantics.

How about:

// When there is more than one way to sort the table while respecting the ABI
// rule, we try to implement the same semantics as gnu as, ie. we try to match
// a HI and a LO the same way gnu as does.

Still not there. Which relocation gets matched to another is part of
of the semantics of the assembly file. We have to match gas or
document that we are incompatible.

Comment at: lib/Target/Mips/MCTargetDesc/MipsELFObjectWriter.cpp:366
@@ +365,3 @@
+ // Also find HIs and LOs that are already matched.
+ if (I > 0 && areMatchingHiAndLo(MipsRelocs[I], MipsRelocs[I-1]))

+ setMatch(MipsRelocs[I], MipsRelocs[I-1]);

rafael wrote:

Why is this first check for I and I-1 necessary? The main loop should handle it, no?

It finds 'non-free' %lo's, ie. the ones that are already preceded with a %hi. Then it's easier for the main loop to find a 'free' %lo for a %hi, which are preferred over the 'non-free' ones. As an addition, it remembers %hi's with a matched %lo so the main loop can early exit for them.

The rules around this area are pretty fuzzy. Lets try to make the code
as direct as possible on the first implementation at least. Could you
please remove this case and let the main loop handle it?

I will take another look in phab.

rafael accepted this revision.Mar 18 2015, 10:15 AM
rafael edited edge metadata.

LGTM in that

  • It is clear from how linkers behave that something has to be done to sort the entries for Mips.
  • The api change is reasonable: making sortRelocs virtual.
This revision is now accepted and ready to land.Mar 18 2015, 10:15 AM
vstefanovic edited edge metadata.

But it cant introduce a wrong result if another relocation is 1 byte
away, no? We just know that that is not possible? If so please add a
comment about that.

Yes, Mips instructions length is fixed and at least 2 bytes (2 for micromips and mips16, 4 for mips32 and mips64), so there will always be 2 or 4 bytes offset between 2 instructions.

Still not there. Which relocation gets matched to another is part of
of the semantics of the assembly file. We have to match gas or
document that we are incompatible.

Copied the sentence from the generic sortRelocs().

Comment at: lib/Target/Mips/MCTargetDesc/MipsELFObjectWriter.cpp:366
@@ +365,3 @@
+ // Also find HIs and LOs that are already matched.
+ if (I > 0 && areMatchingHiAndLo(MipsRelocs[I], MipsRelocs[I-1]))

+ setMatch(MipsRelocs[I], MipsRelocs[I-1]);

rafael wrote:

Why is this first check for I and I-1 necessary? The main loop should handle it, no?

It finds 'non-free' %lo's, ie. the ones that are already preceded with a %hi. Then it's easier for the main loop to find a 'free' %lo for a %hi, which are preferred over the 'non-free' ones. As an addition, it remembers %hi's with a matched %lo so the main loop can early exit for them.

The rules around this area are pretty fuzzy. Lets try to make the code
as direct as possible on the first implementation at least. Could you
please remove this case and let the main loop handle it?

Moved to the main loop.

Still not there. Which relocation gets matched to another is part of

of the semantics of the assembly file. We have to match gas or

document that we are incompatible.

Copied the sentence from the generic sortRelocs().

That is a very different case. That is about ELF, not mips-ELF.

On ELF, the order is not important, and we just need to make sure it
is deterministic. On mips-ELF it is part of the semantics of the .o.

Still not there. Which relocation gets matched to another is part of

of the semantics of the assembly file. We have to match gas or

document that we are incompatible.

Copied the sentence from the generic sortRelocs().

That is a very different case. That is about ELF, not mips-ELF.

On ELF, the order is not important, and we just need to make sure it
is deterministic. On mips-ELF it is part of the semantics of the .o.

// There may be more ways to sort the table while respecting the ABI rule;
// we always sort the same way gnu as does, for compatibility.

// There may be more ways to sort the table while respecting the ABI rule;
// we always sort the same way gnu as does, for compatibility.

Still not particularly clear. We *have* to sort it in a way that
causes bfd ld to match the same hi and lo relocations as would be
matched had the .s been assembled by gas.

Cheers,
Rafael

// There may be more ways to sort the table while respecting the ABI rule;
// we always sort the same way gnu as does, for compatibility.

Still not particularly clear. We *have* to sort it in a way that
causes bfd ld to match the same hi and lo relocations as would be
matched had the .s been assembled by gas.

Cheers,
Rafael

Ok, a new one:

We are reusing gnu as sorting algorithm so we are emitting the relocation
table sorted the same way as gnu as would sort it.

But we are sorting the table exactly as gas only for easier comparison of the generated o files, aren't we?

The linker must not produce different results if it matches a hi with a different lo, as long as their types match (R_MIPS_HI16 with R_MIPS_LO16, etc.) and are against the same symbol.

E.g.:

0x0: lui $2,%hi(sym)
0x4: addiu $2,$2,%lo(sym)
0x8: addiu $2,$2,%lo(sym)
0xc: lui $2,%hi(sym)

The ABI and the extension allow hi at 0xc to be put before any of two lo's, and the linker must properly calculate the address in both cases. If it didn't, it would be a linker bug.

Regards,
Vladimir

Fine by me.

I am convinced that we need the hook and I am not the best reviewer for the mips stuff.

This revision was automatically updated to reflect the committed changes.