Если ваш адрес время сборки константа, а не время соединения , это очень просто. Это просто целое число, и вы можете разделить его вручную.
Я попросил 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, удаляя цепочку зависимостей.