alpha: don't delete a caller's gp reload after a call
elf64_alpha_relax_with_lituse turns
ldq t12,lit(gp) bsr ra,callee+8
jsr ra,(t12) ==> unop
ldah gp,hi(ra) unop
lda gp,lo(gp)
whenever elf64_alpha_relax_opt_call returns a non-zero destination, i.e.
when the callee starts with our gp (or is marked NOPV). That justifies
entering the callee past its own ldgp, since the ldgp would recompute the
gp we already have, but it does not justify deleting the reload. $gp is
caller-saved; the callee is under no obligation to return with it intact.
A callee ending in a tail call
ldq ra,0(sp)
lda sp,N(sp)
ldq t12,lit(gp)
jmp zero,(t12)
hands control to a function that runs its own ldgp and returns straight to
our caller. When the tail target is in a different sub-GOT the caller
resumes with the wrong gp, and every later disp(gp) names a slot in the
wrong sub-GOT. The value loaded is a valid GOT entry belonging to some
other reference, so nothing traps until it is used, typically an indirect
call landing in .data.rel.ro.
This needs a multi-sub-GOT link to be observable, which is why it went
unnoticed. It was found in libLLVM.so, where SelectionDAG::getConstant
called a gp-sharing callee that tail-called into another sub-GOT and then
jumped through a slot holding a vtable pointer.
Recovering the optimization needs proof that the callee returns with gp
intact. A callee containing no tail call, direct or indirect, always
does, but establishing that requires scanning the target function here.
Until then, keep the reload. It costs no code size: the two instructions
are already emitted and were only being rewritten to unops.
The new test only needs a single sub-GOT: it checks that ld --relax still
converts the jsr to a bsr into foo+8 while leaving the ldah/lda pair
alone.
* elf64-alpha.c (elf64_alpha_relax_with_lituse): Don't turn the
GPDISP ldah/lda pair into unops when the callee shares our gp.
ld/
* testsuite/ld-alpha/relax-gpdisp.s: New test.
* testsuite/ld-alpha/relax-gpdisp.d: New test.
3 files changed