如何解决在自定义编程语言中调用函数的难题



几个月来,我一直在断断续续地思考如何解决用自定义编程语言调用函数的问题。有一件奇怪的事情,对同一个函数的无限递归调用,我很难在脑海中超越它。

我会这样举例说明。假设您正在调用doFoo(1, 2)之类的函数,现在必须实现它。在我看来,首先将变量推到堆栈上,然后跳转到函数。但让我们关注第一步,将变量推送到堆栈中。

您所做的是创建一个堆栈帧,并将其推到堆栈上。但为了创建堆栈帧,需要在内存中分配空间。因此,要创建堆栈框架,首先需要调用自定义的allocateMemory(size)类函数。现在这个函数需要将变量推到堆栈上,创建一个堆栈框架。。。。因此,通过从原始allocateMemory(size)函数中调用allocateMemory(size),可以在堆栈上为该堆栈帧分配空间。但后来又发生了!一次又一次。一切都需要分配内存,但内存分配需要推送到堆栈上,这需要内存分配,这需要推送到栈上。。。。等等

因此,我刚刚想到的解决这一问题的方法是通过思考";推到堆栈上";作为原子基元操作。从本质上讲;向上更高";抽象级别,推送到堆栈上是仅一步。就像我想象的组装在引擎盖下创建堆栈框架一样,它是在硬件中实现的(我想是吗?),所以你只需要做push <value>,其余的都在较低级别的系统中抽象掉。

因此,像那样思考可以解决问题,但不能完全解决。

为了解决这个问题,我正在构建一个在浏览器中运行的自定义语言解释器。从本质上讲,它将像VM解释字节码一样工作。假设我们有一个字节码,它具有等效的命令push <value>,它创建了一个堆栈帧(不知何故)。问题是,我在哪里/如何实现push命令的实现?在VM解释器下面?这意味着我必须在JavaScriptland中编写分配逻辑和堆栈框架的创建,然后自定义语言将在VM land中运行,调用JavaScriptland来分配堆栈上的东西。

但我真的不想那样做。我想用这种自定义语言写所有!那么我该如何做到这一点呢?就好像我需要创建的虚拟机。一个VM正在运行,它创建堆栈帧并处理来自更高级别VM的命令。较低级别的VM是使用非常低级别的基元来实现的;"推到堆栈上";。它基本上使用storefetchcall,仅此而已

基本上我在这里迷路了。我该如何处理这种情况或思考它以克服心理障碍?有没有一种方法可以避免创建这些类型的层?有什么更好的概念化方法吗?

  1. 通常,管理运行时程序所需的堆栈和运行时本身管理的堆栈是完全独立的,在概念上不在同一级别。

  2. 此外,您不能将已经实现的高级分配机制本身作为先决条件来实现。

然而,这两件事显然不是你的问题,这就是为什么除了你需要以不同的方式处理问题之外,我不确定还有多少答案。我建议从使用C或汇编实现分配开始,然后在语言运行库中使用它。那么接下来的步骤可能会更加明显。

我想我要做的事情受到了协程的启发,我看到有人说要有一个辅助堆栈。

基本上;"推到堆栈";如果操作只是移动堆栈指针的位置(将其增加激活记录的大小),则操作可以在一个步骤中完成。然后是createStackFrame(size),它所做的只是移动一个指针。这可以在较低级别的VM中实现。因此没有必要";分配存储器";当创建堆栈帧时。您为一个大堆栈(例如8MB)分配了足够的内存,所有这些都是前置的。这样就可以避免在运行时进行分配。

但后来就有点棘手了。我读到关于协程的文章,对于分段堆栈,它们有小堆栈";分段";大小约为4096字节(大致为一页),并且它们被链接到一个链表中。因此,在这个想法的基础上,让我们假设一个单独的线程/协同程序/光纤/回调是使用它自己的堆栈执行的,这些堆栈被划分为多个段,以便它可以增长。我知道这是一个棘手的问题,但我还没有走那么远。但是我们可以处理多个协程/光纤/线程,每个协程都有自己的分段堆栈。

这样做的方法是引入第二个堆栈,一个只用于内存分配过程的堆栈!为了实现为协同程序/线程/光纤动态创建堆栈,您需要分配一个新堆栈(比如4096字节)。该分配算法可能是一组复杂的函数调用(如实现malloc),比我们已经决定的一步createStackFrame(size)多得多。createStackFrame(size)只是一个步骤,因为我们已经预先分配了堆栈(比如说,我们当前光纤的当前堆栈)。所以它只需要改变指针的位置。但是我们的malloc用于分配一个新堆栈,称之为createStack(),可能需要做很多事情。为了处理这个问题,而不是遇到原始问题中概述的递归难题,我们有一个第二个堆栈,一个仅用于createStack()malloc实现!

因此,我们对createStackFrame(size)的调用之前可以进行检查,看看我们是否即将耗尽堆栈段空间,如果是,我们将处理器切换为使用";分配堆栈";,然后使用该堆栈运行分配算法(如果我们保持算法足够简单,它是固定的,不会增长)。一旦它分配了一些空间,就会切换回最后一个线程/光纤/协同程序堆栈,并将新的内存分配空间链接到它。但这种在分配堆栈和常规协同程序堆栈之间的切换会使它变得更好,这样你就不会在这篇文章中遇到递归问题。分配堆栈本身不需要重新分配,因此只能使用原子createStackFrame(size)

最新更新