首页 > 解决方案 > 如何使用 1 个全局内存数组在 JavaScript 中模拟带有参数和局部变量的调用堆栈?

问题描述

我已经坚持了几天。基本上我可以把它总结为问,你如何模拟这个函数,就像它是用汇编编写的(或者甚至是机器代码,只使用 1 个内存数组的东西),但是用 JavaScript 做所有事情?

function start() {
  let x = doX(1, 2)
  let y = doX(3, 4)
  let z = doX(x, y)
  return z
}

function doX(a, b) {
  let x = a + b
  let y = a - b
  let z = x * y
  return z
}

所以我的尝试是这样的:

const memory = []

function start() {
  // capture push (function prologue)?
  memory[0] = 1
  memory[1] = 2
  doX()
  memory[2] = memory[100]
  memory[0] = 3
  memory[1] = 4
  doX()
  memory[0] = memory[2]
  memory[1] = memory[100]
  doX()
  // capture pop (function epilogue)?
  memory[100] = memory[100]
}

function doX() {
  // somehow allocate space "on the stack"
  // using only this memory object?
  // don't know how to do that....
  memory[10] = memory[0] + memory[1]
  memory[11] = memory[0] - memory[1]
  memory[12] = memory[10] * memory[11]
  // put it on the return register?
  memory[100] = memory[12]
}

如何仅使用此内存数组正确添加推送和弹出操作并使其看起来正确?另外,我对所有内存地址进行了硬编码,如何正确地使它们相对?

标签: javascriptarraysassemblymemorycallstack

解决方案


您需要一个堆栈指针,或者作为一个单独的全局变量(例如具有与内存分开的寄存器的 CPU),或者只是为这个特殊用途选择一个内存位置。就像@bergi在回答您之前的问题时所解释的那样,您需要做一些事情,比如memory[tos++]将东西推送到堆栈上,而不是假设堆栈指针的起始值是0通过硬编码存储到memory[0].

(在包括 x86 在内的许多 ISA 中,堆栈指针从区域中的最高地址开始,将堆栈中的内容压入堆栈会从堆栈指针中减去。因此它会向下增长)。

您已经将memory[100]其用作返回值寄存器,而不是让 JavaScript 返回值。 如果memory[99]需要,可以用作堆栈指针,因此您可能有类似mem[ --mem[99] ] = val_to_push. sp如果您使用可以调用的单独变量,或者state.sp如果您想定义一个带有内存一些标量寄存器(包括sp堆栈指针)的状态对象,那么它显然更具可读性。


真正的调用约定在寄存器中返回,而不是在内存中;使用“在内存中”的 retval 寄存器是不必要的复杂化。如果你让 JS 函数通过 JS 机制返回值,它仍然类似于 CPU,并认为它是一个寄存器。只要您将其限制为简单的数字。

既然你要弥补你的机器的细节,你可以使用 JS 局部变量作为临时寄存器,并允许函数有尽可能多的。

因此,为这台机器编程有点像 LLVM-IR,您只需使用任意数量的“寄存器”,LLVM 负责实际存储它们的位置。但不是真的;您的代码不会“编译”以将多余的寄存器溢出到堆栈中,它确实具有您想要使用的尽可能多的寄存器。

要将它们视为寄存器并且不让它退化为不使用的纯 JS memory[],您仍然可以要求通过内存进行 arg传递(即堆栈参数调用约定),并假装函数调用破坏了所有的值局部变量。


推荐阅读