我目前正在用ANTLR + Java编写一个JavaScript编译器。
我已经在Stack Overflow上阅读了关于如何继续执行的问题 - 而且答案总是很难进行动态语言的静态编译(没有JIT信息) - 但为什么那到底是什么?当然有明显的“类型解析”问题,在JavaScript中可能是eval
函数的问题 - 但还有其他原因吗? (因为它们似乎不太难以静态克服(没有JITS))
我正在排除基于JIT的编译,因为我认为这对我来说实施起来太难了。
我在使用字节码执行编写静态编译器方面有一些经验。
更新:
您的所有答案都非常有助于理解问题。 澄清这是否意味着JavaScript比其他动态语言更难实现?
这是否也意味着我更好地使用基于树的解释器而不是例如字节码(如果我们忘记JS总是在原始源代码中提供的属性 - 因此增加了生成和IR的额外时间,然后执行它)? - 或者他们应该同样容易/难以做到?
(我是新手SOF;不知道这是否是更新问题的首选方式?)
答案 0 :(得分:6)
这种对话有很多方法可以实现。这是一个方向。在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 :(得分:4)
编写静态JavaScript编译器的挑战在于,通常难以辨认的硬来确定在任何程序点引用哪些对象或调用哪些函数。我可以使用JavaScript是动态的,以根据某些图灵机的输出决定调用哪个函数。例如:
var functionName = RunTuringMachineAndReportOutputOnTape(myTM, myInput);
eval(functionName + "();");
此时,除非您事先了解myTM
和myInput
是什么,否则显然不可能决定调用{{ {1}},因为如果它停止在图灵机的磁带上确定什么是不可判定的(你可以减少这个问题的暂停问题)。因此,无论您有多聪明,无论您构建的静态分析器有多好,您都无法正确地静态解析所有函数调用。你甚至无法绑定可能在这里调用的函数集,因为图灵机的输出可能会定义一些函数,然后由上面的代码执行。
您可以做的是编译代码,无论何时调用函数,该代码都包含用于解析调用的额外逻辑,并且可能使用inline caching等技术来加快速度。此外,在某些情况下,您可能能够证明正在调用某个函数(或者将调用少量函数之一),然后可以在这些调用中进行硬编码。您还可以编译一段代码的多个版本,每个版本对应一种常见类型(对象,数字等),然后根据动态类型发出代码以跳转到相应的编译跟踪。
答案 2 :(得分:2)
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将激活记录定义为具有原型链的对象。