On Fri, 16 Oct 2026 15:58:14 GMT, Jorn Vernee <[email protected]> wrote:
> Currently we do not directly support unsigned types in the Linker. The > javadoc has this to say about it: > >> A native linker typically does not provide canonical layouts for C's >> unsigned integral > types. Instead, they are modeled using the canonical layouts associated with > their > corresponding signed integral types. For instance, the C type `unsigned long` > maps to the canonical layout for `code long`. > > This was done under the earlier assumption that ABIs treat signed and > unsigned types the same. However, it turns out that we missed certain cases > where this does matter for some ABIs. More concretely: > > - Mac x64 and AArch64 ABIs require integral types smaller than `int` to be > widened to `int` before being passed to a function > [1](https://developer.apple.com/documentation/xcode/writing-64-bit-intel-code-for-apple-platforms), > > [2](https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms). > Clang even extends this behavior to Linux x64 (although this appears to be a > bug) > [3](https://mail.openjdk.org/archives/list/[email protected]/message/RJNMKNUXN7R5PSJETZKX3PMBJ7P6GYJG/). > - RISCV requires arguments smaller than XLEN to be extended to 32 bits > according to their sign, and then sign extended to XLEN > [4](https://docs.riscv.org/reference/abi/v1.0/riscv-cc-procedure-calling-convention.html). > - PowerPC requires small integer types (char, short, int, long, enum) to be > extended to long > [5](https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi-1.9.html#PARAM-PASS). > > In order to address these use cases we need to attach a bit of extra > information to a linkage request that indicates the signedness of certain > arguments. For this patch, we've chosen to attach that information directly > to the argument layouts through a newly introduced `boolean isSigned()` > method on a new `ValueLayout` subclass `OfIntegral` that is implemented by > `OfByte`, `OfShort`, `OfInt`, and `OfLong`. > > This information is potentially used by the linker to implement the correct > extension. In cases where it doesn't affect the way functions are linker the > information is erased in order to provide more efficient caching, since > signed and unsigned types can share the same downcall handle in that case. > > Testing: `jdk_foreign` test suite on Windows/Linux/Mac-x64, > Mac/Linux-AArch64, Linux-x64 using the fallback linker. > > --------- > - [x] I confirm that I make this contribution in accordance with the [OpenJDK > Interim AI Policy](https://openjdk.org/legal/ai). Hi Jorn, thanks for extending the API! I'm glad to see that. It was really missing. The new tests seem to work on PPC64, but I'm getting a failure in another test, now: TestLargeStub::testDowncall '[1] i4, 1' java.lang.IllegalArgumentException: bad parameter count 502 at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:177) at java.base/java.lang.invoke.MethodType.checkSlotCount(MethodType.java:219) at java.base/java.lang.invoke.MethodType.checkPtypes(MethodType.java:209) at java.base/java.lang.invoke.MethodType.makeImpl(MethodType.java:406) at java.base/java.lang.invoke.MethodType.methodType(MethodType.java:375) at java.base/java.lang.invoke.MethodType.methodType(MethodType.java:245) at java.base/jdk.internal.foreign.abi.CallingSequenceBuilder.computeTypeHelper(CallingSequenceBuilder.java:172) at java.base/jdk.internal.foreign.abi.CallingSequenceBuilder.computeCalleeTypeForDowncall(CallingSequenceBuilder.java:154) at java.base/jdk.internal.foreign.abi.CallingSequenceBuilder.build(CallingSequenceBuilder.java:134) at java.base/jdk.internal.foreign.abi.ppc64.CallArranger.getBindings(CallArranger.java:138) at java.base/jdk.internal.foreign.abi.ppc64.CallArranger.arrangeDowncall(CallArranger.java:142) at java.base/jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64leLinker.arrangeDowncall(LinuxPPC64leLinker.java:71) at java.base/jdk.internal.foreign.abi.AbstractLinker.lambda$downcallHandle0$0(AbstractLinker.java:116) at java.base/jdk.internal.foreign.abi.SoftReferenceCache$Node.get(SoftReferenceCache.java:49) at java.base/jdk.internal.foreign.abi.SoftReferenceCache.get(SoftReferenceCache.java:38) at java.base/jdk.internal.foreign.abi.AbstractLinker.downcallHandle0(AbstractLinker.java:113) at java.base/jdk.internal.foreign.abi.AbstractLinker.downcallHandle(AbstractLinker.java:102) at TestLargeStub.testDowncall(TestLargeStub.java:62) I need to take a closer look. We actually only need to handle 32 bit unsigned int on PPC64. If the long conversion in Java causes problems, an alternative would be to pass the information (e.g. by introducing PPC64Architecture.REG32_MASK_UNSINGED) to the backend where it's trivial to handle (and which I can implement quickly). Or do you have another solution in mind? Hey Jorn, I have spent some time looking at your changes and making experiments on PPC64. Here are my current thoughts (not necessarily final): 1. TestLargeStub.java has inspired me to see what happens when passing lots of integer arguments. Original: ... [5.315s][trace][foreign,downcall] 0x00007cb7c7410014: lwa r0,336(r2) [5.315s][trace][foreign,downcall] 0x00007cb7c7410018: std r0,680(r1) [5.315s][trace][foreign,downcall] 0x00007cb7c741001c: lwa r0,340(r2) [5.315s][trace][foreign,downcall] 0x00007cb7c7410020: std r0,688(r1) With your current proposal: ... [5.703s][trace][foreign,downcall] 0x0000793e93cefe94: ld r0,640(r2) [5.703s][trace][foreign,downcall] 0x0000793e93cefe98: std r0,680(r1) [5.703s][trace][foreign,downcall] 0x0000793e93cefe9c: ld r0,648(r2) [5.703s][trace][foreign,downcall] 0x0000793e93cefea0: std r0,688(r1) Not sure how relevant this is, but the storage efficiency and slot count is much better if we can avoid the long conversions in Java. Adapting the test feels more like a workaround for something which is not ideal. 2. Doing the conversion in the backend would therefore still be my preferred solution as the complexity increase is rather negligible. 3. An alternative would be to convert only unsigned types to long in Java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/CallArranger.java @@ -46,6 +46,7 @@ import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; +import java.lang.foreign.ValueLayout.IntegralExtension; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.util.List; @@ -435,7 +436,14 @@ List<Binding> getBindings(Class<?> carrier, MemoryLayout layout) { case INTEGER -> { // ABI requires all int types to get extended to 64 bit. VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, false); - bindings.vmStore(storage, carrier); + Optional<IntegralExtension> integralExtension = ((ValueLayout) layout).integralExtension(); + if (integralExtension.isPresent() && integralExtension.get() == IntegralExtension.ZERO_EXTEND + && carrier != long.class) { + bindings.cast(carrier, long.class, integralExtension) + .vmStore(storage, long.class); + } else { + bindings.vmStore(storage, carrier); + } } case FLOAT -> { VMStorage storage = storageCalculator.nextStorage(StorageType.FLOAT, carrier == float.class); The tests are passing with that solution and we don't have to modify the backend. I guess nobody will pass so many unsigned integer arguments? 4. I have only taken a very quick look at your prototype. The idea looks good, but I guess PPC64 would need some more workarounds for very obscure corner cases (ABI requires FP arguments passed in GP regs and vice-versa! IEEE754 format conversions are sometimes needed). So, I guess the integral extension part would not make a real difference in terms of complexity or could still be reworked later if desired? 5. s390 also needs all integral types extended to 64 bit. 6. Should we permit IntegralExtension for both, Java `short` and `char`? (Naturally, `short` are signed and `char` unsigned 2 Byte.) I guess IntegralExtension should not be permitted for `boolean`. Right? Comments are appreciated. You may convince me of other solutions. Thanks for your detailed answer and for filing [JDK-8394983](https://bugs.openjdk.org/browse/JDK-8394983)! And also for adapting the `OfChar` handling. > The C standard limits the number of parameters to 127 (a limit which we > currently don't enforce). That is the minimum required by the C standard. Modern compilers support much more. > I think the main issue with your proposal is splitting the argument handling > logic across the Java and VM sides. On most platforms 100% of the ABI > specific logic is implemented on the Java side. The backend just handles the > thread state transition and shuffling register values (prepared by the Java > side) into the right locations. i.e. the _real_ brains behind the operation > are the various call arrangers. Having some ABI specific logic on the Java > side and some in the backend breaks this mental model. This seems like tech > debt that will need to be paid down again later. Ok, you have convinced me that we shouldn't add more complexity to the backend. I also think that the backend should only move values between locations. However, locations have different sizes and hence need different instructions. (The backend for aarch64 also uses different instructions for different sizes.) On PPC64, `float` load and store instructions convert between single and double-precision formats which perfectly serves the needs. Since size conversions for floating point values are done in the backend, it feels natural to me to do the same for integer types which means sign extend for Java integral types. (Note that sign extend comes for free because it is integrated into the move instructions.) That is exactly what is already implemented. I wouldn't call this ABI specific logic in the backend. > Some other issues are: 1. If the backend needs to handle signed and unsigned > types differently, we'd need separate downcall stubs for each, whereas if > this is handled on the Java side the stub can be shared. 2. The contents of > the downcall stub are hidden from the JIT, so it can't for instance fold the > sign extension away, or translate it into a more optimal code shape. Ok. I have given up the idea of handling unsigned types in the backend. While I still like the current backend design, I'm willing to support simplifying problematic parts of the backend when moving towards a new native entry point implementation. > I think these corner cases can be handled by the call arranger. Passing FP > arguments in GP regs is something that Windows/x64 does as well (see the > `VARARG_FLOAT` case). I don't think a C2 intrinsic should have support for > that directly. Again, out of the two, implementing in Java code is preferable. Still looks challenging to me. We may find a solution, but I guess that's out of scope, here. It's not related to the sign topic and rather interesting for future enhancements. My preference for proceeding would be: - Use my proposal under 3. for this PR and keep the test as is. I think that is good enough as a temporary solution, doesn't cause any regression and doesn't need any backend changes. - Decide about [JDK-8394983](https://bugs.openjdk.org/browse/JDK-8394983). - Adapt the test and the implementation according to that decision. - Think about backend simplifications for the new native entry point. ------------- PR Comment: https://git.openjdk.org/jdk/pull/33417#issuecomment-6223355882 PR Comment: https://git.openjdk.org/jdk/pull/33417#issuecomment-6275285535 PR Comment: https://git.openjdk.org/jdk/pull/33417#issuecomment-6328005325
