如何在 ARM64 中初始化 16 位整数数组?
How to initialize an array of 16-bit integers in ARM64?
我正在尝试学习一些汇编,特别是 ARM64。
我正在尝试将 16 位整数数组初始化为某个固定值 (123)。
这是我拥有的:
.global _main
.align 2
_main:
mov x0, #0 ; start with a 0-byte offset
mov x1, #123 ; the value to set each 16-bit element to
lsl x1, x1, #48 ; shift this value to the upper 16-bits of the register
loop:
str x1, [sp, x0] ; store the full 64-bit register at some byte offset
add x0, x0, #2 ; advance by two bytes (16-bits)
cmp x0, #10 ; loop until we've written five 16-bit integers
b.ne loop
ldr x2, [sp] ; load the first four 16-bit integers into x2
ubfm x0, x2, #48, #63 ; set the exit status to the leftmost 16-bit integer
mov x16, #1 ; system exit
svc 0 ; supervisor call
我希望退出状态为 123,但它是 0。我不明白为什么。
如果我注释掉循环的最后两行,退出状态为 123,这是正确的。
有人能解释一下这是怎么回事吗?是不是对齐问题?
谢谢
假设您 运行 您的程序在 little-endian Aaarch64 system
上,在给定的循环迭代中,您将覆盖您在前一个中修改的字节:
你实际上是在写字节:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7b
来自:sp + x0 + 0
至:sp + x0 + 7
每次迭代。
初始条件:
(gdb) p/x $sp
= 0x40010000
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000
0x40010010: 0x0000 0x0000 0x0000 0x0000
# initializing some memory to 0xff so that we may see what is going on
(gdb) set {unsigned long }(0x40010000) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010008) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010010) = 0xffffffffffffffff
(gdb) x/12xh 0x40010000
0x40010000: 0xffff 0xffff 0xffff 0xffff 0xffff 0xffff 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
在第一个循环之前:
(gdb) p/x$x1
= 0x7b000000000000
循环:
# pass #1, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x007b 0xffff 0xffff 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #2, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x007b 0xffff 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #3, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x007b 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #4, after str x1, [sp, x0]
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x007b 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #5, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x007b
0x40010010: 0xffff 0xffff 0xffff 0xffff
# after ldr x2, [sp]:
(gdb) p/x $sp
= 0x40010000
(gdb) p/x $x2
= 0x0
(gdb)
如果您不通过注释 lsl x1, x1, #48
:
来移动 x1
中的值,您的程序将运行
# after ldr x2, [sp]
(gdb) x/12xh 0x40010000
0x40010000: 0x007b 0x007b 0x007b 0x007b 0x007b 0x0000 0x0000 0x0000
0x40010010: 0x0000 0x0000 0x0000 0x0000
(gdb) p/x $x2
= 0x7b007b007b007b
(gdb)
这就是说,这可能会更好地使用 strh 指令,这样您就可以避免在循环的每次迭代中写入比您应该写入的字节更多的字节,即 16 个而不是 2 个。
最重要的是,在小端系统上,常量 0x0000000000007b
将作为 7b 00 00 00 00 00 00 00
存储在内存中(升序地址),而常量 0x7b00000000000000
将存储为00 00 00 00 00 00 00 7b
.
由于您进行的移位,您将 0x7b00000000000000
而不是 0x0000000000007b
存储到内存中。
“有趣”的方式:
AArch64 可以有效地将重复模式(任何 2 的幂次方长度)放入 64 位寄存器中,如果每个重复中的所有设置位都是连续的。 (这就是它为 orr x1, xzr, #0x0303030303030303
= mov x1, #...
等按位布尔指令编码立即数的方式。这几乎适用于您的 123
= 0x7b
= 0b1111011
.
或者,ldr x1, =0x007B007B007B007B
会要求 assembler 为您完成;在这种情况下,GAS 选择将常量放在附近的内存中,并使用相对于 PC 的寻址方式加载它。
您可以在将数组存储到堆栈的同时为您的数组保留 space,方法是使用具有更新的回写寻址模式的存储基址寄存器(在本例中为 sp
)和您减去的偏移量。这就是 AArch64 如何高效地实现堆栈“推送”操作的方式。例如在需要保存一些寄存器的函数中,GCC 在函数入口使用 stp x29, x30, [sp, -32]!
从 SP 中减去 32 个字节,并在 space 的底部存储那对寄存器。 (Godbolt example)
所以我认为这应该可行。这确实 assemble 但我还没有尝试 运行。 AArch64 的标准调用约定保持 16 字节堆栈对齐,因此这个存储对 16 字节存储是对齐的。
mov x0, #0x7f007f007f007f
and x0, x0, #~0x0004000400040004 // construct 0x007B repeating
stp x0, x0, [sp, -16]! // push x0 twice
// SP now points at 8 copies of (uint16_t)123, below whatever was on the stack before
带有strh
的循环(存储16位半字)是为了乏味的编译器;手写时,请尝试用尽可能少的指令完成尽可能多的工作。 (这是一个 一般 经验法则,并不总是与性能相关!例如,如果存储是最近的,则仅部分重叠先前存储的宽负载可能会导致存储转发停顿。
我正在尝试学习一些汇编,特别是 ARM64。
我正在尝试将 16 位整数数组初始化为某个固定值 (123)。
这是我拥有的:
.global _main
.align 2
_main:
mov x0, #0 ; start with a 0-byte offset
mov x1, #123 ; the value to set each 16-bit element to
lsl x1, x1, #48 ; shift this value to the upper 16-bits of the register
loop:
str x1, [sp, x0] ; store the full 64-bit register at some byte offset
add x0, x0, #2 ; advance by two bytes (16-bits)
cmp x0, #10 ; loop until we've written five 16-bit integers
b.ne loop
ldr x2, [sp] ; load the first four 16-bit integers into x2
ubfm x0, x2, #48, #63 ; set the exit status to the leftmost 16-bit integer
mov x16, #1 ; system exit
svc 0 ; supervisor call
我希望退出状态为 123,但它是 0。我不明白为什么。
如果我注释掉循环的最后两行,退出状态为 123,这是正确的。
有人能解释一下这是怎么回事吗?是不是对齐问题?
谢谢
假设您 运行 您的程序在 little-endian Aaarch64 system
上,在给定的循环迭代中,您将覆盖您在前一个中修改的字节:
你实际上是在写字节:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7b
来自:sp + x0 + 0
至:sp + x0 + 7
每次迭代。
初始条件:
(gdb) p/x $sp
= 0x40010000
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000
0x40010010: 0x0000 0x0000 0x0000 0x0000
# initializing some memory to 0xff so that we may see what is going on
(gdb) set {unsigned long }(0x40010000) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010008) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010010) = 0xffffffffffffffff
(gdb) x/12xh 0x40010000
0x40010000: 0xffff 0xffff 0xffff 0xffff 0xffff 0xffff 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
在第一个循环之前:
(gdb) p/x$x1
= 0x7b000000000000
循环:
# pass #1, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x007b 0xffff 0xffff 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #2, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x007b 0xffff 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #3, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x007b 0xffff 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #4, after str x1, [sp, x0]
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x007b 0xffff
0x40010010: 0xffff 0xffff 0xffff 0xffff
# pass #5, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x007b
0x40010010: 0xffff 0xffff 0xffff 0xffff
# after ldr x2, [sp]:
(gdb) p/x $sp
= 0x40010000
(gdb) p/x $x2
= 0x0
(gdb)
如果您不通过注释 lsl x1, x1, #48
:
x1
中的值,您的程序将运行
# after ldr x2, [sp]
(gdb) x/12xh 0x40010000
0x40010000: 0x007b 0x007b 0x007b 0x007b 0x007b 0x0000 0x0000 0x0000
0x40010010: 0x0000 0x0000 0x0000 0x0000
(gdb) p/x $x2
= 0x7b007b007b007b
(gdb)
这就是说,这可能会更好地使用 strh 指令,这样您就可以避免在循环的每次迭代中写入比您应该写入的字节更多的字节,即 16 个而不是 2 个。
最重要的是,在小端系统上,常量 0x0000000000007b
将作为 7b 00 00 00 00 00 00 00
存储在内存中(升序地址),而常量 0x7b00000000000000
将存储为00 00 00 00 00 00 00 7b
.
由于您进行的移位,您将 0x7b00000000000000
而不是 0x0000000000007b
存储到内存中。
“有趣”的方式:
AArch64 可以有效地将重复模式(任何 2 的幂次方长度)放入 64 位寄存器中,如果每个重复中的所有设置位都是连续的。 (这就是它为 orr x1, xzr, #0x0303030303030303
= mov x1, #...
等按位布尔指令编码立即数的方式。这几乎适用于您的 123
= 0x7b
= 0b1111011
.
或者,ldr x1, =0x007B007B007B007B
会要求 assembler 为您完成;在这种情况下,GAS 选择将常量放在附近的内存中,并使用相对于 PC 的寻址方式加载它。
您可以在将数组存储到堆栈的同时为您的数组保留 space,方法是使用具有更新的回写寻址模式的存储基址寄存器(在本例中为 sp
)和您减去的偏移量。这就是 AArch64 如何高效地实现堆栈“推送”操作的方式。例如在需要保存一些寄存器的函数中,GCC 在函数入口使用 stp x29, x30, [sp, -32]!
从 SP 中减去 32 个字节,并在 space 的底部存储那对寄存器。 (Godbolt example)
所以我认为这应该可行。这确实 assemble 但我还没有尝试 运行。 AArch64 的标准调用约定保持 16 字节堆栈对齐,因此这个存储对 16 字节存储是对齐的。
mov x0, #0x7f007f007f007f
and x0, x0, #~0x0004000400040004 // construct 0x007B repeating
stp x0, x0, [sp, -16]! // push x0 twice
// SP now points at 8 copies of (uint16_t)123, below whatever was on the stack before
带有strh
的循环(存储16位半字)是为了乏味的编译器;手写时,请尝试用尽可能少的指令完成尽可能多的工作。 (这是一个 一般 经验法则,并不总是与性能相关!例如,如果存储是最近的,则仅部分重叠先前存储的宽负载可能会导致存储转发停顿。