[Clang] ppc64le ELF V2 ABI "Full doubleword rule" not followed
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
The calling convention in the "Power Architecture 64-Bit ELF V2 ABI Specification" requires "When a doubleword in the Parameter Save Area (or its GPR copy) contains at least a portion of a structure, that doubleword must contain all other portions mapping to the same doubleword."
This is called the "Full doubleword rule" or "no partial DW rule". Figure 2.24 shows an example.
Unfortunately, this rule seems to be violated when building with
```
clang version 18.1.8
Target: powerpc64le-unknown-linux-gnu
```
According to the rule, `pass_struct_after_floats` in the following program must pass `s1.p0` in a floating point register and the whole `s1` on stack.
```C
// gcc -O1 -fPIC -c -o TestHFAPassing.o TestHFAPassing.c
// or
// clang -O1 -fPIC -c -o TestHFAPassing.o TestHFAPassing.c
// ld -shared -z noexecstack -o TestHFAPassing.so TestHFAPassing.o
struct S_FF { float p0; float p1; };
struct S_FF pass_struct_after_floats(struct S_FF (*fun)(
float, float, float, float, float,
float, float, float, float, float,
float, float, struct S_FF),
struct S_FF s1) {
return fun(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, s1);
}
```
GCC stores the whole `s1` on stack offset 128, but clang 18.1.8 only stores the 2nd half violating the rule:
```
fmr f13,f1
stfs f2,132(r1)
```
(This pattern is also found when compiling the C code with "ppc64le clang (trunk)" in https://godbolt.org .)
That works as long as the function `fun` only uses the value from `f13` and never from stack. Both, gcc and clang seem to prefer that, so the problem is not visible when `fun` is created by these compilers. However, other compilers may use the value from stack.
That is the case in OpenJDK 25:
```java
import java.io.File;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static java.lang.foreign.ValueLayout.*;
public class TestHFAPassing {
static {
// Resolve absolute path to the DLL in the current directory
String libPath = new File("TestHFAPassing.so").getAbsolutePath();
System.load(libPath);
}
static final Linker abi = Linker.nativeLinker();
static final SymbolLookup lookup = SymbolLookup.loaderLookup();
static final OfFloat C_FLOAT = (ValueLayout.OfFloat) abi.canonicalLayouts().get("float");
static final GroupLayout S_FFLayout = MemoryLayout.structLayout(
C_FLOAT.withName("p0"),
C_FLOAT.withName("p1")
).withName("S_FF");
static final FunctionDescriptor fdadd_float_to_struct_after_floats = FunctionDescriptor.of(S_FFLayout,
C_FLOAT, C_FLOAT, C_FLOAT, C_FLOAT, C_FLOAT,
C_FLOAT, C_FLOAT, C_FLOAT, C_FLOAT, C_FLOAT,
C_FLOAT, C_FLOAT, S_FFLayout);
static final FunctionDescriptor fdpass_struct_after_floats = FunctionDescriptor.of(S_FFLayout, ADDRESS, S_FFLayout);
static final MethodHandle mhpass_struct_after_floats = abi.downcallHandle(lookup.find("pass_struct_after_floats").orElseThrow(),
fdpass_struct_after_floats);
// callback function
public static MemorySegment addFloatToStructAfterFloats(
float f1, float f2, float f3, float f4, float f5,
float f6, float f7, float f8, float f9, float f10,
float f11, float f12, MemorySegment s) {
float val = s.get(C_FLOAT, 0);
s.set(C_FLOAT, 0, val + 1.0f);
return s;
}
void main() {
float p0 = 0.0f, p1 = 0.0f;
try (Arena arena = Arena.ofConfined()) {
MemorySegment s = arena.allocate(S_FFLayout);
s.set(C_FLOAT, 0, 1.0f);
s.set(C_FLOAT, 4, 1.0f);
MethodType mt = MethodType.methodType(MemorySegment.class,
float.class, float.class, float.class, float.class,
float.class, float.class, float.class, float.class,
float.class, float.class, float.class, float.class,
MemorySegment.class);
MemorySegment stub = abi.upcallStub(MethodHandles.lookup().findStatic(TestHFAPassing.class, "addFloatToStructAfterFloats", mt),
fdadd_float_to_struct_after_floats, arena);
s = (MemorySegment)mhpass_struct_after_floats.invokeExact((SegmentAllocator)arena, stub, s);
p0 = s.get(C_FLOAT, 0);
p1 = s.get(C_FLOAT, 4);
System.out.println("S_FF(" + p0 + ";" + p1 + ")");
} catch (Throwable t) {
t.printStackTrace();
}
if (p0 != 2.0f || p1 != 1.0f) throw new RuntimeException("wrong result from add_float_to_struct_after_floats (Upcall)");
}
}
```
This reproduces the error. (Other subtests of the OpenJDK's test test/jdk/java/foreign/TestHFA.java are passing with clang version 18.1.8.)
Contributor guide
Assessment
This issue has not been assessed yet.