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