动态与静态编译器(JavaScript)
我目前正在用ANTLR+Java编写JavaScript编译器 我在这里读到了关于堆栈溢出的问题,这些问题是关于如何继续执行的——答案总是,对动态语言进行静态编译(没有JIT信息)太难了——但这到底是为什么呢?当然,存在明显的“类型解析”问题,在JavaScript中,动态与静态编译器(JavaScript),javascript,static-analysis,dynamic-languages,compiler-construction,Javascript,Static Analysis,Dynamic Languages,Compiler Construction,我目前正在用ANTLR+Java编写JavaScript编译器 我在这里读到了关于堆栈溢出的问题,这些问题是关于如何继续执行的——答案总是,对动态语言进行静态编译(没有JIT信息)太难了——但这到底是为什么呢?当然,存在明显的“类型解析”问题,在JavaScript中,eval函数可能存在问题,但还有其他原因吗?(因为它们似乎不太难克服纯静态(无JIT)) 我排除了基于JIT的编译,因为我认为这对我来说太难实现了 我在编写执行字节码的静态编译器方面有一些经验 更新: 你所有的答案都对理解这个问题
eval
函数可能存在问题,但还有其他原因吗?(因为它们似乎不太难克服纯静态(无JIT))
我排除了基于JIT的编译,因为我认为这对我来说太难实现了
我在编写执行字节码的静态编译器方面有一些经验
更新:
你所有的答案都对理解这个问题很有帮助。
要澄清这是否意味着JavaScript比其他动态语言更难实现
这是否也意味着我更喜欢使用基于树的解释器而不是字节码(如果我们忘记了JS总是在原始源代码中提供的属性,因此增加了额外的时间来生成和IR,然后执行它)或者它们应该同样容易/难做
(我是SOF新手;不知道这是否是更新问题的首选方式?这段对话可以有很多种方式。这是一个方向。在javascript中,几乎所有内容都是一个对象,可以在运行时将属性或方法添加到任何对象中。因此,在编译时,您不知道哪些方法或属性会或不会附加到对象。因此,必须在运行时查找所有内容 例如:
var myObj = {};
function configureObject() {
if (something in the environment) {
myObj.myfunc = function () {alert("Hi");}
} else {
myObj.myfunc = function () {document.write("Hello");}
}
}
var functionName = RunTuringMachineAndReportOutputOnTape(myTM, myInput);
eval(functionName + "();");
现在,在稍后的代码中调用myObj.myfunc()代码>编译时不知道什么是myfunc
,也不知道它是否是myObj
的属性。它必须是运行时查找
在另一个示例中,以这行代码为例:
var c = a + b;
他的意思完全取决于a和b的类型,而这些类型在编译时是未知的
如果a和b都是数字,那么这是一个加法语句,c将是一个数字
如果a或b是字符串,那么另一个将被强制为字符串,而c将是字符串
您不能将这种逻辑预编译为本机代码。执行环境必须记录这是对这两个操作数之间的加法运算符的请求,并且必须(在运行时)检查两个操作数的类型并决定要做什么。编写静态JavaScript编译器面临的挑战是,通常很难确定在任何程序点引用什么对象或调用什么函数。我可以利用JavaScript是动态的这一事实,根据某个图灵机的输出来决定调用哪个函数。例如:
var myObj = {};
function configureObject() {
if (something in the environment) {
myObj.myfunc = function () {alert("Hi");}
} else {
myObj.myfunc = function () {document.write("Hello");}
}
}
var functionName = RunTuringMachineAndReportOutputOnTape(myTM, myInput);
eval(functionName + "();");
在这一点上,除非您事先知道什么是myTM
和myInput
,否则显然不可能决定调用eval
将调用什么函数,因为如果图灵机停止,则无法确定图灵机磁带上的内容(您可以将停止问题简化为此问题)。因此,无论您多么聪明,无论您构建的静态分析器多么优秀,您都无法正确地静态解析所有函数调用。您甚至无法绑定此处可能调用的函数集,因为图灵机的输出可能会定义由上述代码执行的某个函数
你可以做的是编译代码,每当调用函数时,都包含额外的逻辑来解析调用,并可能使用诸如加速之类的技术。此外,在某些情况下,你可能能够证明某个函数正在被调用(或少数函数中的一个将被调用)然后可以在这些调用中硬编码。您还可以编译一段代码的多个版本,每种常见类型(对象、数字等)一个版本,然后根据动态类型发出代码跳转到相应的编译跟踪。V8可以这样做。请参阅
使用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将激活记录定义为具有原型链的对象。有许多语言是静态编译的,但不能静态解析所有函数调用站点。在C中,可转换函数指针具有这种效果。所有广泛使用的语言的编译器都通过推送一些子集来解决此问题直到运行时,使用各种机制,包括简单的指针解引用、虚拟表、动态代码加载、哈希表查找等,调用站点解析的数量都在不断增加。JavaScript没有什么特别之处可以阻止这种情况的发生,事实证明它已经做到了。@Mike Samuel-啊,我明白你的意思了。我想我是在将OP的问题解释为他可以静态地解析函数调用和访问,这显然不起作用。然而,我认为我的答案解决了这个问题-我在回答的后半部分描述了在不知道调用什么时编译代码的潜在方法。这不够吗?足够公平。只有OP可以澄清他们的问题。+1这个。螺旋图灵机,现实世界的代码有足够多的问题。你能希望编译成的最好的东西(静态的,就是)是解释器将要做的任何事情的展开版本