RIP 相对寻址仅在寻址模式下没有其他寄存器时才起作用。
[table + rcx*8]
只能用 x86-64 机器代码编码为[disp32 + rcx*8]
, 因此仅适用于适合 32 位有符号绝对地址的非大地址。 Windows 显然可以支持这一点LARGEADDRESSAWARE:NO
,就像在 Linux 上一样编译用-no-pie解决同样的问题。
MacOS 没有解决方法,您根本无法使用 64 位绝对寻址。Mach-O 64 位格式不支持 32 位绝对地址。 NASM 访问阵列 shows 如何使用 RIP 相对索引静态数组lea
将表地址存入寄存器同时避免使用 32 位绝对地址。
你的跳转表本身很好:他们使用64-bit绝对地址,可以在虚拟地址空间中的任何位置重定位。 (在 ASLR 之后使用加载时修复。)
我认为你的间接层次太多了。由于您已经将函数指针加载到寄存器中,因此您应该使用jmp r10
not jmp [r10]
。在任何可能的分支错误预测之前,预先将所有负载加载到寄存器中可以让它们更快地进入管道,因此maybe如果您有大量闲置寄存器,这是一个好主意。
内联一些后面的块会更好,如果它们很小,因为任何给定 RCX 值可到达的块都无法通过任何其他方式到达。所以内联所有的会更好func_21
and func_31
into func_11
,依此类推func_12
。您可以使用汇编器宏来使这更容易。
实际上重要的是最后的跳转func_11
always去func_21
。最好还有其他方法可以到达该街区,例如来自跳过表 1 的其他间接分支。这没有理由func_11
不要陷入其中;它仅限制您可以在这两个块之间进行哪些优化,如果func_21
仍然必须是未从以下位置落入的执行路径的有效入口点func_11
.
但无论如何,您可以像这样实现您的代码。如果确实优化的话,可以去掉后面的调度步骤和相应的负载。
我认为这是有效的 MASM 语法。如果没有,应该仍然清楚所需的机器代码是什么。
lea rax, [jumpTable1] ; RIP-relative by default in MASM, like GAS [RIP + jumpTable1] or NASM [rel jumpTable1]
; The other tables are at assemble-time-constant small offsets from RAX
mov r10, [rax + rcx*8 + jumpTable3 - jumpTable1]
mov r11, [rax + rcx*8 + jumpTable2 - jumpTable1]
jmp [rax + rcx*8]
func_11:
...
jmp r10 ; TODO: inline func_21 or at least use jmp func_21
; you can use macros to help with either of those
或者,如果您只想为一张表绑定一个寄存器,可以使用:
lea r10, [jumpTable1] ; RIP-relative LEA
lea r10, [r10 + rcx*8] ; address of the function pointer we want
jmp [r10]
align 8
func_11:
...
jmp [r10 + jumpTable2 - jumpTable1] ; same index in another table
align 8
func_12:
...
jmp [r10 + jumpTable3 - jumpTable1] ; same index in *another* table
这充分利用了表之间已知的静态偏移。
跳转目标的缓存局部性
在跳跃目标矩阵中,任何单一使用都会沿着“列”向下移动以遵循某些跳跃链。显然,最好转置布局,使跳转链沿着“行”行进,这样所有目标都来自同一缓存行。
即这样安排你的桌子func_11
and 21
可以结束于jmp [r10+8]
, 进而jmp [r10+16]
,而不是+表之间的一些偏移,以改进空间局部性。 L1d 加载延迟只有几个周期,因此与在第一个间接分支之前加载到寄存器相比,CPU 在检查分支预测的正确性时没有太多额外的延迟。 (我正在考虑第一个分支错误预测的情况,因此 OoO exec 无法“看到”内存间接 jmp,直到开始发出正确的路径为止。)
避免使用 64 位绝对地址:
您还可以存储相对于跳转目标附近的某个引用地址或相对于表本身的 32 位(或 16 或 8 位)偏移量。
比如看看GCC编译时做了什么switch
与位置无关的代码中的跳转表,即使对于允许运行时修复绝对地址的目标也是如此。
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84011包括一个测试用例;看到它Godbolt 与 GCC 的 MASM 风格.intel_syntax。它使用一个movsxd
从表中加载,然后add rax, rdx
/ jmp rax
。表条目是这样的dd L27 - L4
and dd L25 - L4
(其中这些是标签名称,给出从跳跃目标到“锚点”L4 的距离)。
(也与该案例相关https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85585).