x86 中哪些 MOV 指令未使用或最少使用,可用于自定义 MOV 扩展

new*_*www 2 x86 gcc machine-code inline-assembly gem5

我正在 gem5 模拟器中的 X86 架构中对自定义 MOV 指令进行建模,为了在模拟器上测试其实现,我需要使用内联汇编来编译 C 代码以创建二进制文件。但由于它是一条自定义指令,尚未在 GCC 编译器中实现,因此编译器会抛出错误。我知道一种方法是扩展 GCC 编译器以接受我的自定义 X86 指令,但我不想这样做,因为它更耗时(但稍后会这样做)。

作为临时黑客(只是为了检查我的实现是否值得)。我想编辑已经存在的 MOV 指令,同时更改模拟器中的底层“微操作”,以便欺骗 GCC 接受我的“自定义”指令并进行编译。

因为 x86 架构中有多种类型的 MOV 指令。因为它们是86架构参考中的各种MOV指令。

因此,我的问题是,哪条 MOV 指令使用最少,我可以编辑其底层微操作。假设我的工作负载仅包括整数,即很可能不会使用 xmm 和 mmx 寄存器,并且我的指令反映了 MOV 指令的相同实现。

Pet*_*des 5

最好的选择是mov使用常规前缀,GCC 永远不会自行发出。即创建一个新的mov编码,其中在任何其他mov. 就像怎么lzcntrep bsr

或者,如果您正在修改 GCC 和as,则可以添加一个新的助记符,该助记符仅使用 的内存源、内存目标和直接源版本的无效(在 64 位模式下)单字节操作码mov。AMD64 释放了多个操作码,包括 AAM 等 BCD 指令,以及压入/弹出大多数段寄存器。(x86-64 仍然可以mov往返 Sreg,但每个方向只有 1 个操作码,而不是每个 Sreg 有 2 个操作码用于推送 ds/弹出 ds 等。)

假设我的工作负载仅包括整数,即很可能不会使用 xmm 和 mmx 寄存器

XMM 的错误假设:GCC 积极使用 16 字节movaps/movups而不是一次复制 4 或 8 字节的结构。在标量整数代码中找到向量 mov 指令作为小已知长度或结构/数组 init 的内联扩展的一部分并不罕见memcpy。此外,这些mov指令至少有 2 字节操作码( SSE1 0F 28movaps,因此 plain 前面的前缀mov与您的想法的大小相同)。

不过,您对 MMX 规则的看法是正确的。我认为现代 GCC 根本不会发出movq mm0, mm1或使用 MMX,除非您使用 MMX 内在函数。当针对 64 位代码时绝对不行。

另外mov,往返控制寄存器 ( 0f 21/23 /r) 或调试寄存器 ( 0f 20/22 /r) 都是助记符mov,但 gcc 绝对不会自行发出任何一个。仅适用于 GP 寄存器操作数作为非调试或控制寄存器的操作数。因此,从技术上讲,这就是您标题问题的答案,但可能不是您真正想要的。


GCC 不会解析其内联 asm 模板字符串,它只是将其包含在其 asm 文本输出中,以在替换%number操作数后提供给汇编器。因此,GCC 本身并不是使用内联 asm 发出任意 asm 文本的障碍。

您可以使用它.byte来发出任意机器代码。

也许一个好的选择是使用一个0E字节作为mov您要专门进行 GEM 解码的特殊编码的前缀。 32位模式下0E有效push CS,64位模式下无效。GCC 也永远不会发出。

或者只是 F2repne前缀;GCC 永远不会repne在操作码前面发出mov(它不适用的地方),只会发出movs. (F3 rep/repe表示在内存目标指令上使用时 xrelease,因此不要使用它。https: //www.felixcloutier.com/x86/xacquire:xrelease 表示 F2 repne 与locked 指令一起使用时是 xacquire 前缀,这不包含mov到内存中,因此它将被默默地忽略。)

像往常一样,不适用的前缀没有记录的行为,但实际上,不理解rep/ 的CPUrepne会忽略它。未来的某些 CPU 可能会将其理解为特殊的含义,而这正是您使用 GEM 所做的事情。

如果您想防止在真实 CPU 上运行的构建中意外留下这些前缀,那么选择 代替.byte 0x0e;可能是更好的选择。(它会在 64 位模式下 #UD -> SIGILL,或者通常会因在 32 位模式下搞乱堆栈而崩溃。)但是,如果您确实希望能够在真正的 CPU 上运行完全相同的二进制文件,请使用相同的代码对齐和一切,那么忽略 REP 前缀是理想的。repne;


在标准指令前面使用前缀的mov优点是让汇编器为您编码操作数:

template<class T>
void fancymov(T& dst, T src) {
    // fixme: imm -> mem  needs a size suffix, defeating template
    // unless you use Intel-syntax where the operand includes "dword ptr"
    asm("repne; movl  %1, %0"
#if 1
       : "=m"(dst)
       : "ri" (src)
#else
       : "=g,r"(dst)
       : "ri,rmi" (src)
#endif
       : // no clobbers
    );
}

void test(int *dst, long src) {
    fancymov(*dst, (int)src);
    fancymov(dst[1], 123);
}
Run Code Online (Sandbox Code Playgroud)

(多重替代约束让编译器选择 reg/mem 目标或 reg/mem 源。实际上,它更喜欢寄存器目标,即使这会花费另一条指令来执行自己的存储,所以这很糟糕。)

在 Godbolt 编译器资源管理器上,对于仅允许内存目标的版本:

test(int*, long):
        repne; movl  %esi, (%rdi)       # F2 E9 37
        repne; movl  $123, 4(%rdi)      # F2 C7 47 04 7B 00 00 00
        ret
Run Code Online (Sandbox Code Playgroud)

如果你希望它可用于加载,我认为你必须制作该函数的 2 个单独版本,并在适当的情况下手动使用加载版本或存储版本,因为 GCC 似乎希望尽可能使用 reg,reg 。


或者使用允许寄存器输出的版本(或以 a 形式返回结果的另一个版本T,请参阅 Godbolt 链接):

test2(int*, long):
        repne; mov  %esi, %esi
        repne; mov  $123, %eax
        movl    %esi, (%rdi)
        movl    %eax, 4(%rdi)
        ret
Run Code Online (Sandbox Code Playgroud)