[英]How to resolve the conundrum of calling a function in a custom programming language?
几个月来,我一直在思考如何解决在自定义编程语言中调用 function 的问题。 有一个奇怪的事情是对同一个 function 的无限递归调用,我很难在精神上超越。
我会这样说明。 假设您正在调用一些 function ,例如doFoo(1, 2)
,您现在必须实现它。 发生的事情(在我看来)是,您首先将变量压入堆栈,然后跳转到 function。 但是让我们专注于第一步,将变量压入堆栈。
您所做的是创建一个 stack frame ,并将其推送到堆栈上。 但是为了创建堆栈帧,需要在 memory 中分配空间。 因此,要创建堆栈帧,您首先调用自定义的allocateMemory(size)
排序 function。 现在这个 function需要将它的变量推入堆栈,创建一个堆栈帧......所以你通过在原始allocateMemory(size)
function 中调用allocateMemory(size)
来为这个堆栈帧分配堆栈空间。 但随后又发生了。 一次又一次,Everything 需要分配 memory,但是 memory 分配需要压入堆栈,这需要 memory 分配。 这需要推入堆栈......等等。
因此,我刚刚想到的可能解决此问题的方法是将这种“压入堆栈”的操作视为原子原始操作。 从本质上讲,从“更高”的抽象级别来看,压入堆栈只是一步。 就像我想象的组装在引擎盖下创建堆栈框架,但它确实是在硬件中实现的(我认为?),所以你只需push <value>
并且 rest 在较低级别的系统中被抽象出来。
所以这样的想法可以解决问题,但并不完全。
出于这个问题的目的,我正在构建一个自定义语言解释器以在浏览器中运行。 本质上,它将像解释字节码的 VM 一样工作。 假设我们有具有push <value>
等效命令的字节码,它创建了一个堆栈帧(以某种方式)。 问题是,我在哪里/如何实现push
命令的实现? 在VM解释器下面? 这意味着我必须在 JavaScript 领域编写分配逻辑和堆栈帧的创建,然后自定义语言将在 VM 领域运行,调用 JavaScript 领域以在堆栈上分配内容。
但我真的不想那样做。 我想用这种自定义语言编写所有内容吗? 那么我该如何实现呢? 好像我需要创建虚拟机层。 一个 VM 正在运行,它创建堆栈帧并处理来自更高级别 VM 的命令。 较低级别的 VM 是使用非常低级别的原语实现的,比“推入堆栈”更小。 它基本上使用store
、 fetch
和call
,仅此而已。
基本上我在这里迷路了。 我该如何处理这种情况或考虑如何克服心理障碍? 有没有办法避免必须创建这些类型的图层? 有什么更好的概念化方法吗?
通常,管理运行时程序所需的堆栈和运行时本身管理的堆栈是完全独立的,并且在概念上不在同一级别。
此外,您不能通过将已经实现的本身作为先决条件来实现高级分配机制。
但是,您的问题显然没有遵循这两件事,这就是为什么我不确定除了您需要以不同的方式解决问题之外还有很多答案。 我建议首先使用 C 或程序集实现分配,然后在您的语言运行时使用它。 那么接下来的步骤可能应该更明显。
我想我要做的事情是从协程和我看到的关于有一个辅助堆栈的东西中得到启发的。
基本上,如果操作只是移动堆栈指针的 position (将其增加激活记录的大小),则“推入堆栈”操作可以在一个步骤中完成。 然后是createStackFrame(size)
,它所做的只是移动一个指针。 这可以在较低级别的 VM 中实现。 所以在创建堆栈帧时不需要“分配内存”。 您为大堆栈(例如 8MB)分配了足够的 memory,所有这些都是预先设置的。 然后你可以避免在运行时进行分配。
但后来它变得有点棘手。 我读到了协程,对于分段堆栈,它们具有大小为 4096 字节(大致为一页)的小堆栈“段”,它们被链接在一起形成一个链表。 因此,基于这个想法,假设单个线程/协程/光纤/回调使用自己的堆栈执行,分成段以便它可以增长。 我知道这有一个颠簸的问题,但我还没有那么远。 但是我们可以处理多个协程/光纤/线程,每个都有自己的分段堆栈。
这样做的方法是引入第二个堆栈,一个仅用于 memory 分配过程的堆栈,为了实现为协程/线程/光纤动态创建堆栈,您需要分配一个新堆栈(例如 4096 字节)。 该分配算法可能是一组复杂的 function 调用(如实现malloc
),远远超过我们已经决定的一步createStackFrame(size)
。 createStackFrame(size)
只是一步,因为我们已经预先分配了堆栈(假设我们当前的堆栈用于我们当前的光纤)。 所以它只需要改变一个指针 position。 但是我们的malloc
用于分配一个新堆栈,称为createStack()
,可能需要做很多事情。 为了解决这个问题,而不是遇到原始问题中概述的递归难题,我们有第二个堆栈,一个仅用于createStack()
或malloc
实现!
因此,在调用createStackFrame(size)
之前可以检查是否即将用完堆栈段空间,如果是,我们切换处理器以使用“分配堆栈”,然后运行分配使用该堆栈的算法(如果我们保持算法足够简单,它是固定的并且不会增长)。 一旦它分配了一些空间,就会切换回最后一个线程/光纤/协程堆栈,并将新的 memory 分配空间链接到它。 但是分配堆栈和常规协程堆栈之间的这种切换将使您不会遇到本文中的递归问题。 分配堆栈不需要自己重新分配,因此只能使用原子createStackFrame(size)
!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.