【问题标题】:Dynamic vs Static Compiler (JavaScript)动态与静态编译器 (JavaScript)
【发布时间】:2011-08-24 18:55:10
【问题描述】:

我目前正在用 ANTLR+Java 编写一个 JavaScript 编译器。

我在 Stack Overflow 上阅读了有关如何继续执行的问题 - 答案始终是对动态语言进行静态编译(没有 JIT 信息)太难了 - 但为什么是这样吗?当然有明显的“类型解析”问题,在 JavaScript 中可能是 eval 函数的问题 - 但还有其他原因吗? (因为它们似乎不太难克服纯静态(无 JITS))

我不包括基于 JIT 的编译,因为我认为这对我来说太难实现了。

我在编写带有字节码执行的静态编译器方面有一些经验。

更新:

你所有的答案都对理解问题很有帮助。 澄清一下这是否意味着 JavaScript 比其他动态语言更难实现?

这是否也意味着我使用基于树的解释器比使用基于树的解释器更好。字节码(如果我们忘记了 JS 总是以原始源代码提供的属性 - 因此增加了额外的时间来生成和 IR 并随后执行它)? - 还是应该同样容易/难做?

(我是 SOF 新手;不知道这是否是更新问题的首选方式?)

【问题讨论】:

    标签: javascript static-analysis dynamic-languages compiler-construction


    【解决方案1】:

    这种对话可以有很多方式。这是一个方向。在 javascript 中,几乎所有东西都是对象,并且可以在运行时将属性或方法添加到任何对象。因此,您在编译时不知道哪些方法或属性将或不会附加到对象。因此,必须在运行时查看所有内容。

    例如:

    var myObj = {};
    
    function configureObject() {
        if (something in the environment) {
            myObj.myfunc = function () {alert("Hi");}
        } else {
            myObj.myfunc = function () {document.write("Hello");}
        }
    }
    

    现在,稍后在您调用myObj.myfunc(); 的代码中,在编译时不知道myfunc 是什么,甚至不知道它是否是myObj 的一个属性。它必须是运行时查找。

    再举一个例子,取这行代码:

    var c = a + b;
    

    他的意思完全取决于 a 和 b 的类型,而这些类型在编译时是未知的。

    如果 a 和 b 都是数字,那么这是一个加法语句,c 将是一个数字。

    如果 a 或 b 是字符串,则另一个将被强制转换为字符串,而 c 将是字符串。

    您不能将这种逻辑预编译为本机代码。执行环境必须记录这是对这两个操作数之间的加法运算符的请求,并且它必须(在运行时)检查两个操作数的类型并决定要做什么。

    【讨论】:

    • +1 这个。螺丝图灵机,现实世界的代码有足够多的问题。您希望将其编译成(即静态地)最好的东西是解释器无论如何都会做的展开版本。
    【解决方案2】:

    编写静态 JavaScript 编译器的挑战在于,通常难以确定确定在任何程序点引用了哪些对象或调用了哪些函数。我可以利用 JavaScript 是动态的这一事实来根据某些图灵机的输出来决定调用哪个函数。例如:

    var functionName = RunTuringMachineAndReportOutputOnTape(myTM, myInput);
    eval(functionName + "();");
    

    此时,除非您提前了解 myTMmyInput 是什么,否则可能无法确定调用 eval 将调用哪个函数,因为如果图灵机停止,则无法确定图灵机磁带上的内容(您可以将停止问题简化为该问题)。因此,无论您多么聪明,无论您构建的静态分析器多么出色,您都永远无法正确地静态解析所有函数调用。您甚至无法绑定可能在此处调用的函数集,因为图灵机的输出可能会定义一些函数,然后由上述代码执行。

    您可以做的是编译代码,每当调用一个函数时,该代码包含用于解析调用的额外逻辑,并且可能使用inline caching 等技术来加快处理速度。此外,在某些情况下,您可能能够证明某个函数正在被调用(或者少数函数之一将被调用),然后可以在这些调用中进行硬编码。您还可以编译一段代码的多个版本,一个用于每种常见类型(对象、数字等),然后发出代码以根据动态类型跳转到适当的编译跟踪。

    【讨论】:

    • 有许多语言是静态编译的,但不能静态解析所有函数调用站点。在 C 中,可转换函数指针具有这种效果。所有广泛使用的语言的编译器都通过使用各种机制(包括简单的指针取消引用、虚拟表、动态代码加载、哈希表查找等)将调用站点解析的某些子集直到运行时来解决这个问题。 JavaScript 并没有什么特别之处可以防止这种情况发生,这一点已经完成。
    • @Mike Samuel- 啊,我明白你在说什么。我想我是在解释 OP 的问题,说他可以静态解决函数调用和访问,这显然是行不通的。但是,我认为我的答案解决了这个问题 - 我在答案的后半部分描述了当您不知道自己在调用什么时可以编译代码的潜在方法。这还不够吗?
    • 很公平。只有 OP 可以澄清他们的问题。
    【解决方案3】:

    V8 就是这样做的。见Compile JavaScript to Native Code with V8

    对于非严格的 EcmaScript 3 和 5,范围周围有许多皱纹,这是您在其他动态语言中不会遇到的。您可能认为对局部变量进行编译器优化很容易,但实际上语言中存在边缘情况,即使忽略 eval 的作用域自省。

    考虑

    function f(o, x, y) {
      with (o) { return x + y + z; }
    }
    

    调用时

    o = {};
    o = { z: 3 };
    o = { x: 1, z: 2 };
    Object.prototype.z = 3, o = {};
    

    并且根据 EcmaScript 3,

    x = (function () { return toString(); })()
    

    应该会产生与

    完全不同的结果
    x = toString();
    

    因为 EcmaScript 3 将激活记录定义为具有原型链的对象。

    【讨论】:

      猜你喜欢
      • 2012-09-17
      • 1970-01-01
      • 2015-03-06
      • 1970-01-01
      • 1970-01-01
      • 2010-11-12
      • 2010-11-25
      • 1970-01-01
      • 2018-11-08
      相关资源
      最近更新 更多