开发者生态
morning
SBCL:终极汇编代码面包板 (2014)
2026-05-20
1 阅读
yacin
编辑:Lutz Euler 指出 NEXT 序列(用于)使用索引寄存器但没有基址来编码有效地址。该错误不会影响指令的含义,但会强制进行浪费的编码。机器码的区别如下。之前(14 个字节): 1 2 3 4 ; 03:8B043D00000000 MOV EAX,[RDI]; _5_ 无用字节! ; 0A:4883C704 添加 RDI,4; 0E:4801F0 添加 RAX、RSI; 11:FFE0 JMP RAX 现在(9 个字节):1 2 3 4 ; 93:8B07 MOV EAX,[RDI]; 95:4883C704 添加 RDI,4; 99:4801F0 添加 RAX、RSI; 9C:FFE0 JMP RAX 我修复了 NEXT 的定义,但没有修复下面的反汇编片段;他们仍然显示旧的机器代码。本周早些时候,我又看了一眼 F18。和 Chuck Moore 的作品一样,很难区分疯狂和纯粹的才华;)令我震惊的一件事是堆栈有多么小:10 个插槽,没有花哨的溢出/下溢陷阱。基本原理是,如果您需要更多插槽,那么您就做错了,当您知道自己在做什么时,无声溢出很有用。这当然符合我在 HP-41C 和 x87 上的经验。这也让我想起 djb 的一篇文章,谴责我们滥用 x87 的旋转堆栈:他的论点是,通过仔细的调度,“自由”FXCH 使堆栈即使不是优于寄存器,也可以等效。这篇文章以一个(非流水线)循环结束,由于 x87 的隐式堆栈旋转,该循环不会浪费任何循环来洗牌数据。这让我想知道哪些实现技术可用于基于堆栈的虚拟机,将其堆栈限制为例如 8 个插槽。显然,将所有内容都保存在寄存器中是理想的选择。然而,如果我们天真地这样做,push 和 pop 就会变得更加复杂; Forth 引擎通常只缓存堆栈前 1-2 个元素是有原因的。我决定模仿 x87 和 F18(编辑:以后者的两个 TOS 缓存寄存器为模):推入/弹出不会导致任何数据移动。相反,如下图所示,它们递减/递增指向堆栈顶部 (TOS) 的模块化计数器。这在软件中仍然会很慢(大多数 ISA 无法索引寄存器)。关键是计数器不能取太多值:如果堆栈中有 8 个槽,则只能取 8 个值。出于性能原因,堆栈虚拟机已经复制了原语(例如,通过在多个地址之间分散执行相同原语来帮助 BTB),因此为堆栈计数器可以采用的所有 8 个值专门化原语似乎是合理的。在常规的直接线程虚拟机中,大多数 primops 都会以跳转到下一个 (NEXT) 的代码序列结束,例如 add rsi, 8 ;在跳转 jmp [rsi-8] 之前增加虚拟 IP;跳转到RSI先前指向的地址,其中rsi是虚拟指令指针,VM指令只是指向相关原语的机器代码的指针。我将对这个序列进行两处更改。我不喜欢在字节码中硬编码地址,而且每个虚拟指令 64 位太浪费了。相反,我将对 primop 代码块的偏移量进行编码: mov eax, [rsi] add rsi, 4 add rax, rdi jmp rax 其中 rdi 是 primops 的基地址。我还需要根据隐式堆栈计数器的新值进行调度。我决定通过定期(例如一页)存储每个 primop 的变体来使调度尽可能简单。我将其四舍五入为 64 * 67 = 4288 字节,以最大限度地减少混叠事故。 NEXT 变成类似 mov eax, [rsi] add rsi, 4 lea rax, [rax + rdi +variant_offset] jmp rax 的技巧是,variant_offset = 4288 * stack_counter,并且在编译原语时(通常)知道堆栈计数器。如果堆栈保持原样,则计数器也保持原样;压入一个值会使计数器递减,弹出一个值会使计数器递增。这似乎很合理。让我们看看能否让它发挥作用。准备工作我想探索一个问题,为此我将发出大量重复的机器代码。 SLIME 的 REPL 和 SBCL 的汇编器非常适合这项任务! (我希望很清楚我使用的是不受支持的内部结构;如果它损坏了,您可以保留这些碎片。)VM 的基本设计是: r8 - r15:堆栈插槽(32 位); rsi :机器代码原语的基地址; rdi :虚拟指令指针(指向下一条指令); rax 、 rbx 、 rcx 、 rdx :暂存寄存器; rsp :(虚拟)返回堆栈指针。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 (导入 '(sb-assem:inst sb-vm::make-ea)) ;我们会经常使用这两个;;我们堆栈的后备存储 (defvar *stack* (make-array 8 :initial-contents (list sb-vm::r8d-tn sb-vm::r9d-tn sb-vm::r10d-tn sb-vm::r11d-tn sb-vm::r12d-tn sb-vm::r13d-tn sb-vm::r14d-tn sb-vm::r15d-tn))) ;; _primop- Generation-time_ 堆栈指针 (defvar *stack-pointer*) ;; (@ 0) 返回 TOS 的(当前)寄存器,(@ 1) 返回 ;;下面那个,等等 (defun @ (i) (aref *stack* (mod (+ i *stack-pointer*) (length *stack*)))) (defvar *code-bas