movsd 从内存到 c x86-64 jit 中的 xmm0

movsd from memory to xmm0 in c x86-64 jit

我正在尝试编写一个小型的 x86-64 JIT,但有几个地方有点难以理解。

我正在尝试 JIT 一个简单的函数,该函数将 float 的值分配给 xmm0 寄存器,然后 returns 它,但我不确定我应该如何对 movsd 的参数进行编码打电话。

如有任何帮助,我们将不胜感激。

/* main.c */
#include <stdio.h>
#include <sys/mman.h>

#define xmm(n) (n)

typedef double(*fn)();

fn jit(){
    char* memory = mmap(NULL,
                        4096,
                        PROT_READ|PROT_WRITE|PROT_EXEC,
                        MAP_PRIVATE|MAP_ANONYMOUS,
                        -1, 0);
    int i=0;
    float myfloat = 3.1f;
    memory[i++] = 0x48; /* REX.W */
    memory[i++] = 0xf2; /*******************/
    memory[i++] = 0x0f; /* MOVSD xmm0, m64 */
    memory[i++] = 0x10; /*******************/

    memory[i++] = 0x47 | xmm(0) << 3; /* Not 100% sure this is correct */

    memory[i++] = 0; /* what goes here to load myfloat into xmm0? */

    memory[i++] = 0xc3; /* RET */
    return (fn) memory;
}

int main(){
    fn f = jit();
    printf("result: %f\n", (*f)());
    return 0;
}

SSE 指令通常不支持立即数,除了一些使用 one-byte 立即数来控制其操作的罕见指令。因此你需要:

  • 存储myfloat到附近的内存区域
  • 生成一个引用该区域的内存操作数

这两个步骤都很简单。对于第一步,我只是使用 memory 的开头,然后让代码立即开始。请注意,在这种情况下,您需要确保 return 指向函数开头的指针,而不是 memory 的开头。其他解决方案也是可能的。只需确保 myfloat 存储在代码中的 ±2 GiB 内。

要生成操作数,请重新查阅英特尔手册。您想要的寻址 mode 是一个 32 位 RIP-relative 操作数。这是用 mod = 0,r/m = 5 生成的。位移是一个有符号的 32 位数字,它在指令末尾添加到 RIP 的值(这是 +4来自必须考虑位移的长度)。

因此我们有类似的东西:

memory[i++] = 0xf2; /*******************/
memory[i++] = 0x0f; /* MOVSD xmm0, m64 */
memory[i++] = 0x10; /*******************/
memory[i++] = 0005 | xmm(0) << 3; /* mod = 0, r/m = 5: [rip + disp32] */
*(int *)(memory + i) = memory + i + 4 - addr_of_myfloat;
i += 4;
memory[i++] = 0xc3; /* RET */

注意这里不需要REX前缀。