栈机


堆栈机是一种计算机。在某些情况下,该术语是指模拟堆栈机器的软件方案。与其他计算机的主要区别在于它的大部分指令都是在一个数字下拉堆栈上运行,而不是寄存器中的数字。堆栈计算机使用反向波兰语表示法 指令集进行编程。大多数计算机系统以某种形式实现堆栈以传递参数并链接到子例程。这不会使这些计算机堆叠机器。



堆栈机的常见替代方法是注册机器,其中每条指令明确地为其操作数和结果命名特定的寄存器。



实用的表达式堆栈机器
“堆栈机器”是一台使用后进先出堆栈来保存短暂临时值的计算机。它的大部分指令都假定操作数将来自堆栈,并将结果放入堆栈。



对于像“添加”这样的典型指令,计算机从堆栈的最顶层(最新)值中取得两个操作数。计算机用执行“添加”指令时由计算机计算的总和替换这两个值。指令的操作数从堆栈中“弹出”,然后将其结果“推回”堆栈,准备下一条指令。大多数堆栈指令只有一个操作码命令一个操作,没有额外的字段来标识一个常量,寄存器或存储单元。堆栈很容易保存两个以上的输入或多个结果,因此可以计算更丰富的一组操作。整型常量操作数通常由独立的加载即时指令推送。内存通常通过单独的包含内存地址的加载或存储指令或通过堆栈中的值计算地址来访问。



为了提高速度,堆栈机器通常使用寄存器实现堆栈的某些部分。为了快速执行,算术逻辑单元(ALU)的操作数可以是堆栈的前两个寄存器,并且来自ALU的结果被存储在堆栈的顶部寄存器中。一些堆栈机器有一堆有限的大小,实现为一个寄存器文件。ALU将用索引访问它。有些机器具有无限大小的堆栈,作为RAM中的一个数组实现,由’栈顶’地址寄存器访问。这个速度较慢,但​​触发器的数量较少,使CPU成本更低,更紧凑。其最高的N个值可以被缓存为了速度。一些机器在内存中同时具有表达式堆栈和独立的寄存器堆栈。在这种情况下,软件或中断可能会在它们之间移动数据。



所述指令集执行与后缀最ALU动作(逆波兰表示法)只表达堆栈上的工作,而不是在数据寄存器或主存储器单元的操作。这对于执行高级语言可能非常方便,因为大多数算术表达式可以很容易地转换为后缀表示法。



相反,注册机器在一个小而快的寄存器阵列中保存临时值。累加器机器只有一个通用寄存器。带式机器使用FIFO队列来保存临时值。内存到内存的机器没有临时寄存器可供程序员使用。



堆栈机器可以将它们的表达式堆栈和它们的回调堆栈分开或作为一个集成结构。如果它们是分开的,堆栈机器的指令可以通过较少的交互和较少的设计复杂度进行流水线操作。通常它可以运行得更快。



一些技术手持式计算器在其键盘界面中使用逆波兰符号,而不是使用括号键。这是堆叠机的一种形式。Plus键依赖于它的两个操作数已经在用户可见堆栈的正确最高位置。



堆栈机器指令集的优点
非常紧凑的目标代码
堆栈机器比其他类型的机器具有更小的指令。加载和存储到内存是分开的,因此堆栈代码所需的指令大约是注册机器等效代码的两倍。堆栈计算机的总代码大小(以字节计)仍然较少[ 需要的引证 ]。



在堆栈机器代码中,最常见的指令仅由选择操作的操作码组成。这可以很容易地适应6位或更少。分支,加载立即数和加载/存储指令需要一个参数字段,但堆栈机器通常会安排这些操作的常见情况仍然与操作码一起放入紧凑的位组中。先前结果中操作数的选择是通过对指令进行排序来隐式完成的。相比之下,注册机器每个ALU指令需要两个或三个寄存器号字段来选择操作数; 最密集的注册机器平均每个指令约16位。



累加器或内存到内存机器的指令不会被填充多个寄存器字段。相反,他们使用编译器管理的匿名变量来表示子表达式值。这些临时位置需要额外的内存参考指令,这些指令比堆栈机器甚至更小型的注册机器占用更多的代码空间。



所有实用的堆栈机器都有加载/存储操作码的变体,用于访问局部变量和形式参数,而无需显式地址计算。这可以通过从当前堆栈顶部地址的偏移或者通过来自稳定的帧基寄存器的偏移来实现。注册机器使用寄存器+偏移地址模式来处理这种情况,但使用更宽的偏移量域。



密集机器代码在20世纪60年代非常有价值,当时主存储器非常昂贵,甚至在主机上也非常有限。它在小型计算机和微处理器的最初微小的记忆中再次变得重要。目前,智能手机应用程序,通过慢速互联网连接下载到浏览器的应用程序以及用于嵌入式应用程序的ROM中密度仍然非常重要。增加密度的更普遍的优点是提高了缓存和指令预取的有效性。



Burroughs B6700代码的一些密度是由于将其他地方的重要操作数信息移动到每个数据字或指针表中的“标签”。Add指令本身是通用的或多态的。它必须获取操作数来发现这是一个整数加法还是浮点加法。Load指令可能会发现它自己在间接地址上跳闸,或者更糟糕的是,它会伪装成一个按名称调用的 thunk例程。通用操作码需要更少的操作码位,但使得硬件更像是一个解释器,处理常见情况的机会较少。



简单编译器
编译器比其他机器编译器更简单,更快速。代码生成是微不足道的,并且独立于之前或之后的代码。例如,给定表达式x + y * z + u,相应的语法树将是:



二叉树 - 堆栈
一个简单的堆栈机器的编译代码将采用如下形式:



推x
推y
推动z


推你

请注意,算术运算’multiply’和’add’作用于堆栈的两个最高操作数。



这种简单的编译可以通过解析过程完成。不需要注册管理。大多数堆栈机器可以复制堆栈条目以避免内存访问(这会慢得多),但这些通常是微不足道的。处理加法,索引加载或函数调用的常见情况的相同操作码也将处理涉及复杂子表达式和嵌套调用的一般情况。对于具有单独的地址计算路径的指令,编译器和CPU不需要特殊情况。



这种简单性使编译器能够适应非常小的机器。简单的编译器允许新产品线迅速上市,并允许新的操作系统完全用高级语言编写而不是汇编。例如,UCSD p-System通过编译到虚拟堆栈机器而不是实际硬件,在早期的8位微处理器上支持完整的学生编程环境,这些编程环境具有较差的指令集和很少的RAM。



堆栈机器编译器简单性的缺点是,纯堆栈机器具有较少的优化(请参见堆栈机器的性能缺点小节)。然而,编译堆栈代码的优化是很有可能的。编译器输出的后端优化已经被证明可以显着改善代码[1] [2]和潜在的性能,而编译器本身的全局优化可以获得进一步的收益。[3]



简单的口译员
一些堆栈机器指令集用于解释执行虚拟机,而不是直接驱动硬件。虚拟堆栈机器的解释器比寄存器或存储器到内存机器的解释器更易于构建; 处理内存地址模式的逻辑仅在一个地方,而不是在许多指令中重复。堆栈机器往往具有较少的操作码变化; 一个通用操作码可以处理常见的情况,并且可以处理内存引用或函数调用设置的角落案例。(但是,对于相同的操作,代码密度通常通过添加短和长的形式得到改进。)



快速操作数访问
由于没有操作数字段需要解码,因此堆栈机器会同时获取每条指令及其操作数。堆栈机器可以省略注册机器的操作数获取阶段。[4]此外,除了从存储器中的指令的显式负载,操作数使用顺序是与所述数据堆栈中的操作数的顺序是相同的。通过将操作数保存在快速存储器的堆栈顶部,可以轻松实现出色的预取。例如,在Java优化处理器(JOP)微处理器中,栈的前2个操作数直接进入比寄存器文件更快的数据转发电路。[5]



避免数据通过内存传递,更快的解释器
快速访问对解释者也很有用。大多数寄存器解释器按编号指定其寄存器。但是主机的寄存器不能在索引数组中访问,因此将为虚拟寄存器分配一个内存数组。因此,寄存器解释器的指令必须使用存储器将生成的数据传递给下一条指令。这迫使注册解释器在使用精细处理规则(即,没有提高电路速度的更快晶体管,例如Haswell x86)制造的微处理器上慢得多。这些需要几个时钟来访问存储器,但只有一个时钟来访问寄存器。在具有数据转发电路而不是寄存器文件的堆栈机器的情况下,堆栈解释器可以将主机的寄存器分配给堆栈的顶部几个操作数,而不是主机的’



最小的处理器状态
具有表达式堆栈的机器可以通过只有两个对程序员可见的寄存器来获得:栈顶地址和下一个指令地址。最小的硬件实现具有少得多的触发器或寄存器位。更快的设计可以简单地将最上面的N个堆栈单元缓存到寄存器中,以减少存储器堆栈周期。



响应中断涉及将寄存器保存到堆栈,然后分支到中断处理程序代码。在堆栈机器中,大多数参数已经在堆栈中。因此,没有必要把他们推到那里。堆栈机器通常会更快地响应中断。一些注册机通过具有多个可立即交换的寄存器文件来解决这个问题[6],但这会增加成本并降低寄存器文件的速度。



堆叠机的性能缺点
更多内存参考
一些业内人士认为堆栈机器比注册机器对临时值和局部变量执行更多的数据缓存周期。[7]



在堆栈机器上,临时值通常会溢出到内存中,而在具有多个寄存器的机器上,这些临时值通常保留在寄存器中。(但是,在中断处理期间,这些值通常需要在过程定义结束时,基本块或至少被分配到“激活帧”中)存入内存缓冲区。溢出到内存的值会增加更多缓存周期。这种溢出效应取决于用于缓冲栈顶值的隐藏寄存器的数量,嵌套过程调用的频率以及主机中断处理速率。



一些简单的堆栈机器或堆栈解释器不使用栈顶硬件寄存器。这些最小的实现总是比标准注册机慢。像X + 1这样的典型表达式编译为“加载X; 加载1; 加’。这确实隐式写入和读取不需要的内存堆栈:



加载X,推入内存
加载1,推入内存
从内存中弹出2个值,添加并将结果推送到内存
共有5个数据缓存参考。



下一步就是一个堆栈机器或具有单个栈顶寄存器的解释器。上面的代码然后执行:



将X载入空的TOS寄存器(如果是硬件机器)或
将TOS寄存器推入内存,将X载入TOS寄存器(如果解释器)
将TOS寄存器推入内存,将1加载到TOS寄存器中
从存储器中弹出左操作数,添加到TOS寄存器并保留在那里
总共有5个数据高速缓存参考,最坏的情况。一般来说,解释器不会跟踪空虚,因为它们不需要 - 低于堆栈指针的任何东西都是非空值,并且TOS高速缓存寄存器始终保持高温状态。但是,典型的Java解释器不会以这种方式缓冲堆栈顶部,因为程序和堆栈具有短和宽数据值的混合。



如果硬连线堆栈机器有N个寄存器来缓存最高存储器堆栈字,则在本例中避免所有溢出和重新填充,并且只有1个数据高速缓存周期,与寄存器或累加器机器相同。



在使用优化编译器的注册机器上,最常用的局部变量保留在寄存器而不是堆栈帧存储单元中是很常见的。这消除了读取和写入这些值的大部分数据缓存周期。用于执行实时变量分析并因此将关键变量保留在堆栈上的’堆栈调度’的开发有助于解决这个问题。



另一方面,注册机器必须通过嵌套过程调用将许多寄存器泄漏到内存中。决定哪些寄存器溢出以及何时在编译时进行静态编译,而不是在调用的动态深度。这可能会导致比高级堆栈机器实现更多的数据高速缓存流量。



分解常见子表达的高成本
在注册机器中,一个常见的子表达式(一个多次使用相同结果值的子表达式)只能被评估一次,其结果保存在一个快速寄存器中。随后的重复使用没有时间或代码成本,只是一个注册参考。这种优化加速了简单的表达式(例如,加载变量X或指针P)以及不太常见的复杂表达式。



与堆垛机相反,结果可以以两种方式之一存储。首先,可以使用内存中的临时变量来存储结果。存储和随后的检索花费额外的指令和额外的数据高速缓存周期。如果子表达式计算的成本比从内存中获取更多,那么在大多数堆栈CPU中,这几乎总是如此。对于简单变量和指针提取来说永远不值得,因为每次访问的数据缓存周期已经有相同的代价。对于像X + 1这样的表达式来说,这只是微不足道的。这些更简单的表达式构成了以非连续语言编写的程序中大部分冗余的,可优化的表达式。优化编译器只能赢得程序员在源代码中可以避免的冗余。



第二种方法在数据堆栈上留下计算值,并根据需要复制它。这使用操作来复制堆栈条目。堆栈必须足够浅才能获得CPU的可用复制指令。手写堆栈代码通常使用这种方法,并且像通用注册机一样达到速度。[4] [8] [9]不幸的是,最佳“堆栈调度”算法并未被编程语言广泛使用。



刚性代码顺序
在现代机器中,从数据缓存中获取变量的时间通常是基本ALU操作所需时间的几倍。如果一个程序的内存加载可以在需要该变量的指令之前的几个周期开始,那么程序运行得更快而不会停顿。复杂的机器可以通过深层流水线和“无序执行”来执行此操作,它可以一次检查并运行多条指令。注册机甚至可以通过更简单的“按顺序”硬件,浅层流水线和稍微更智能的编译器来完成此操作。加载步骤成为单独的指令,并且该指令在代码序列中更早地被静态调度。编译器在两者之​​间放置独立的步骤。



调度内存访问需要显式的备用寄存器。没有将微架构的某些方面暴露给程序员,堆栈机器是不可能的。对于表达式AB,必须在减号步骤之前立即评估和推动右操作数。没有堆栈排列或硬件多线程,在等待加载B完成时,可以放入相对较少的有用代码。堆栈机器可以解决内存延迟问题,可以通过一个深度无序执行管道一次覆盖许多指令,或者更有可能的是,它们可以对堆栈进行置换,以便在负载完成时可以处理其他工作负载,或者可以交错执行不同的程序线程,就像Unisys A9系统一样。[10] 然而,今天越来越平行的计算负载表明,这可能不是过去被证明的缺点。



能够使用乱序执行
该tomasulo算法发现的指令级并行通过它们的数据可用发出指令。从概念上讲,堆栈中的位置地址与寄存器文件的寄存器索引没有区别。该视图允许将Tomasulo算法的乱序执行与堆栈机器一起使用。



堆栈机器的无序执行似乎减少或避免了许多理论和实践难题。[11]所引用的研究表明,这种堆叠机器可利用指令级的并行性,和所得到的硬件必须为指令缓存的数据。这些机器有效地绕过了大部分内存访问堆栈。该结果实现了与RISC注册机相当的吞吐量(每个时钟的指令),代码密度更高(因为操作数地址是隐含的)。



堆栈机器的紧凑代码自然适合缓存中的更多指令,因此可以实现更好的缓存效率,降低内存成本或允许在给定成本下更快的内存系统。另外,大多数堆栈指令非常简单,只由一个操作码字段或一个操作数字段组成。因此,堆栈机器需要很少的电子资源来解码每条指令。



研究中提出的一个问题是,需要大约1.88个堆栈指令才能完成注册机器RISC指令的工作。因此,竞争性乱序堆栈机器需要大约两倍的电子资源来跟踪指令(“发布站”)。这可以通过指令高速缓存和存储器以及指令解码电路中的节省来补偿。



隐藏一个更快的注册机器
一些简单的堆栈机器具有完全定制的芯片设计,一直到单个寄存器级别。堆栈地址寄存器的顶部和堆栈数据缓冲区的顶部N是由独立的单独寄存器电路构建而成,具有独立的加法器和ad hoc连接。



但是,大多数堆栈机器都是由更大的电路组件构成的,其中N个数据缓冲区一起存储在寄存器文件中并共享读/写总线。解码的堆栈指令被映射到该隐藏寄存器文件上的一个或多个连续动作。负载和ALU操作在最上面的寄存器上执行,隐式溢出和填充在最底层的寄存器上执行。解码器允许指令流紧凑。但如果代码流具有直接操作底层寄存器文件的显式寄存器选择字段,则编译器可以更好地使用所有寄存器,并且程序运行得更快。



微程序堆栈机就是一个例子。内部微码引擎是某种类似RISC的寄存器机器或使用多个寄存器文件的类似VLIW的机器。当由特定于任务的微代码直接控制时,该引擎在每个周期完成的工作要比由相同任务的等效堆栈代码间接控制更多。



另一个例子是HP 3000和Tandem T / 16 的目标代码转换器。[12] [13]他们将堆栈码序列转换为等效的RISC码序列。次要的“本地”优化消除了堆栈体系结构的大部分开销。备用寄存器被用来分解重复的地址计算。翻译后的代码仍然保留了原始和目标机器之间不匹配的大量仿真开销。尽管有这种负担,翻译后的代码的循环效率与原始堆栈代码的循环效率相匹配。当源代码通过优化编译器直接重新编译到注册机时,效率提高了一倍。这表明堆栈体系结构及其非优化编译器浪费了底层硬件的一半功耗。



与通过数据缓存进行内存引用相比,寄存器文件是计算的好工具,因为它们具有高带宽和非常低的延迟。在一个简单的机器中,寄存器文件允许读取两个独立的寄存器并写入第三个,全部在一个ALU周期中,具有一个周期或更少的延迟。鉴于相应的数据高速缓存每个周期只能启动一次读取或一次写入(不是两次),并且读取通常具有两个ALU周期的延迟。这是流水线延迟两倍的吞吐量的三分之一。像Athlon这样的复杂机器每个周期完成两个或更多指令,寄存器文件允许读取四个或更多个独立寄存器并写入另外两个,全部在一个具有一个周期延迟的ALU周期中。鉴于相应的双端口数据缓存每个周期只能启动两次读取或写入操作,并存在多个等待时间周期。同样,这是寄存器吞吐量的三分之一。使用其他端口构建高速缓存非常昂贵。



更多说明,更慢的口译员
虚拟堆栈机的解释器通常比其他类型虚拟机的解释器慢。[14]在具有深度执行流水线的主机上运行时,如当前的x86芯片,速度最慢。



编译到堆栈机器时,程序必须执行更多的指令,而不是编译到注册机器或内存到机器的机器。每个可变负载或常量都需要它自己单独的Load指令,而不是捆绑在使用该值的指令中。分离后的指令可能更简单,运行速度更快,但总指令数仍然较高。



在一些解释器中,解释器必须执行一个N路开关跳转来解码下一个操作码并分支到该特定操作码的步骤。另一种选择操作码的方法是线程代码。主机的预取机制无法预测和获取该索引或间接跳转的目标。因此,每次托管解释器解码另一个虚拟指令时,主机的执行管道必须重新启动。对于虚拟堆栈机器而言,这比其他类型的虚拟机器更频繁地发生。[15]



用于Java的Android Dalvik虚拟机使用虚拟寄存器16位指令集,而不是Java通常的8位堆栈代码,以最小化指令计数和操作码调度停顿。算术指令直接通过4位(或更大)指令字段获取或存储局部变量。5.0版本的Lua用更快的虚拟注册机器取代了它的虚拟堆栈机器。[16] [17]



由于Java虚拟机变得流行,目前的微处理器采用先进的分支预测器进行间接跳转。[18]这一进步避免了大部分流水线从N路跳转重新启动,因此几乎消除了与注册解释器相比,影响栈解释器的指令计数成本。



混合机器
(这些不应该与混合了数字和模拟特性的混合计算机相混淆,例如,数字计算机可以通过内存映射和模拟设备输入和输出的转换来实现模拟乘法或微分方程求解。)



对于从同一对象访问多个字段的过程,纯堆栈机器的效率相当低。堆栈机器代码必须为每个指针+偏移量计算重新加载对象指针。一个常见的解决方法是在堆栈机器中添加一些注册机器功能:一个专门用于保存地址的可见寄存器文件,以及用于执行加载和简单地址计算的寄存器式指令。将寄存器完全用于通用目的是很少见的,因为没有充分的理由要有表达式堆栈和后缀指令。



另一个常见的混合操作是从一个寄存器机器体系结构开始,并添加另一个模拟堆栈机器的push或pop操作的内存地址模式:’memaddress = reg; reg + = instr.displ’。这在DEC的PDP-11小型计算机中首次使用。该功能在VAX计算机和Motorola 6800和M68000微处理器中得到了推广。这允许在早期编译器中使用更简单的堆栈方法。它还使用堆栈解释器或线程代码有效支持虚拟机。然而,这个功能并没有帮助注册机器自己的代码变得像纯粹的堆栈机器代码一样紧凑。另外,执行速度比编译寄存器体系结构时要低。只是偶尔(每次调用或返回一次)更改栈顶指针的速度会更快,而不是在每个程序语句中不断上下调整,并且更快速地避免完全使用内存引用。



最近,所谓的第二代堆栈机器采用专用寄存器集合作为地址寄存器,从数据堆栈中卸载内存寻址任务。例如,MuP21依赖于名为“A”的寄存器,而最近的GreenArrays处理器依赖于两个寄存器:A和B.



英特尔x86系列微处理器为大多数操作提供了一种寄存器式(累加器)指令集,但对于其x87,英特尔8087浮点运算使用堆栈指令,可以追溯到8086和8088的iAPX87(8087)协处理器。是,没有程序员可访问的浮点寄存器,但只有80位宽,8位深的堆栈。x87在很大程度上依赖x86 CPU来帮助执行其操作。



商业堆叠机器
另请参阅:高级语言计算机体系结构
硬件中直接执行的堆栈指令集的示例包括:



GreenArrays公司的144处理器GA144芯片的F18A架构[19] [20] [21]
的巴勒斯大型系统架构(自1961)
在英国电KDF9机。首先在1964年交付,KDF9有一个19深的下推堆栈的算术寄存器,以及一个17层的子程序返回地址栈
在科林斯无线电 柯林斯自适应处理系统小型机(CAPS,自1969年以来)和罗克韦尔·柯林斯公司 高级架构微处理器(AAMP,自1981年以来)。[22]
在UCSD帕斯卡对机器(如帕斯卡微引擎和其他许多人)
MU5和ICL 2900系列。混合堆和累加器机器。累加器寄存器缓冲存储器堆栈的最高数据值。当寄存器溢出到内存堆栈或从那里重新加载时,控制加载和存储操作码的变体。
HP 3000(经典,不是PA-RISC)
串联电脑 T / 16。像HP 3000一样,除了编译器,而不是微代码,当寄存器堆栈溢出到内存堆栈或从内存堆栈中重新填充时,它们都被控制。
所述爱特梅尔 MARC4 微控制器[23]
诸如RTX2000,RTX2010,F21 [25]和PSC1000 [26] [27]等几种“Forth芯片” [24 ]
所述Setun 三元计算机执行均衡三元使用堆栈。
Bernd Paysan的4stack处理器有四个堆栈。[28]
由Charles H. Moore设计的Patriot Scientific的Ignite堆叠机拥有领先的功能密度基准。
萨博爱立信Space Thor 辐射硬化微处理器[29]
Inmos transputers。
ZPU设计用于监督FPGA系统的物理小型CPU 。[30]
虚拟堆栈机器
在软件中解释的虚拟堆栈机的示例:



的油石 ALGOL 60解释代码,[31]在其上巴勒斯B6500的一些特征是基于
的UCSD帕斯卡对机器(这非常类似于伯勒斯)
所述的Niklaus维尔特p代码机
在Java虚拟机指令集
该WebAssembly字节码
用于ECMA 335(Microsoft .NET环境)的CIL(公共中间语言)指令集的VES(虚拟执行系统)
在第四编程语言,特别是第四虚拟机
Adobe的PostScript
鹦鹉编程语言
Sun Microsystems针对Sun Ray 智能卡识别的SwapDrop编程语言
Adobe的AVM2
以太坊的EVM
在CPython的 字节码解释器
在红宝石 YARV字节码解释器
在Rubinius的虚拟机
使用调用堆栈和堆栈帧的计算机
大多数当前的计算机(任何指令集样式)和大多数编译器在内存中使用一个大的调用返回堆栈来组织短期局部变量并返回所有当前活动过程或函数的链接。每个嵌套调用都将在内存中创建一个新的堆栈框架,该框架在该调用完成之前一直存在。这个调用返回堆栈可能完全由硬件通过指令中的专用地址寄存器和特殊地址模式来管理。或者它可能仅仅是编译器遵循的一组约定,使用通用寄存器和寄存器+偏移地址模式。或者它可能介于两者之间。



由于这种技术现在几乎是通用的,即使在注册机器上,将所有这些机器称为堆栈机器也没有多大用处。该术语通常用于也使用表达式堆栈和仅用于堆栈的算术指令来评估单个语句片段的机器。



计算机通常提供对程序全局变量的直接,高效的访问,以及只有当前最内层过程或函数(最上面的堆栈帧)的局部变量。通常不需要对呼叫者的堆栈帧的内容进行“上级”寻址,并且不直接受硬件支持。如果需要,编译器通过传递帧指针作为附加的隐藏参数来支持这一点。



一些Burroughs堆栈机器直接在硬件中支持上层参考,具有专门的地址模式和一个特殊的“显示”寄存器文件,其中包含所有外部范围的帧地址。没有后续的计算机产品线在硬件上完成这项工作 当Niklaus Wirth为CDC 6000开发第一款Pascal编译器时,他发现将帧指针作为链传递的速度更快,而不是不断更新完整的帧指针数组。这种软件方法也不会增加像C这样的常用语言的开销,而这些语言缺少上级参考。



相同的Burroughs机器也支持嵌套任务或线程。任务及其创建者共享创建任务时存在的堆栈帧,但不共享创建者的后续帧或任务自己的帧。这是通过一个支撑仙人掌堆,其布局图类似于一个的躯干和手臂仙人掌仙人掌。每个任务都有自己的内存段,用于存放它的堆栈和它拥有的帧。这个堆栈的基础链接到其创建者堆栈的中间。在具有传统平面地址空间的机器中,创建者堆栈和任务堆栈将在一个堆中成为单独的堆对象。



在一些编程语言中,外部范围数据环境并不总是及时嵌套的。这些语言将其过程“激活记录”组织为单独的堆对象,而不是作为附加到线性堆栈的堆栈框架。



在像Forth这样的简单语言中,缺少局部变量和参数命名,堆栈帧将只包含返回分支地址和帧管理开销。所以他们的返回栈保存裸回地址而不是帧。返回堆栈与数据值堆栈分开,以改善呼叫建立和返回的流程。



另见
皮带机
面向堆栈的编程语言
连续编程语言
应用程序虚拟机的比较


Category lang