Issue 56469
Summary PowerPC 32-bit passes float with wrong alignment
Labels
Assignees
Reporter kernigh
    If clang targets 32-bit PowerPC and passes a 4-byte float on the stack, then clang uses a wrong alignment of 8 bytes. This is not compatible with gcc, libffcall, nor libffi, which expect an alignment of 4 bytes. This issue is rare: it can only happen when a function has more than 8 floating-point arguments (because the 32-bit ELF ABI passes 8 such arguments in registers, without allocating stack space) and the 9th or later argument is a float in the prototype.

Given this _pass.c_,

```c
#include <stdio.h>
void pass(float a, float b, float c, float d, float e, float f,
          float g, float h, float i, float j, float k, float l)
{
  printf("i = %g, j = %g, k = %g, l = %g\n", i, j, k, l);
}
```

Today's clang miscompiles it,

```
$ clang-15 -v
clang version 15.0.0 ([email protected]:llvm/llvm-project.git 28b41237e6b296bf777d2f0c13c48031525fcdc4)
Target: powerpc-unknown-openbsd
Thread model: posix
InstalledDir: /home/kernigh/bin
$ clang-15 -S -O1 pass.c
```

I see wrong offsets in _pass.s_,

```
        stwu 1, -16(1)
        lfs 1, 24(1)
        lfs 2, 32(1)
        lfs 3, 40(1)
        lfs 4, 48(1)
```

The stack arguments i, j, k, l are at 8(%r1) before _pass_ allocates its stack frame, or 24(%r1) after. The code must load them into %f1, %f2, %f3, %f4 for _printf_. The correct offsets are 24, 28, 32, 36 (every 4 bytes), but clang uses 24, 32, 40, 48 (every 8 bytes).

I expose the problem by building a shared object `cc -shared -o libpass.so pass.c` (with clang 13, which has the same problem), then calling it with libffcall or libffi.

This _call.lisp_ for GNU CLISP will use libffcall,

```lisp
(ffi:default-foreign-language :stdc)
(ffi:def-call-out pass (:return-type nil)
  (:arguments (a single-float) (b single-float) (c single-float)
              (d single-float) (e single-float) (f single-float)
              (g single-float) (h single-float) (i single-float)
              (j single-float) (k single-float) (l single-float))
  (:library "./libpass.so"))
(pass 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0)
```

This _call.rb_ for Ruby will use libffi,

```rb
require 'fiddle/import'
module Pass
  extend Fiddle::Importer
  dlload './libpass.so'
  extern 'void pass(float, float, float, float, float, float,
                    float, float, float, float, float, float)'
end
Pass.pass 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
```

The output is wrong,

```
$ clisp call.lisp
i = 9, j = 11, k = 3.54381e-29, l = 4.04994e-29
$ ruby call.rb                                                        
i = 9, j = 11, k = 1.875, l = 2
```

If I rebuild libpass.so with gcc, then they give the correct output `i = 9, j = 10, k = 11, l = 12`
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to