Hello World
Spiga

Jscex编译器更新:已支持嵌套Jscex函数

2011-04-30 00:11 by 老赵, 2458 visits

Jscex的编译器更新了。之前的编译器并不会将一个Jscex函数内部的其他Jscex函数代码一并展开,这导致内嵌的Jscex函数会在外部函数调用时反复编译,性能开销较大;不过更重要问题,可能是AOT编译后的代码无法彻底解除与编译器的依赖。嵌套Jscex函数是否合理是一回事儿,使用者可以不去这么做,但是编译器本身还是该支持的。这也是Jscex编译器改进计划中的重要一步。

之前,如果您编写这样的函数:

var outerAsync = eval(Jscex.compile("async", function () {

    var innerAsync = eval(Jscex.compile("async", function () {
        // inner implementations
    }));

}));

实际上编译器会生成这样的代码:

var outerAsync = (function () {
    var $$_builder_$$ = Jscex.builders["async"];
    return $$_builder_$$.Start(this,
        $$_builder_$$.Delay(function () {

            var innerAsync = eval(Jscex.compile("async", function () {
                // inner implementations
            }));

            return $$_builder_$$.Normal();
        })
    );
})

这样在每次调用outerAsync的时候,都会重新启用Jscex.compile及eval的过程,这个性能开销还是比较可观的。不过目前的编译器已经会将内部的Jscex函数彻底展开:

var outerAsync = (function () {
    var $$_builder_$$_0 = Jscex.builders["async"];
    return $$_builder_$$_0.Start(this,
        $$_builder_$$_0.Delay(function () {

            var innerAsync = (function () {
                var $$_builder_$$_1 = Jscex.builders["async"];
                return $$_builder_$$_1.Start(this,
                    // compiled inner implementations
                    $$_builder_$$_1.Normal()
                );
            });

            return $$_builder_$$_0.Normal();
        })
    );
})

除了不会多次编译内部的Jscex函数外,还彻底解除了与Jscex编译器的依赖,AOT编译器表示情绪愉快。您可能发现了,原本的builder变量名是固定的,不过为了支持内嵌函数,我在代码里添加了一个种子,这样每次编译时的builder变量名就不同了。例如在上面的代码中,编译后的外层函数使用$$_builder_$$_0,而内层函数使用的则是$$_builder_$$_1。

于是现在我们便可以使用内嵌Jscex函数了,例如之前提到过的“动画”示例,现在便可以写为:

var moveSquareAsync = eval(Jscex.compile("async", function(e) {

    var moveAsync = eval(Jscex.compile("async", function(startPos, endPos, duration) {
        for (var t = 0; t < duration; t += 50) {
            e.style.left = (startPos.x + (endPos.x - startPos.x) * t / duration) + "px";
            e.style.top = (startPos.y + (endPos.y - startPos.y) * t / duration) + "px";
            $await(Jscex.Async.sleep(50));
        }

        e.style.left = endPos.x;
        e.style.top = endPos.y;
    }));

    $await(moveAsync({x:100, y:100}, {x:400, y:100}, 1000));
    $await(moveAsync({x:400, y:100}, {x:400, y:400}, 1000));
    $await(moveAsync({x:400, y:400}, {x:100, y:400}, 1000));
    $await(moveAsync({x:100, y:400}, {x:100, y:100}, 1000));
}));

内层的moveAsync函数直接使用外部函数的参数e,这代码绝对美观大方——至于是否真写这样的代码,就靠开发人员自身的考量了。

话说回来,其实让现有的编辑器支持内嵌的Jscex函数并不困难,事实上原本“不支持”的原因也只是“没想太多”,就这么机械地写下来了。例如最核心的Jscex.compile,其“逻辑”大约是这样的:

Jscex.compile = function (builderName, func) {

    var code = "var f = " + func.ToString() + ";";
    var ast = UglifyJS.parse(code);

    // [ "toplevel", [ [ "var", [ [ "f", [...] ] ] ] ] ]
    var funcAst = ast[1][0][1][0][1];

    // compile funcAst with builderName;
}

简单地说,我是根据传入函数的AST生成代码,当然还有些中间过程,但总体而言的“输入”只是函数本身(外加个builderName)。而为了能够在“生成代码”的过程中继续编译内嵌函数,其实我们也只要支持“标准模式(即eval(Jscex.compile(...))这种形式)”的AST即可:

function compileStandardPattern(evalAst) {
    // ...
}

Jscex.compile = function (builderName, func) {
    var funcCode = func.toString();
    var evalCode = "eval(Jscex.compile(" + JSON.stringify(builderName) + ", " + funcCode + "))"
    var evalCodeAst = UglifyJS.parse(evalCode);

    // [ "toplevel", [ [ "stat", [ "call", ... ] ] ] ]
    var evalAst = evalCodeAst[1][0][1];
    var newCode = compileStandardPattern(evalAst);

    // ...
}

compileStandardPattern方法会根据“标准模式”的AST输出展开后的代码。而对于从Jscex.compile调用中输入的函数对象,我们也将其构造为标准形式并生成AST。然后,compileStandardPattern方法内部在生成代码的时候,如果发现某一部分AST又恰好符合“标准模式”,则递归调用自身来生成内嵌Jscex函数的代码,最终便得到了充分展开的结果。

当然,这么做和AOT编译器都有一个限制:由于其展开方式依靠静态代码分析,因此只能识别出“标准模式”的代码。例如,以下代码可以在JIT编译器下正常工作:

var compile = Jscex.compile;
var builderName = "async";
var func = function () { ... };
var newCode = compile(builderName, func);
var funcAsync = eval(newCode);

但显然,如果这段代码出现在某个Jscex函数内部,或是使用AOT编译器,便无法将其展开了。不过这个“限制”只是理论上的,我还没有想到不用“标准模式”的理由。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

3 条回复

  1. hpf1908
    1.202.198.*
    链接

    hpf1908 2011-05-05 13:41:43

    看到 JsCex 的demo后非常有兴趣,不知道JsCex的性能怎么样,是否有性能上的数据指标可供参考呢?

  2. 老赵
    admin
    链接

    老赵 2011-05-05 13:51:56

    @hpf1908

    我不担心性能,你不会拿JS做计算密集型吧。我觉得Jscex没道理成为性能瓶颈,以“排序动画”示例(大量计算和异步操作),IE 9,Firefox 3,Chrome的耗时几乎一样,因为纯JS执行的部分实在太微不足道了。

    当然Jscex在编译时也考虑到了性能,尽可能多地保证原生代码,让性能损失尽可能小。

  3. littlewater
    74.125.156.*
    链接

    littlewater 2011-11-16 15:44:44

    刚看完GIT上的文档,那这么说文档上的Limitation可以去掉一个了吧=w= 当然很希望另外一个Limitation也可以被早日remove,水水的脑存也可以用来存其他的嘿嘿=w=

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我