:lower16, :upper16 用于 aarch64;绝对地址写入寄存器;
:lower16, :upper16 for aarch64; absolute address into register;
我需要将 32 位绝对地址放入 AArch64 上的寄存器中。 (例如 MMIO 地址,不是 PC 相关的)。
在 ARM32 上可以使用 lower16
& upper16
将地址加载到寄存器中
movw r0, #:lower16:my_addr
movt r0, #:upper16:my_addr
有没有办法在 AArch64 上使用 movk
做类似的事情?
如果代码重定位,我还是要一样的绝对地址,所以adr
不合适
来自附近文字池的 ldr
会起作用,但我宁愿避免那样做。
如果您的地址是 assemble-time 常数,而不是 link-time,这是超级简单。它只是一个整数,您可以手动拆分它。
我让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
implicitly zero-extends into 64-bit x0
,与x86-64相同)。
或者,如果您的常量只是一个 link 时间常量,您需要在 .o
中生成重定位以供 linker 填写,GAS 手册记录了您可以做什么,in the AArch64 machine-specific section:
Relocations for ‘MOVZ’ and ‘MOVK’ instructions can be generated by
prefixing the label with #:abs_g2:
etc. For example to load the
48-bit absolute address of foo
into 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
GAS 手册的示例不是最佳的;从低到高至少在某些 AArch64 CPU 上效率更高(见下文)。 对于 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
will 已知在 16-31 运行ge 中有其可能设置的位,因此 assembler 知道在以下情况下使用 lsl 16
编码 movk
。您不应在此处使用明确的 lsl 16
。
我选择了 x0
而不是 w0
,因为 gcc 对 unsigned long long
就是这样做的。可能所有 CPU 上的性能都相同,代码大小也相同。
.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
构建 和 link(只是为了证明我们可以,如果你真的 运行 这只会崩溃它), 然后 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 似乎在这里有一个错误,使其无法使用:它只有 assembles #:abs_g1_nc:foo
(不检查高半部分)和 #:abs_g0:foo
(低半部分的溢出检查)。这是倒退的,当 foo
具有 32 位地址时会导致 linker 错误(g0 溢出)。我在 x86-64 Arch Linux.
上使用 clang 版本 7.0.1
$ 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
,除非你有一个可以禁用检查的linker。 (或者也许某些 clang 安装带有一个 linker,它与 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 puts a 16-bit immediate into a register with a left-shift of 0, 16, 32 or 48 (and clears the rest of the bits). You always want to start a sequence like this with a movz
, and then movk
the rest of the bits. (movk
= move-keep。将 16 位立即数移入寄存器,保持其他位不变。)
mov
是一种可以选择 movz
的伪指令,但我刚刚使用 GNU binutils 和 clang 进行了测试,并且 你需要一个明确的 movz
(而不是 mov
),立即数如 #:abs_g0:foo
。显然 assembler 不会推断它在那里需要 movz
,这与数字文字不同。
对于窄立即数,例如0xFF000
在两个对齐的 16 位值块中有非零位,mov w0, #0x18000
会选择 bitmask-immediate form of mov
,它实际上是 ORR
-立即数的别名零寄存器。 AArch64 bitmask-immediates 使用强大的编码方案来重复 bit-运行ges 模式。 (因此,例如 and x0, x1, 0x5555555555555555
(仅保留偶数位)可以编码为单个 32 位宽的指令,非常适合位破解。)
还有 movn
(不移动)翻转位。这对于负值很有用,允许您将所有高位设置为 1
。根据 .
,它甚至还需要搬迁
性能:movz low16; movk high16
顺序
The Cortex A57 optimization manual
4.14 Fast literal generation
Cortex-A57 r1p0 and later revisions support optimized literal generation for 32- and 64-bit code
MOV wX, #bottom_16_bits
MOVK wX, #top_16_bits, lsl #16
[and other examples]
... If any of these sequences appear sequentially and in the described order in program code, the two instructions
can be executed at lower latency and higher bandwidth than if they do not appear sequentially in the program
code, enabling 32-bit literals to be generated in a single cycle and 64-bit literals to be generated in two cycles.
序列包括 movz low16
+ movk high16
到 x 或 w 寄存器,按此顺序。 (并且还背靠背 movk
设置高 32,再次按低、高顺序。)根据手册,两条指令都必须使用 w,或者都必须使用 x 寄存器。
如果没有特殊支持,movk
将不得不等待 movz
结果准备好作为 ALU 运算的输入来替换该 16 位块。大概在流水线的某个点,这 2 条指令合并为一个 32 位立即数 movz 或 movk,从而删除了依赖链。
假设 Peter Cordes 对您的 post 的编辑反映了您的实际意图,您可以使用 MOVL psuedo-instruction 将绝对地址加载到寄存器中,而无需使用 LDR 指令。例如:
MOVL x0, my_addr
MOVL 指令的优点是可以同时处理外部定义的符号和本地定义的常量。伪指令将扩展为两条或四条指令,具体取决于目标是 32 位还是 64 位寄存器,通常是一条 MOV 指令后跟一条或三条 MOVK 指令
然而,为什么 LDR 指令,特别是 LDR pseudo-instruction 也不起作用,这一点并不明显。这通常会导致来自文字池的 PC 相对加载,汇编程序会将其放置在与您的代码相同的部分(区域)中。
例如:
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
与引用它的 PC 相关 LDR 指令属于同一代码段,因此指令和符号之间的偏移量永远不会改变,从而使代码可重定位。您可以将您的 trampoline 代码放在它自己的部分 and/or 使用 LTORG 指令来确保将文字池放置在一个靠近且易于预测的位置。
我需要将 32 位绝对地址放入 AArch64 上的寄存器中。 (例如 MMIO 地址,不是 PC 相关的)。
在 ARM32 上可以使用 lower16
& upper16
将地址加载到寄存器中
movw r0, #:lower16:my_addr
movt r0, #:upper16:my_addr
有没有办法在 AArch64 上使用 movk
做类似的事情?
如果代码重定位,我还是要一样的绝对地址,所以adr
不合适
ldr
会起作用,但我宁愿避免那样做。
如果您的地址是 assemble-time 常数,而不是 link-time,这是超级简单。它只是一个整数,您可以手动拆分它。
我让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
implicitly zero-extends into 64-bit x0
,与x86-64相同)。
或者,如果您的常量只是一个 link 时间常量,您需要在 .o
中生成重定位以供 linker 填写,GAS 手册记录了您可以做什么,in the AArch64 machine-specific section:
Relocations for ‘MOVZ’ and ‘MOVK’ instructions can be generated by prefixing the label with
#:abs_g2:
etc. For example to load the 48-bit absolute address offoo
intox0
: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
GAS 手册的示例不是最佳的;从低到高至少在某些 AArch64 CPU 上效率更高(见下文)。 对于 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
will 已知在 16-31 运行ge 中有其可能设置的位,因此 assembler 知道在以下情况下使用 lsl 16
编码 movk
。您不应在此处使用明确的 lsl 16
。
我选择了 x0
而不是 w0
,因为 gcc 对 unsigned long long
就是这样做的。可能所有 CPU 上的性能都相同,代码大小也相同。
.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
构建 和 link(只是为了证明我们可以,如果你真的 运行 这只会崩溃它), 然后 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 似乎在这里有一个错误,使其无法使用:它只有 assembles #:abs_g1_nc:foo
(不检查高半部分)和 #:abs_g0:foo
(低半部分的溢出检查)。这是倒退的,当 foo
具有 32 位地址时会导致 linker 错误(g0 溢出)。我在 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
,除非你有一个可以禁用检查的linker。 (或者也许某些 clang 安装带有一个 linker,它与 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 puts a 16-bit immediate into a register with a left-shift of 0, 16, 32 or 48 (and clears the rest of the bits). You always want to start a sequence like this with a movz
, and then movk
the rest of the bits. (movk
= move-keep。将 16 位立即数移入寄存器,保持其他位不变。)
mov
是一种可以选择 movz
的伪指令,但我刚刚使用 GNU binutils 和 clang 进行了测试,并且 你需要一个明确的 movz
(而不是 mov
),立即数如 #:abs_g0:foo
。显然 assembler 不会推断它在那里需要 movz
,这与数字文字不同。
对于窄立即数,例如0xFF000
在两个对齐的 16 位值块中有非零位,mov w0, #0x18000
会选择 bitmask-immediate form of mov
,它实际上是 ORR
-立即数的别名零寄存器。 AArch64 bitmask-immediates 使用强大的编码方案来重复 bit-运行ges 模式。 (因此,例如 and x0, x1, 0x5555555555555555
(仅保留偶数位)可以编码为单个 32 位宽的指令,非常适合位破解。)
还有 movn
(不移动)翻转位。这对于负值很有用,允许您将所有高位设置为 1
。根据
性能:movz low16; movk high16
顺序
The Cortex A57 optimization manual
4.14 Fast literal generation
Cortex-A57 r1p0 and later revisions support optimized literal generation for 32- and 64-bit code
MOV wX, #bottom_16_bits MOVK wX, #top_16_bits, lsl #16
[and other examples]
... If any of these sequences appear sequentially and in the described order in program code, the two instructions can be executed at lower latency and higher bandwidth than if they do not appear sequentially in the program code, enabling 32-bit literals to be generated in a single cycle and 64-bit literals to be generated in two cycles.
序列包括 movz low16
+ movk high16
到 x 或 w 寄存器,按此顺序。 (并且还背靠背 movk
设置高 32,再次按低、高顺序。)根据手册,两条指令都必须使用 w,或者都必须使用 x 寄存器。
如果没有特殊支持,movk
将不得不等待 movz
结果准备好作为 ALU 运算的输入来替换该 16 位块。大概在流水线的某个点,这 2 条指令合并为一个 32 位立即数 movz 或 movk,从而删除了依赖链。
假设 Peter Cordes 对您的 post 的编辑反映了您的实际意图,您可以使用 MOVL psuedo-instruction 将绝对地址加载到寄存器中,而无需使用 LDR 指令。例如:
MOVL x0, my_addr
MOVL 指令的优点是可以同时处理外部定义的符号和本地定义的常量。伪指令将扩展为两条或四条指令,具体取决于目标是 32 位还是 64 位寄存器,通常是一条 MOV 指令后跟一条或三条 MOVK 指令
然而,为什么 LDR 指令,特别是 LDR pseudo-instruction 也不起作用,这一点并不明显。这通常会导致来自文字池的 PC 相对加载,汇编程序会将其放置在与您的代码相同的部分(区域)中。
例如:
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
与引用它的 PC 相关 LDR 指令属于同一代码段,因此指令和符号之间的偏移量永远不会改变,从而使代码可重定位。您可以将您的 trampoline 代码放在它自己的部分 and/or 使用 LTORG 指令来确保将文字池放置在一个靠近且易于预测的位置。