[clang] [llvm] [SystemZ] Do not extend integer arguments in z/OS XPLINK64 ABI (PR #206833)

Zibi Sarbinowski via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 31 07:04:54 PDT 2026


================

----------------
zibi2 wrote:

> > You are right that my framing was imprecise. The real distinction is between a partial register write and a full register write.
> > CCAssignToRegAndStack<[R1L,...]> assigns the argument to the 32-bit sub-register R1L, so the caller only writes the low 32 bits of the GPR. The upper 32 bits are never touched by the call sequence.
> > CCPromoteToType with ANY_EXTEND assigns the argument to the full 64-bit register R1D. On SystemZ, ANY_EXTEND i32→i64 lowers to a zero-filling instruction (LLILL/LLILF), so the caller writes all 64 bits.
> 
> But `ANY_EXTEND` is not required or guaranteed to do that - this could change at any time!
> 
> > xlc requires the caller to have written the full 64-bit register. R1L violates that because the upper half is never written. ANY_EXTEND satisfies it because the full register is written.
> 
> It doesn't make sense to say it is a requirement that the upper half is "written" - what does that even mean? _What value_ is supposed to be written?
> 
The requirement is that the upper 32 bits are zero. The XPLINK64 ABI spec (z/OS Language Environment Vendor Interfaces) states that integer arguments smaller than 64 bits are passed zero-extended into the full 64-bit GPR. So a caller passing int -1 must put 0x00000000ffffffff in the register, not 0xffffffffffffffff. 

> Can you be specific in what callees compiled with these compiler versions actually expect to see in the upper half of the register? Is it always zero? Is it zero- or sign-extended depending on the type? Anything else?
> 
Based on disassembling xlc output: xlc callees sign-extend the incoming int parameter themselves, unconditionally, in the callee prologue — LGFR R2,R2 — before the first C statement executes. They do not rely on the caller having done anything specific to the upper half.

So the "what does the upper half need to contain" answer is: anything — the callee fixes it regardless. The upper half content from the caller is irrelevant to xlc callees.

> It appears to me that we have two conflicting requirements here. On the one hand, you say that in a callee we cannot rely on an i32 argument to always be extended to i64 because some old (caller) code doesn't do that. On the other hand, you say as a caller we must extend i32 arguments to i64 because some old (callee) code expects that. What if that old caller code calls that old callee code? That would already not work correctly, right?

They're not contradictory, they address different failure modes:
Scenario | Problem | Our fix
-- | -- | --
clang-as-callee, xlc-as-caller | xlc caller writes R1L only. If clang-callee has signext/zeroext on the parameter, clang emits a prologue LGFR/LLGFR that re-extends whatever the caller left — which may be sign-extended garbage from xlc. getDirect (no extend attribute) avoids this prologue. | classifyArgumentType returns getDirect
clang-as-caller, xlc-as-callee | At -O0, clang spills the i32 arg to a 32-bit stack slot and reloads it. ANY_EXTEND of a 32-bit memory load on big-endian S390 lowers to LGF (sign-extend load). For -1, this produces 0xffffffffffffffff in the GPR. The xlc callee's prologue LGFR R2,R2 then re-sign-extends 0xffffffffffffffff → still 0xffffffffffffffff, which is wrong for what should be 0x00000000ffffffff (zero-extended -1). | convertValVTToLocVT uses ZERO_EXTEND instead of ANY_EXTEND for call args

Agreed, and that's exactly why the latest commit replaces the ANY_EXTEND in convertValVTToLocVT with an explicit ZERO_EXTEND when IsCallArg=true. The zero-extension is now an invariant of our call-lowering, not a side-effect of how ANY_EXTEND happens to lower today.

https://github.com/llvm/llvm-project/pull/206833


More information about the llvm-commits mailing list