llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.08k stars 11.59k forks source link

ARM64 (and other) assembler immediate arithmetic is broken, and evaluated too early #95099

Open distorted-mdw opened 3 months ago

distorted-mdw commented 3 months ago

GNU as accepts the following (https://gcc.godbolt.org/z/e1fed5vhM)

        .text
proc:
        movz    x0, #thing
        add     x0, x0, #thing
        eor     x0, x0, #thing
        ldr     x0, [x1, #thing]
        ldp     x0, x1, [x2, #thing]
        ldr     q0, [x0, #thing]
        ldp     q0, q1, [x0, #thing]
        ret

        .balign 16
base:
        .skip   16
thing = . - base
        .skip   16

and produces

0000000000000000 <proc>:
   0:   d2800200        mov     x0, #0x10                       // #16
   4:   91004000        add     x0, x0, #0x10
   8:   d27c0000        eor     x0, x0, #0x10
   c:   f9400820        ldr     x0, [x1, #16]
  10:   a9410440        ldp     x0, x1, [x2, #16]
  14:   3dc00400        ldr     q0, [x0, #16]
  18:   ad408400        ldp     q0, q1, [x0, #16]
  1c:   d65f03c0        ret

as I expect.

Clang rejects this (https://gcc.godbolt.org/z/W8PTq9aqG), reporting

t.S:3:11: error: immediate must be an integer in range [0, 65535].
 movz x0, #thing
          ^
t.S:4:14: error: expected compatible register, symbol or integer in range [0, 4095]
 add x0, x0, #thing
             ^
t.S:5:14: error: expected compatible register or logical immediate
 eor x0, x0, #thing
             ^
t.S:6:15: error: index must be an integer in range [-256, 255].
 ldr x0, [x1, #thing]
              ^
t.S:7:19: error: invalid operand for instruction
 ldp x0, x1, [x2, #thing]
                  ^
t.S:8:15: error: index must be an integer in range [-256, 255].
 ldr q0, [x0, #thing]
              ^
t.S:9:19: error: invalid operand for instruction
 ldp q0, q1, [x0, #thing]
                  ^

The errors for ldp are unhelpfully vague, but the caret position hints that assembler is objecting to the immediate operand. The eor error is a little unhelpful, but I understand that it's hard to characterize the acceptable ARM64 logical immediate values. The other messages are simply incorrect: it's clear that thing has the value 16, which is easily in range.

It doesn't help to float the data definition above the code which references it (https://gcc.godbolt.org/z/sejn5frx9); nor does it help to replace the expressions with their literal values (https://gcc.godbolt.org/z/v9GKnf8b8). However, doing both of these things is successful (https://gcc.godbolt.org/z/Gjrjc6a7M).

        .text
        .balign 16
base:
        .skip   16
thing = 16 // . - base
        .skip   16

proc:
        movz    x0, #thing
        add     x0, x0, #thing
        eor     x0, x0, #thing
        ldr     x0, [x1, #thing]
        ldp     x0, x1, [x2, #thing]
        ldr     q0, [x0, #thing]
        ldp     q0, q1, [x0, #thing]
        ret

I guess, therefore, that there are at least two underlying bugs:

ARM64 is where I tripped over this, but it seems that other targets are affected also. For example, the ARM32 fragment

        .text
        .arch   armv7-a
        .fpu    neon
proc:
        mov     r0, #thing
        add     r0, r0, #thing
        eor     r0, r0, #thing
        ldr     r0, [r1, #thing]
        ldrd    r0, r1, [r2, #thing]
        vldr    d0, [r0, #thing]
        bx      r14

        .balign 16
base:
        .skip   16
thing = . - base
        .skip   16

is acceptable to GNU as (https://gcc.godbolt.org/z/G454KvrE6) but Clang objects to the mov, ldrd, and vldr instructions (https://gcc.godbolt.org/z/es33YqrfM).

llvmbot commented 3 months ago

@llvm/issue-subscribers-backend-aarch64

Author: Mark Wooding (distorted-mdw)

GNU as accepts the following (https://gcc.godbolt.org/z/e1fed5vhM) ```assembler .text proc: movz x0, #thing add x0, x0, #thing eor x0, x0, #thing ldr x0, [x1, #thing] ldp x0, x1, [x2, #thing] ldr q0, [x0, #thing] ldp q0, q1, [x0, #thing] ret .balign 16 base: .skip 16 thing = . - base .skip 16 ``` and produces ``` 0000000000000000 <proc>: 0: d2800200 mov x0, #0x10 // #16 4: 91004000 add x0, x0, #0x10 8: d27c0000 eor x0, x0, #0x10 c: f9400820 ldr x0, [x1, #16] 10: a9410440 ldp x0, x1, [x2, #16] 14: 3dc00400 ldr q0, [x0, #16] 18: ad408400 ldp q0, q1, [x0, #16] 1c: d65f03c0 ret ``` as I expect. Clang rejects this (https://gcc.godbolt.org/z/W8PTq9aqG), reporting ``` t.S:3:11: error: immediate must be an integer in range [0, 65535]. movz x0, #thing ^ t.S:4:14: error: expected compatible register, symbol or integer in range [0, 4095] add x0, x0, #thing ^ t.S:5:14: error: expected compatible register or logical immediate eor x0, x0, #thing ^ t.S:6:15: error: index must be an integer in range [-256, 255]. ldr x0, [x1, #thing] ^ t.S:7:19: error: invalid operand for instruction ldp x0, x1, [x2, #thing] ^ t.S:8:15: error: index must be an integer in range [-256, 255]. ldr q0, [x0, #thing] ^ t.S:9:19: error: invalid operand for instruction ldp q0, q1, [x0, #thing] ^ ``` The errors for `ldp` are unhelpfully vague, but the caret position hints that assembler is objecting to the immediate operand. The `eor` error is a little unhelpful, but I understand that it's hard to characterize the acceptable ARM64 logical immediate values. The other messages are simply incorrect: it's clear that `thing` has the value 16, which is easily in range. It doesn't help to float the data definition above the code which references it (https://gcc.godbolt.org/z/sejn5frx9); nor does it help to replace the expressions with their literal values (https://gcc.godbolt.org/z/v9GKnf8b8). However, doing _both_ of these things is successful (https://gcc.godbolt.org/z/Gjrjc6a7M). ```assembler .text .balign 16 base: .skip 16 thing = 16 // . - base .skip 16 proc: movz x0, #thing add x0, x0, #thing eor x0, x0, #thing ldr x0, [x1, #thing] ldp x0, x1, [x2, #thing] ldr q0, [x0, #thing] ldp q0, q1, [x0, #thing] ret ``` I guess, therefore, that there are at least two underlying bugs: * the assembler tries to resolve immediate values in the first pass; and * the assembler doesn't understand that the difference between two defined symbols in the same section is a plain integer. ARM64 is where I tripped over this, but it seems that other targets are affected also. For example, the ARM32 fragment ```assembler .text .arch armv7-a .fpu neon proc: mov r0, #thing add r0, r0, #thing eor r0, r0, #thing ldr r0, [r1, #thing] ldrd r0, r1, [r2, #thing] vldr d0, [r0, #thing] bx r14 .balign 16 base: .skip 16 thing = . - base .skip 16 ``` is acceptable to GNU as (https://gcc.godbolt.org/z/G454KvrE6) but Clang objects to the `mov`, `ldrd`, and `vldr` instructions (https://gcc.godbolt.org/z/es33YqrfM).