在 AArch64 架构中对立即数进行编码的方法

 

翻译自:Encoding of immediate values on AArch64

AArch64 是一个具有 32 位固定指令宽度的指令集架构(ISA),这意味着在单个指令中没有足够的空间来存储一个 64 位的立即数。与作者之前熟悉的 x86 架构相比,在 x86-64 上处理立即数要简单一些,因为 x86 指令可以有可变宽度。例如,在 x86-64 上,一个 64 位的立即数只是字节序列:

mov rax, 0x1122334455667788 # 编码为:0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11

像 ARM 这样的固定宽度指令集必须以不同的方式处理立即数。将相同的值赋给寄存器 x0 需要四个指令:

movz x0, 0x7788 
movk x0, 0x5566, lsl 16 
movk x0, 0x3344, lsl 32 
movk x0, 0x1122, lsl 48

移动宽立即数

移动指令(movzmovnmovk)有空间存储一个 16 位的无符号立即数,该数可以左移 0、16、32 或 48 位(移位由 2 位表示)。

movz 将给定的 16 位值赋给由移位操作数指定的位置,并将所有其他位清零。

movk 执行相同的操作,但是 k 保留其他位的值而不是将它们清零。所以在最坏的情况下,一个 64 位的立即数需要 4 条指令。但是,许多常见的立即数可以用更少的指令编码:

# x0 = 0x10000 
movz x0, 0x1, lsl 16 
# x0 = 0x10001 
movz x0, 0x1 
movk x0, 0x1, lsl 16

只有当 64 位寄存器中非零的 16 位部分才需要被初始化。

现在我们已经看到了这个,我们如何编码 -1 呢?在这种情况下,所有位都是 1,所以即使只使用 movzmovk,我们也必须再次使用 4 条指令。

对于这样的数字,AArch64 引入了 movn 指令,它将表达式 ~(imm16 << shift) 赋给寄存器。因此,-1 可以用单一指令编码:movn x0, 0

movn 也可以与 movk 结合使用,用它来设置数字中不是全 1 的部分。

例如,v8 真的决定了通过 movnmovz 编码一个立即数是否更有益(这意味着使用更少的指令)。

加/减立即数

除了移动指令中的立即数,一些指令如 addsub 也接受立即数作为操作数。

这允许直接在指令中编码一些数字,而不是使用临时寄存器。

所有加/减立即指令类允许一个 12 位的无符号立即数,可以可选地左移 12 位(移位由 1 位表示)。

如果你想使用这些指令与无法以这种格式编码的立即数,你别无选择,只能使用临时寄存器,可能还需要多条指令来初始化这个寄存器。

尽管负数,例如 -1(全 1)不能使用 add 指令编码,但可以使用 sub 指令减去 1:sub x0, x0, 1

逻辑立即数

还有另一个指令类允许立即数作为操作数:逻辑立即数。这个指令类用于 and(按位与)、orr(按位或)、eor(按位异或)和 ands(按位与并设置标志)。

这个指令类是最复杂和直观的(至少对我来说),也是我开始写这篇博客文章的原因。

让我们看看 ARM 参考手册中的定义:

逻辑立即指令接受掩码立即数 bimm32 或 bimm64。 这样的立即数由一个至少有一个非零位和一个零位的连续序列组成,在一个 2、4、8、16、32 或 64 位的元素内; 然后该元素被复制到寄存器宽度,或者是这样值的按位取反。 所有零和全一的立即值都不能被编码为掩码立即数,因此汇编器必须为具有这样立即数的逻辑指令生成一个错误, 或者一个用户友好的汇编器可以将它转换为实现预期结果的其他指令。

这段话包含了很多信息。

我将尝试用我自己的话来描述这种格式:

逻辑立即指令有 13 位用于编码立即数,它由三个字段 N(1 位)、immr(6 位)和 imms(6 位)组成。

这种格式不允许将 0 或 ~0(全一)编码为立即数。

尽管这听起来一开始可能有问题,但实际上这并不是一个限制:这种格式只用于像按位 andorr 这样的指令,这些常数在这里并不是很有用(例如 x0 | 0 可以优化为 x0x0 | ~0 可以优化为 ~0)。

立即数的位模式由相同子模式组成,长度为 2、4、8、16、32 或 64 位。

子模式的大小和值存储在 Nimms 字段中。

位模式需要是一个至少有一个零位的连续序列,后面是至少有一个一位的连续序列(该模式的正则表达式将是 0+1+)。

要生成位模式,格式实际上只存储元素中的连续一的数目和元素的大小。

特别指定的元素值可以通过右旋转最多元素大小减 1 位来移动一的序列的开始到元素的任何其他点。

旋转的次数存储在 immr 中,它有 6 位,所以在元素大小为 64 位的情况下允许最多 63 次旋转。

元素大小为 2 只允许 0 或 1 次旋转,在这种情况下,只有最低有效位被考虑,immr 的上 5 位被简单地忽略。

元素被复制直到达到 32 或 64 位。

13 位可以存储 8192 个不同的值,但由于例如旋转并不总是被充分利用到其全部潜力的较小元素大小,它实际上允许较少的不同值,但可能是一组更有用位模式。

由于 immr 实际上相当无聊(只存储旋转次数),让我们看看 Nimms 如何同时存储元素大小和连续一的数目:

Nimms元素大小
011
011
011
010
011
010
00x
00x
1xx

上面的位指定元素大小,而用 x 标记的低位用于存储连续一的序列。

0 表示位模式中有 1 个 1,1 表示有两个 1,依此类推。

同时,不允许将所有 x 设置为 1,因为这将允许创建全 1 的位模式(记住:格式不允许 0 或全一被编码)。

让我们看一些例子:

  • 0 | 111100 表示元素 01(2 位元素大小,一个 1)
  • 0 | 110101 表示元素 00111111(8 位元素大小,六个 1)

Stack Overflow 上有一个有趣的答案,列举了所有 5334 个可能的 64 位立即数,使用这种编码。

我将这个代码移植到 Ruby 并转储了所有值的字段 nimmrimms

在这里查看脚本的全部输出。

通过比较所有值与 AArch64 汇编器的输出,我验证了输出。

滚动查看所有值、元素大小、旋转等应该可以快速了解哪些数字可以被编码成这种表示。

对于一些

源代码示例,见 LLVM,它也处理逻辑立即数的编码和解码。

其他立即数

还有更多接受立即数作为操作数的指令类(甚至有一个接受浮点数的)。

但我认为它们不像逻辑立即数类那样复杂。

 

 

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
AArch64架构是一种基于ARMv8指令集64位处理器架构,它广泛应用于移动设备、嵌入式系统和服务器等领域。在AArch64架构上安装Qt可以为开发者提供跨平台的应用程序开发环境。 要在AArch64架构上安装Qt,可以按照以下步骤进行操作: 1. 下载Qt安装包:首先,你需要从Qt官方网站(https://www.qt.io/)下载适用于AArch64架构的Qt安装包。确保选择与你的操作系统和编译器兼容的版本。 2. 安装编译工具链:在安装Qt之前,你需要确保已经安装了适用于AArch64架构的编译工具链。这包括AArch64架构的交叉编译器和相关的开发工具。你可以根据你的操作系统选择合适的工具链,并按照它们的安装说明进行安装。 3. 安装Qt:解压下载的Qt安装包,并按照安装向导的指示进行安装。在安装过程,你可以选择需要的组件和功能,例如Qt Creator集成开发环境、Qt库和工具等。 4. 配置编译环境:安装完成后,你需要配置编译环境以便使用Qt。这包括设置环境变量、添加Qt库的路径等。具体的配置步骤可以参考Qt官方文档或者相关的教程。 5. 创建和编译项目:现在你可以使用Qt Creator或者其他编辑器创建和编译你的Qt项目了。在创建项目时,选择适用于AArch64架构的目标平台,并配置好编译选项。 6. 运行和调试:完成编译后,你可以在AArch64架构的设备上运行和调试你的Qt应用程序。确保你的设备已经连接到开发机,并按照Qt Creator或者其他工具的指导进行运行和调试操作。 希望以上信息对你有帮助!如果你还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神一样的老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值