动态vs静态编译器(JavaScript)

Dynamic vs Static Compiler (JavaScript)

本文关键字:JavaScript 编译器 vs 静态 动态      更新时间:2023-09-26

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

我在Stack Overflow上看到了一些关于如何继续执行的问题——答案总是说,对动态语言进行静态编译(没有jit信息)太难了——但这到底是为什么呢?当然有明显的"类型解析"问题,在JavaScript中可能是eval函数的问题-但还有其他原因吗?(因为纯静态克服它们似乎并不太难(没有jit))

我排除了基于jit的编译,因为我认为这对我来说太难实现了。

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

更新:

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

这是否也意味着我使用基于树的解释器比例如字节码更好(如果我们忘记了JS总是在原始源代码中发布的属性-因此增加了额外的时间来生成和IR,然后执行它)?——或者它们应该同样容易/难做吗?

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

这种对话有很多种方式。这是一个方向。在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将是字符串。

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

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

var functionName = RunTuringMachineAndReportOutputOnTape(myTM, myInput);
eval(functionName + "();");
在这一点上,除非您预先知道myTMmyInput是什么,否则是不可能确定调用eval将调用什么函数的,因为如果图灵机停止,则无法确定图灵机磁带上的内容(您可以将停止问题减少为此问题)。因此,无论您多么聪明,无论您构建的静态分析器有多好,您都永远无法正确地静态解析所有函数调用。你甚至不能绑定这里可能调用的函数集,因为图灵机的输出可能定义了一些函数,然后由上面的代码执行。

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

V8就是这样做的。参见使用V8将JavaScript编译为本机代码

对于EcmaScript 3和5 non-strict,在作用域周围有一些你在其他动态语言中不会遇到的问题。您可能认为对局部变量进行编译器优化很容易,但是在语言中存在一些边缘情况,当它不是,甚至忽略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将激活记录定义为带有原型链的对象。