: lower16,: upper16 для aarch64;абсолютный адрес в регистре; - PullRequest
0 голосов
/ 12 марта 2019

Мне нужно поместить 32-битный абсолютный адрес в регистр на AArch64.(например, адрес MMIO, не относящийся к ПК).

На ARM32 можно было использовать lower16 & upper16 для загрузки адреса в регистр

movw    r0, #:lower16:my_addr
movt    r0, #:upper16:my_addr

Есть лиспособ сделать то же самое на AArch64, используя movk?

Если код перемещен, я все еще хочу тот же абсолютный адрес, , поэтому adr не подходит.

ldr из ближайшего буквального пула будет работать, но я бы предпочел этого избежать.

Ответы [ 3 ]

3 голосов
/ 12 марта 2019

Если ваш адрес время сборки константа, а не время соединения , это очень просто. Это просто целое число, и вы можете разделить его вручную.

Я попросил gcc и clang скомпилировать unsigned abs_addr() { return 0x12345678; } ( Godbolt )

// gcc8.2 -O3
abs_addr():
    mov     w0, 0x5678               // low half
    movk    w0, 0x1234, lsl 16       // high half
    ret

(запись w0 неявным образом расширяется на ноль в 64-битную x0, то же, что x86-64).


Или, если ваша константа является только константой времени соединения, и вам нужно сгенерировать перемещения в .o, чтобы компоновщик заполнил , руководство GAS документирует, что вы можете сделать, в машинном разделе AArch64 :

Перемещение для команд "MOVZ" и "MOVK" может быть сгенерировано добавление метки #:abs_g2: и т. д. Например, чтобы загрузить 48-битный абсолютный адрес foo в x0:

    movz x0, #:abs_g2:foo     // bits 32-47, overflow check
    movk x0, #:abs_g1_nc:foo  // bits 16-31, no overflow check
    movk x0, #:abs_g0_nc:foo  // bits  0-15, no overflow check

Пример руководства по ГАЗУ является неоптимальным; переход от низкого к высокому уровню более эффективен, по крайней мере, на некоторых процессорах AArch64 (см. ниже). Для 32-битной константы следуйте тому же шаблону, который gcc использовал для числового литерала .

 movz x0, #:abs_g0_nc:foo           // bits  0-15, no overflow check
 movk x0, #:abs_g1:foo              // bits 16-31, overflow check
Известно, что

#:abs_g1:foo будет иметь свои возможные биты в диапазоне 16-31, поэтому ассемблер знает, как использовать lsl 16 при кодировании movk. Вы не должны использовать явное lsl 16 здесь.

Я выбрал x0 вместо w0, потому что это то, что gcc делает для unsigned long long. Вероятно, производительность одинакова на всех процессорах, а размер кода одинаков.

.text
func:
   // efficient
     movz x0, #:abs_g0_nc:foo           // bits  0-15, no overflow check
     movk x0, #:abs_g1:foo              // bits 16-31, overflow check

   // inefficient but does assemble + link
   //  movz x1, #:abs_g1:foo              // bits 16-31, overflow check
   //  movk x1, #:abs_g0_nc:foo           // bits  0-15, no overflow check

.data
foo: .word 123       // .data will be in a different page than .text

С GCC: aarch64-linux-gnu-gcc -nostdlib aarch-reloc.s для сборки и связью (просто чтобы доказать, что мы можем, это просто рухнет, если вы его действительно запустили), а затем aarch64-linux-gnu-objdump -drwC a.out:

a.out:     file format elf64-littleaarch64


Disassembly of section .text:

000000000040010c <func>:
  40010c:       d2802280        mov     x0, #0x114                      // #276
  400110:       f2a00820        movk    x0, #0x41, lsl #16

Кажется, что в Clang есть ошибка, делающая его непригодным для использования : он только собирает #:abs_g1_nc:foo (без проверки верхней половины) и #:abs_g0:foo (проверка переполнения нижней половины). Это обратный процесс и приводит к ошибке компоновщика (переполнение g0), когда foo имеет 32-битный адрес. Я использую Clang версии 7.0.1 на x86-64 Arch Linux.

$ clang -target aarch64 -c aarch-reloc.s
aarch-reloc.s:5:15: error: immediate must be an integer in range [0, 65535].
     movz x0, #:abs_g0_nc:foo
              ^

В качестве обходного пути g1_nc вместо g1 хорошо, вы можете жить без проверки переполнения. Но вам нужно g0_nc, если только у вас нет компоновщика, где проверка может быть отключена. (Или, может быть, некоторые установки clang поставляются с компоновщиком, совместимым с ошибками с перемещениями, которые генерирует clang?) Я тестировал с GNU ld (GNU Binutils) 2.31.1 и GNU gold (GNU Binutils 2.31.1) 1.16

$ aarch64-linux-gnu-ld.bfd aarch-reloc.o 
aarch64-linux-gnu-ld.bfd: warning: cannot find entry symbol _start; defaulting to 00000000004000b0
aarch64-linux-gnu-ld.bfd: aarch-reloc.o: in function `func':
(.text+0x0): relocation truncated to fit: R_AARCH64_MOVW_UABS_G0 against `.data'

$ aarch64-linux-gnu-ld.gold aarch-reloc.o 
aarch-reloc.o(.text+0x0): error: relocation overflow in R_AARCH64_MOVW_UABS_G0

MOVZ против MOVK против MOVN

movz = move-zero помещает 16-битное значение в регистр с левым сдвигом 0, 16, 32 или 48 (и очищает остальные биты). Вы всегда хотите начать подобную последовательность с movz, а затем movk остальные биты. (movk = move-keep . Move 16- Немедленный бит в регистр, оставляя остальные биты без изменений.)

mov является своего рода псевдоинструкцией, которая может выбрать movz, но я только что протестировал с GNU binutils и clang, и вам нужен явный movz (не mov) с немедленным как #:abs_g0:foo. Очевидно, ассемблер не сделает вывод, что ему нужно movz, в отличие от числового литерала.

Для узкого немедленного, например, 0xFF000, который имеет ненулевые биты в двух выровненных 16-битных порциях значения, mov w0, #0x18000 выберет форму с непосредственной битовой маской mov, которая на самом деле является псевдонимом для ORR Непосредственно с нулевым регистром. AArch64 bitmask-немедленно использует мощную схему кодирования для повторяющихся комбинаций битовых диапазонов. (Так, например, and x0, x1, 0x5555555555555555 (оставляйте только четные биты) можно кодировать в одной 32-битной инструкции, отлично подходит для битовых хаков.)

Также есть movn (не двигайтесь), который переворачивает биты. Это полезно для отрицательных значений, позволяя установить для всех старших битов значение 1. Для этого существует даже перемещение, согласно AArch64 префиксы перемещения .


Производительность: movz low16; movk high16 в таком порядке

Руководство по оптимизации Cortex A57

4.14 Быстрая генерация букв

Cortex-A57 r1p0 и более поздние версии поддерживают оптимизированную генерацию литералов для 32- и 64-битного кода

    MOV wX, #bottom_16_bits
    MOVK wX, #top_16_bits, lsl #16

[и другие примеры]

... Если любая из этих последовательностей появляется последовательно и в описанном порядке в программном коде , две инструкции может выполняться с меньшей задержкой и более высокой пропускной способностью, чем если бы они не появлялись последовательно в программе код , позволяющий генерировать 32-битные литералы в одном цикле и 64-битные литералы в двух циклах.

Последовательности включают movz low16 + movk high16 в регистры x или w, в этом порядке . (А также вплотную movk для установки высокого 32, опять же в низком, высоком порядке.) В соответствии с руководством обе инструкции должны использовать w, или обе должны использовать регистры x.

Без специальной поддержки movk пришлось бы ждать, пока результат movz будет готов в качестве входа для операции ALU, чтобы заменить этот 16-битный блок. Предположительно, в какой-то момент конвейера две инструкции объединяются в один 32-битный немедленный movz или movk, удаляя цепочку зависимостей.

1 голос
/ 12 марта 2019

Предполагая, что правки Питера Кордеса в вашем посте отражают ваше реальное намерение, вы можете использовать псевдо-инструкцию MOVL для загрузки абсолютного адреса в регистр без использования инструкции LDR.Например:

    MOVL x0, my_addr

Преимущество инструкции MOVL заключается в работе как с внешними символами, так и с локально определенными константами.Псевдоинструкция расширится до двух или четырех инструкций, в зависимости от того, является ли назначение 32-битным или 64-битным регистром, обычно инструкция MOV, сопровождаемая одной или тремя инструкциями MOVK

Однако не очевидно, почемуинструкция LDR, в частности псевдоинструкция LDR , также не будет работать.Обычно это приводит к относительной загрузке ПК из литерального пула, который ассемблер поместит в тот же раздел (область), что и ваш код.

Например:

    LDR x0, =my_addr

будет собран вчто-то вроде:

    LDR x0, literal_pool   ; <a href="http://infocenter.arm.com/help/topic/com.arm.doc.dui0802b/LDR_lit_gen.html" rel="nofollow noreferrer" title="LDR (PC-relative literal)">LDR (PC-relative literal)</a>
    ; ...
literal_pool:
    .quad my_addr

Поскольку literal_pool является частью той же секции кода, что и инструкция LDR, относящаяся к ПК, которая ссылается на нее, смещение между инструкцией и символом никогда не меняется, что делает код перемещаемым.Вы можете поместить свой батутный код в его собственный раздел и / или использовать директиву LTORG , чтобы гарантировать, что буквенный пул будет размещен в близком и легко предсказуемом месте.

1 голос
/ 12 марта 2019

Почему бы не просто

ldr w0, =my_addr

Это должно расшириться до оптимального кода для любой микроархитектуры, для которой вы программируете.

...