一个无语言的语言如何工作?

我听说过无语的语言。 不过,我不知道这样的语言会如何实施。 有人可以解释吗?

我们拥有的现代操作系统(Windows,Linux)以我所说的“大堆栈模式”运作。 这种模式有时是错误的,并且激发了对“无堆栈”语言的需求。

“大堆栈模型”假定编译的程序将在连续的内存区域中为函数调用分配“堆栈帧”,使用机器指令非常迅速地调整包含堆栈指针(和可选堆栈帧指针)的寄存器。 这导致了快速的函数调用/返回,以堆栈的大的连续区域为代价。 因为在这些现代操作系统下运行的所有程序中的99.99%与大堆栈模型一起工作良好,所以编译器,加载程序甚至操作系统“知道”这个堆栈区域。

所有这些应用程序都有一个共同的问题, “我的堆栈应该多大?” 。 内存很便宜,大部分发生的事情是,为堆栈预留大块(MS默认为1Mb),典型的应用程序调用结构永远不会使用。 但是,如果一个应用程序确实使用了这个function,它就会因为非法的内存引用而死亡(“我很抱歉Dave,我不能这么做”),这是因为它的堆栈结束了。

大多数所谓的“无堆栈”语言并不是真正的无堆栈。 他们只是不使用这些系统提供的连续堆栈。 他们所做的是在每个函数调用中从堆中分配一个堆栈帧。 每个函数调用的成本有所上升; 如果function通常是复杂的,或者语言是解释性的,这个额外的成本是微不足道的。 (也可以在程序调用图中确定调用DAG,并分配一个堆段来覆盖整个DAG;这样,您就可以获得堆分配以及调用DAG内所有调用的经典大堆栈函数调用的速度)。

使用堆栈分配堆栈帧有几个原因:

1)如果程序依赖于正在解决的特定问题进行深度recursion,则预先分配“大堆栈”区域是非常困难的,因为所需的大小是未知的。 人们可能会很笨拙地安排函数调用来检查是否有足够的堆栈,如果没有,重新分配一个更大的块,复制旧的堆栈,并重新调整所有的指针到堆栈; 这是如此尴尬,我不知道任何实现。 分配栈帧意味着应用程序永远不必说对不起,直到没有剩下可分配的内存。

2)程序分叉子任务。 每个子任务都需要自己的堆栈,因此不能使用提供的“大堆栈”。 所以,需要为每个子任务分配堆栈。 如果你有几千个可能的子任务,你现在可能需要成千上万个“大堆栈”,内存需求突然变得荒谬。 分配堆栈帧解决了这个问题。 通常子任务“堆栈”是指回到父任务来实现词汇范围; 作为子任务分叉,创build称为“仙人掌栈”的“子分包”树。

3)你的语言有延续。 这些要求在当前函数可见的词法范围中的数据以某种方式被保留以供以后再使用。 这可以通过复制父堆栈框架,爬上仙人掌栈,然后继续进行。

我实现的PARLANSE编程语言是1)和2)。 我正在3)。

无堆栈的Python仍然有一个Python栈(虽然它可能有尾部调用优化和其他调用帧合并技巧),但它完全脱离解释器的C栈。

Haskell(通常实现)没有调用堆栈; 评估是基于图减less的 。

http://www.linux-mag.com/cache/7373/1.html有一篇关于Parrot语言框架的好文章。; 鹦鹉不使用堆栈调用,这篇文章解释了一下技术。

在无堆栈的环境中,我或多或less地熟悉(图灵机,组装和Brainfuck),实现自己的堆栈是很常见的。 有一个内置的语言堆栈没有什么根本。

在这些最实际的组装中,您只需select一个可用的内存区域,将堆栈寄存器设置为指向底部,然后递增或递减以实现您的推送和popup。

编辑:我知道一些架构有专用堆栈,但他们没有必要。

有一个容易理解本文继续的描述: http : //www.defmacro.org/ramblings/fp.html

继续是你可以通过一个基于堆栈的语言传递给一个函数的东西,但是也可以被一个语言自己的语义所使用,以使其“无堆栈”。 当然这个堆栈还在那里,但正如Ira Baxter所描述的那样,它不是一个大的连续的部分。

古老的叫我,但是我记得当FORTRAN标准和COBOL不支持recursion调用,因此并不需要一个堆栈。 事实上,我记得CDC 6000系列机器的实现没有堆栈,如果您尝试recursion调用子程序,FORTRAN会做一些奇怪的事情。

为了logging而不是调用堆栈,CDC 6000系列指令集使用RJ指令调用子程序。 这将当前的PC值保存在呼叫目标位置,然后分支到后面的位置。 最后,子程序将间接跳转到呼叫目标位置。 这重新加载保存的PC,有效地返回给调用者。

显然,这不适用于recursion调用。 (而我的回忆是,如果你尝试recursion,CDC FORTRAN IV编译器会生成破损的代码…)

假设你想实现无堆栈C,首先要认识到的是,这不需要堆栈:

a == b 

但是,这是吗?

 isequal(a, b) { return a == b; } 

不可以。因为一个智能编译器会内联调用isequal ,把它们变成a == b 。 那么,为什么不把所有内容都内联? 当然,你会产生更多的代码,但如果摆脱堆栈是值得的,那么这是一个小的权衡,很容易。

recursion呢? 没问题。 尾recursion函数,如:

 bang(x) { return x == 1 ? 1 : x * bang(x-1); } 

仍然可以内联,因为它实际上只是一个伪装的循环:

 bang(x) { for(int i = x; i >=1; i--) x *= x-1; return x; } 

从理论上讲,一个非常聪明的编译器可以为你解决这个问题 但是一个不那么聪明的人仍然可以把它变成一个goto:

 ax = x; NOTDONE: if(ax > 1) { x = x*(--ax); goto NOTDONE; } 

有一种情况,你必须做一个小的权衡。 这不能被内联:

 fib(n) { return n <= 2 ? n : fib(n-1) + fib(n-2); } 

Stackless C根本无法做到这一点。 你放弃了很多? 不是真的。 这是正常的C也不能做得很好。 如果你不相信我只是打电话fib(1000) ,看看你的宝贵的电脑会发生什么。

请随时纠正我,如果我错了,但我会认为,在每个函数调用帧的堆分配内存将导致极端的内存抖动。 操作系统毕竟必须pipe理这个内存。 我会认为,避免这种内存抖动的方式将是呼叫帧的caching。 所以,如果你需要一个caching,我们不妨将它在内存中连成一堆,并称之为堆栈。