在编译器中实现闭包

2024-02-17

我正在尝试设计一个伪汇编代码的基本编译器。但是,我不知道如何实现闭包。看来我需要将特定的寄存器值与每个“子例程”相关联。我考虑过使用堆栈,但它似乎又不够。似乎只有关联数组才能工作,但是如何在汇编中完成该操作或类似的操作呢?

我选择尝试表示的示例如下,为了简洁起见,以 CoffeeScript 进行交流。

((x) -> (y) -> x(y))((x) -> x)(2)

这是我一直在尝试的总体结构。这是我正在编译的伪程序集的示例。

'((label lam1)   ;; (x) -> x
  (cp resp arg)
  (ret)

  (label lam2)   ;; (y) -> x(y)
  (jmp x)

  (label lam3)   ;; (x) -> [(y) -> ...]
  (cp x arg)     ;; this is the assignment intended for a closure
  (cp resp lam2) ;; this is a returned lambda, needing the closure
  (ret)

  (label main)
  (cp arg lam1)
  (call lam3)
  (set arg 2)
  (call resp))) 

这有效;但是,该值只是在名称下设置x然后返回一个 lambda,x在执行 lambda 之前,值很容易被污染。

计算机程序的结构和解释中的实现描述如下,这对我来说在汇编中似乎不可行。我不知道他们还能使用什么其他策略。

过程对象将在运行时通过将当前环境(定义时的环境)与已编译过程的入口点(新生成的标签)相结合来构造。

总之,寄存器值如何与“子程序”相关联?堆栈就足够了吗?


堆栈是不够的...考虑一个更简单的情况

function bar(f) {
    alert(f());
}

function foo(x) {
    bar(function(){ return x; });
}

foo(42);

在上述情况下,理论上是可以的x在闭包中居住在堆栈框架中foo因为闭包不会比它的创建者活得更久foo。 然而,有一个小小的改变:

function bar(f) {
    to_call_later.push(f);
}

闭包将被存储起来,并且可能会在以下情况下被调用:foo已经终止并且其激活记录的堆栈空间已被回收。清楚地x不能位于该堆栈区域中,因为它必须生存。

因此存在两个问题:

  1. 一个闭包必须有一些存储(环境)。当你认为调用时,这是显而易见的foo两次传递两个不同的值应该创建两个独立的存储x。如果闭包只是代码,那么这是不可能的,除非每次调用时都会生成不同的代码foo.

  2. 该存储必须存在at least只要闭包本身,而不仅仅是谁创建了闭包。

另请注意,如果您想要读/写封闭变量,则需要额外的间接级别,例如:

function bar(f) {
    alert(f());
}

function foo(x) {
    var c1 = function() { return ++x; };
    var c2 = function() { return x *= 2; };
    bar(c1);
    bar(c2);
}

foo(42);  // displays 42+1=43 and 43*2=86 (not 42*2=84!)

换句话说,你可以有几个不同的闭包共享the same环境。

So x不能在堆栈中foo激活记录,它不能位于闭包对象本身中。闭包对象必须有一个pointer去哪里x活着。

在 x86 上实现此功能的可能解决方案是:

  • 使用垃圾收集或引用计数内存管理系统。堆栈目前还不足以处理闭包。

  • 每个闭包都是一个具有两个字段的对象:一个指向代码的指针和一个指向封闭变量(“环境”)的指针数组。

  • 执行代码时你有一个堆栈esp以及例如esi指向闭包对象本身(所以(esi)是代码的地址,(esi+4)是第一个封闭变量的地址,(esi+8)是第二个封闭变量的地址,依此类推)。

  • 每个变量都是一个独立的堆分配对象,只要仍有闭包指向它,它就可以生存。

这当然是一种非常粗暴的做法。例如,SBCL 更加智能,未捕获的变量仅分配在堆栈和/或寄存器上。这需要对闭包的使用方式进行分析。

Edit

假设您只考虑纯函数设置(换句话说,函数/闭包的返回值仅取决于传递的参数并且闭包状态不能改变),那么事情可以稍微简化一些。

您可以做的是制作包含捕获的闭包对象values而不是捕获的变量并且通过同时使闭包本身成为可复制对象,理论上可以使用堆栈(除了闭包的大小可能会根据需要捕获的状态量而变化的问题),所以这并不容易至少对我来说,在这种情况下可以想象一个合理的基于堆栈的参数传递和值返回协议。

通过使闭包成为固定大小的对象来消除可变大小问题,您可以看到这个 C 程序如何仅使用堆栈来实现闭包(请注意,没有malloc calls)

#include <stdio.h>

typedef struct TClosure {
    int (*code)(struct TClosure *env, int);
    int state;
} Closure;

int call(Closure *c, int x) {
    return c->code(c, x);
}

int adder_code(Closure *env, int x) {
    return env->state + x;
}

int multiplier_code(Closure *env, int x) {
    return env->state * x;
}

Closure make_closure(int op, int k) {
    Closure c;
    c.state = k;
    c.code = (op == '+' ? adder_code : multiplier_code);
    return c;
}

int main(int argc, const char *argv[]) {
    Closure c1 = make_closure('+', 10);
    Closure c2 = make_closure('*', 3);
    printf("c1(3) = %i, c2(3) = %i\n",
           call(&c1, 3), call(&c2, 3));
    return 0;
}

Closure结构可以传递、返回并存储在堆栈上,因为环境是只读的,因此您不会遇到生命周期问题,因为可以复制不可变数据而不影响语义。

C 编译器可以使用这种方法来创建只能按值捕获变量的闭包,这确实是 C++11 lambda 所提供的(您也可以通过引用捕获,但要由程序员来确保捕获变量的生命周期)足够持续)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在编译器中实现闭包 的相关文章

  • 在 x86-64 CPU 上通过交叉修改代码重现意外行为

    Question 对于可能在 x86 或 x86 x64 系统上触发意外行为的交叉修改代码有哪些想法 在这些系统中 交叉修改代码中的所有操作均已正确完成 但在执行处理器之前执行序列化指令除外修改代码 如下所述 我有一个 Core 2 Duo
  • 当我使用 yymore() 时,在 EOF 处出现 Flex 错误“缓冲区末尾丢失”

    我正在编写一个 Flex 程序来处理字符串常量 当输入文件在字符串中遇到 EOF 时 我想返回一个 ERROR 标记 文件遇到 EOF 并打印 ERROR 后出现以下错误 致命的 Flex 扫描仪内部错误 缓冲区末尾丢失 这是我的代码 可以
  • 向 Java 类添加编程注释

    使用示例 我想在类字段上添加一个自定义注释 MyContainer 然后在所有此类字段上自动添加相关的 Hibernate 注释 取决于字段类型和属性 另外 我需要向类添加 JAXB XmlType 注释 并使类型名称基于类名称 我还想根据
  • 解决斐波那契数列的 Lisp 方法

    我想尝试学习 Lisp 但很快就放弃了 我想我会再试一次 我正在看 求 400 万以下所有偶数斐波那契数的总和 我写了下面的代码 它可以工作 但是很丑陋 其中最主要的是它太慢了 因为它一直在进行简单的递归 当我用 Python 编写这个程序
  • 68HC11计算sin(x)的汇编代码

    68HC11 使用泰勒级数或查找表计算正弦值的汇编代码是什么 显示值只能是整数 查找表如何工作 在这种情况下 如何使用它来实现泰勒级数 http en wikipedia org wiki Taylor series 如果您正在寻找浮点解决
  • 你能克隆一个闭包吗?

    A FnMut由于显而易见的原因 闭包无法被克隆 但是Fn闭包具有不可变的范围 有没有办法创建一个 重复 Fn关闭 尝试克隆它会导致 error E0599 no method named clone found for type std
  • 是否有像 gccxml 这样的用于生成包装器的 C 标头解析器工具?

    我需要为一种新的编程语言编写一些 C 标头包装器 并且想要类似 gccxml 的东西 但不完全依赖 gcc 以及它在 Windows 系统上带来的问题 只需要读C而不是C 只要有完整的文档记录 任何格式的输出都可以 Linux Solari
  • JavaScript:事件处理程序:在哪里声明变量 - 本地变量还是闭包(与开销)?

    我发现自己编写了各种包含事件处理程序的函数 感觉最好在父函数 闭包 的根部声明处理函数所需的变量 特别是如果它们是 jQuery 选择 多个处理程序所需的常量 或者需要一些我不想要的预计算每次触发事件时重复 一个简单的例子 var touc
  • 生成 C / C++ 代码时表达式的结合性和优先级?

    我编写了一个生成 AST 的基本编译器 正确考虑了表达式中运算符的优先级 但是 在执行代码生成以生成 C 代码时 我不确定如何处理括号的使用 对于这个表达式 A B c AST如下 A B C 应该正确生成包含括号的前一个表达式 但是如果第
  • 为什么我的空循环在 Intel Skylake CPU 上作为函数调用时运行速度是原来的两倍?

    我正在运行一些测试来比较 C 和 Java 并遇到了一些有趣的事情 在 main 调用的函数中 而不是在 main 本身中 运行具有优化级别 1 O1 的完全相同的基准代码 导致性能大约翻倍 我正在打印 test t 的大小 以毫无疑问地验
  • 无法实现模块模式

    我正在尝试重现 Douglas Crockford 所著的 Javascript The Good Parts 一书中的一些代码 这个想法是使用闭包进行对象封装并避免Javascript固有的全局变量 var serial maker fu
  • 大会,你好世界问题

    我正在 Linux 上学习 asm noobuntu 10 04 我得到了以下代码 http asm sourceforge net intro hello html http asm sourceforge net intro hello
  • AVX-512CD(冲突检测)与原子变量访问有何不同?

    所以我在看他们展示了如何 void Histogram const float age int const hist const int n const float group width const int m const float o
  • ARMv8 A64 汇编中立即值的范围

    我的理解是 ARMv8 A64 汇编中的立即参数可以是 12 位长 如果是这样的话 为什么这行汇编代码是 AND X12 X10 0xFEF 产生此错误 使用 gcc 编译时 Error immediate out of range at
  • 警告:格式“%d”需要类型“int *”,但参数 2 的类型为“int”

    所以我是 C 的新手 并且对这个警告发生的情况遇到了麻烦 该警告是什么意思以及我该如何解决它 我写的代码在这里 void main void char name int age 0 printf input your name n scan
  • 是否可以用 C# 为 Android 编写应用程序?

    我们都知道Android运行Dalvik VM程序 通常开发人员用 Java 编写程序并将其编译为 Dalvik 字节码 我想知道是否有可能创建一个可以接受 C 代码并将其编译为 Dalvik 字节码的编译器 嗯 这是一种选择 或者您可以在
  • FreePascal x64 上系统单元函数的汇编调用

    我有一些 Delphi 汇编代码 可以在 Win32 Win64 和 OSX 32 上编译并正常工作 XE2 但是 由于我需要它在 Linux 上工作 所以我一直在考虑编译它的 FPC 版本 到目前为止 Win32 64 Linux32 6
  • 学习 LISP 的最佳方法是什么? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何仅使用单个数组在 JavaScript 中模拟调用堆栈

    我正在看维基百科页面 https en wikipedia org wiki Call stack在调用堆栈上 并尝试理解这个图像 据我所知 哈哈 const memory memory 0 3 top of stack pointer m
  • 在 x86 程序集中存储大量布尔值的最佳方法是什么?

    最近我一直在处理充满布尔值的大型数组 目前 我将它们存储在 bss部分有一个 space指令 它允许我创建字节数组 但是 由于我只需要存储布尔值 因此我希望从数组中逐位读取和写入数据 目前 我能想到的最好方法是有一个 space指令所需存储

随机推荐