Tamarine

本系列主要是用来研究AVM2和Tamarine相关的技术应用。
一、相关概念
(灰色部分选自 http://www.ibm.com/developerworks/cn/web/wa-actionscript/index.html)
在 2006 年 11 月 7 日,Adobe Systems 宣布将 ActionScript Virtual Machine 的代码捐献给 Mozilla Foundation。因此,Mozilla 启动了一个新的开放源码项目 Tamarin。Adobe 和 Mozilla 希望以此促进这种创建 Web 应用程序的标准化语言的推广。
Tamarin 项目的目标是实现一个高性能的开放源码的 ECMAScript 第四版(ES4)语言规范实现。Mozilla 在 SpiderMonkey 中使用 Tamarin Virtual Machine,SpiderMonkey 是 Firefox 和其他基于 Mozilla 的产品中嵌入的核心 JavaScript 引擎。Adobe 将继续在 Adobe Flash Player 中的 ActionScript Virtual Machine 中使用这些代码。
Tamarin Virtual Machine 当前实现 ECMAScript 第三版语言标准(这是 JavaScript、Adobe ActionScript 和 Microsoft Jscript 采用的标准),还支持 ES4 中建议的一些新语言特性。社区正在开发 ES4 的开放源码实现,Adobe 和 Mozilla 希望以此促进这种创建 Web 应用程序的标准化语言的推广。Tamarin 项目的目标是帮助开发人员创建可以跨多个平台的效果丰富的高度交互性的用户体验。
Tamarin 将支持 ECMAScript 第四版(或者说 “JavaScript 2”)语言,并将集成进 Mozilla 2 项目中的 SpiderMonkey,预期在 2008 年发布。
Mozilla 的开放源码项目团队刚刚开始开发 Tamarin,它的一些目标技术包括:
* 1. 将 Tamarin VM 和垃圾收集器集成进 SpiderMonkey 中
* 2. 使用 SpiderMonkey 编译器生成 Tamarin 的代码
* 3. 将即时编译器移植到新的硬件平台
* 4. 完成 ECMAScript 4 编译器
从以上概述,我们可以对Tamarin有一个初步的认识。虽然Tamarin实现的是一个ES4引擎,但这个项目对我们认识AVM2虚拟机和实现Flex下的脚本解析器活脚本引擎绝对有重要的意义。
下面我们来看一张图:

图描述了AVM2框架的各个部分和各部分之间的关系。可以看出,是的Flash Player 9有JIT complier,同时,它也有Interpreter,并且一切都建立在.abc之上。
随着Flash Player以后的版本将基于tamarine平台,将会有更多的脚本语言能够参与进来,包括目前tamaine的ES4,AS3(Action Monkey)和Iron系列(见IronMonkey, 可能包括IronPython,IronRuby,IronPHP等),到此,像不像.NET framework的感觉了? 甚至还要代替IE的JavaScript、VBScript客户端脚本(见ScreamingMonkey)。
好了,前景先简单介绍到这里,下面,让我们进入tamarine及其AVM2相关的世界中吧。
二、获得tamarin
我们可以用多种途径很轻松的获得tamarin。由于tamarin项目没有release,目前还只是模型阶段。
1、我们可以用mercurial的命令:
hg clone http://hg.mozilla.org/tamarin-tracing/
(没有hg的,可以用APT来安装,sudo apt-get install mercurial)
2、可以直接用浏览器从http://hg.mozilla.org/tamarin-tracing/上点击gz或zip链接来下载。
下载完毕后,就可以编译了。
这里注意编译环境:
1、在WINDOWS下,通过项目文件夹\platform\win32下的avmplus_9.sln或avm_plus_8.sln打开整个项目。
注意解决方案中有zlib,而tamarin源码包并没有包括zlib, 去相关网站下载zlib源码,并配置好路径即可。
编译时,core文件夹下的ErrorCostants.cpp可能出错,这个文件是错误信息常量表,可能是因为字符集的问题,我的解决方案很简单,删除多余的语言,只留en,即数组只留en。其他全部去掉,这样编译就通过了,反正这个文件没什么大用。
2、在LINUX下编译,最好建立一个空的文件夹,然后编译:
mkdir build
cd build
../tamarin-tracing/configure –enable-shell –enable-Debugger
编译最好加 –enable-shell –enable-Debugger这两个选项,否则,嘿嘿,第一个不加是不会有可执行的shell的,至于第二个不加,调试,汇编都不会有。
configure后生成Makefile文件。然后就可以make了。make后会生成shell目录,里面就有我们最终想要的avmshell这个Interpreter了。
3、测试一下:首先作一个hello.as 内容为print (“Hello world”);
然后用Flex-sdk的asc.jar:
java -jar asc.jar hello.as
显示 hello.abc, 84 bytes written
ls一下,可以看到hello.abc
然后./avmshell hello.abc
输出结果Hello world
好了,今天简单介绍这些作为进入tamarin世界的热身运用,下一期,我们来深入了解avmshell这个shell工具
==============================================
由于资料较少,肯定会有遗漏,错误和要补充的地方,欢迎大家讨论并指正。
1、越过编译步骤
我们接着说这个avmshell这个tamarin编译后最终生成的可执行文件。其实呢,当我们了解如何编译后,我们完全可以掠过这个恼人而有时又不成功的过程。
我们从adobe的opensource站上弄下来flex-sdk, 并用ant编译
svn checkout http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/ flex-sdk
cd flex-sdk
ant
这样,我们就可以获得这个avmshell了:
在flex-sdk/modules/asc/bin/linux下有avmplus_s
在flex-sdk/modules/asc/bin/windows下有avmplus_s.exe
这个avmplus_s与我们编译过的avmshell是相同的。
我们再看看flex-sdk/modules/asc/bin/这个目录,你会发现很多有趣的东西,比如asc.exe就是我们上一章用到的 asc.jar,而abcdump.exe就是反编译as3 swf的工具,这个反编译器的制作方法可以参考https: //www.flashsec.org/wiki/Simple_AS3_Decompiler_Using_Tamarin页面下放的How to build an AS3 decompiler。
而且flex-sdk/modules/asc/已经开放了源码,在src文件夹内,也就是说flex sdk\bin下的asc.exe的代码可以在这里参考。
顺便提一下,在flex-sdk/modules/真的有不少好东西,可不光是asc哦,还有一个compiler, 这东西又是做什么的呢,看一下Readme发现,这个东西是用来编译mxml的,是mxmlc某种意义上的替代品,成品名叫flex-compiler- oem.jar。位置在flex-sdk/lib/flex-compiler-oem.jar。
Readme里提供了用法:
import flex2.tools.oem.Application;
import java.io.*;
public class Example
{
public static void main(String[] args)
{
try
{
Application application = new Application(“example”, new File(“example.mxml”));
Configuration c = application.getDefaultConfiguration();
c.setLicense(“compiler”,”0000-0000-0000-0000-0000-0000″);
application.setConfiguration(c);
application.setOutput(new File(“example.swf”));
application.build();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
example.mxml:




然后javac -classpath ${flex.dir}/lib/flex-compiler-oem.jar Example.java,这样就可以了。
当然,这并不是真正的mxmlc,但是仍然可以使用,并且这个complier是有源码提供的哦。
至于modules下其他东西,请大家自己研究吧,再研究这个就跑题了。
2、avmshell命令行操作
上面我们简单介绍了asc工具的来历,它是专门编译AS3到.abc的工具。回忆一下上一章的图,一切都是从这个.abc开始的,为了简明,我们在前几章都假设都是用AS来生成.abc文件,而不是用ES4,因为ES4偶也没学好,呵呵,并且反正生成的.abc是一样的。之后我们可能会讲到另一个ES4的编译器,它叫esc。
首先,回顾上一章那个最简单的程序hello.as:
print(“”Hello world”)
它用ASC编译后成为hello.abc。
我们来看看avmshell的常用参数:
最常用的,我看就是-lifespan了。
./avmshell -lifespan hello.abc
输出
Hello world
Run time was 195 msec = 0.20 sec
这跟time ./avmshell hello.abc用法类似。
另一个是./avmshell -Dverbose hello.abc ,则输出详细汇编信息。
想要更狠更详尽的输出信息? 试试这个 ./avmshell -Dverbose_init hello.abc ,他将会把builtin的汇编信息一同输出,接下来就请看满屏幕飞的代码吧。
其他常用参数
-log 参数, 主要用于输出到文件。
-Dtimeout,设置最多执行15秒
-Dnoloops,设置禁止循环
-Dnogc,不进行垃圾收集
-Dgcstats,生成gc统计信息
-Dstats ,显示优化信息统计
大家可以逐个去试试,也可以去看源码部分。
今天先到这里,接下来会继续深入tamarin的核心部分,如builtin内建包和shell包以及如何用native关键字融合本地C代码,进行JIT编译优化。
1、ASC编译器简介
avmshell执行了abc文件,并输出结果,后面跟的具体的参数主要是用于输出管理,而编译AS3文件的重任则落在了asc.jar上。
上一次我们提到如何简单地使用asc编译器编译.as文件生成.abc。然而Hello world永远都是那么的简单。
那么这一节,我们就来重点看看这个 asc编译器。
源码:flex-sdk/modules/asc/src
成品:asc.jar
主要用法和参数,输入java -jar asc.jar回车即可看到:
asc {-AS3|-ES|-d|-f|-h|-i|-import |-in |-m|-p}* filespec
……
……
2、制作asc.exe
当然,我们也可以把它制作成可执行的文件
我们首先作一个asc.as,内容如下:
package
{
import avmplus.System;
var cmdline = “java -jar asc.jar”;
if( System.argv.length > 0 )
{
cmdline += ” ” + System.argv.join( ” ” );
}
System.exec( cmdline );
}
然后,我们利用asc编译器:
java -jar asc.jar asc.as回车
输出 asc.abc, 205 bytes written
这样,我们可以./avmshell asc.abc来代替java -jar asc.jar,以后就用avmshell来编译和执行文件了。
当然,这样作还是不方便,那么,我们可以这样:
java -jar asc.jar -exe asc.exe asc.as
输出
asc.abc, 205 bytes written
java.io.FileNotFoundException: asc.exe (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.(FileInputStream.java:106)
at macromedia.asc.embedding.Compiler.createProjector(Unknown Source)
at macromedia.asc.embedding.Compiler.doCompile(Unknown Source)
at macromedia.asc.embedding.Compiler.doCompile(Unknown Source)
at macromedia.asc.embedding.Main.handleFile(Unknown Source)
at macromedia.asc.embedding.Main.main(Unknown Source)
asc.exe, 0 bytes written
提示没有这个文件asc.exe生成失败。这可能是个BUG,呵呵,不过解决这个问题很简单,建立一个空的asc.exe就可以了:
cat /dev/null > asc.exe
然后java -jar asc.jar -exe asc.exe asc.as
输出
asc.abc, 205 bytes written
asc.exe, 213 bytes written
好了,asc.exe生成完毕。这样,我们就可以使用这个asc.exe来代替java -jar asc.jar 了
当然,在linux下没这么好的待遇,不过有一个asc.sh,就在flex-sdk/modules/asc/bin下
至于这个神奇的avmplus.System包,它居然能对命令行操作,它的原型是
public native static function exec(command:String):int
也就是说他执行的native的本地代码,它脱离了AVM2的平台去执行了已经设计好的本地代码。大家翻开core目录下的任意.cpp和.as代码
发现都有大量的native代码出现。
其实大家在开发的时候,也会主义到,flex基类里也有很多native代码,比如常用的数字转型:Number(expression)。
这类的本地代码主要安装在解释器内部,如Flash Player 以本机代码的形式实现。
当然也可以是你自己的avmshell。或者自己的MyFlash Player或本地定制的AIR运行时环境。听到这很令人激动不是么?
比如制作public native static function alert(text:String):void 在win下来执行
MessageBoxW(NULL, text->c_str(), L”我的AIR”, MB_ICONEXCLAMATION);
在linux下执行其他的比如QMessageBox等。
当然我们还能制作一些函数调用本地dll等,我们利用这些native代码,可以实现另一个Yahoo ! Widget桌面小件平台运行时环境。
不过大家别激动,关于这些内容,我们在以后的章节会继续详细讨论。
性子急的朋友,可以现看看tamarine-tracing/shell/SystemClass.cpp
3、更复杂的编译:
1) -strict treat undeclared variable and method access as errors
意思是把没定义的变量和方法一律视为错误
首先,我们还是以之前的demo为例子,只是这次我们加上参数-strict
java -jar asc.jar -strict hello.as
输出
[Compiler] Error #1180: Call to a possibly undefined method print.
hello.as, Ln 1, Col 1:
print (“Hello world”);
^
1 error found
在编译hello.as时,我们并没有定义print方法,所以使用strict报错
平时,在编译as文件的时候,一般asc会包含一些内建的方法,所以我们并不需要包含这些文件,但是如果我们使用-strict选项,则必须引用完全。
2)-import = make the packages in the specified file available for import
意思是在指定的文件的packages包内添加import引用
所以我们可以这样写上面的例子:
java -jar asc.jar -strict -import ../../tamarin-tracing/core/builtin_full.abc -import ../../tamarin-tracing/shell/shell_full.abc hello.as
每一个要import的对象前都要写-import,路径要写对,在本文里,编译和执行的目录相对于tamarin项目是../../。
输出
hello.abc, 69 bytes written
还记得第一章编译的输出吗?不用-strict的输出是hello.abc, 84 bytes written,。可见,使用-strict后,尺寸缩小了。
我们再来一个复杂点的例子, Test.as :
package {
public class Test {
public function Test() {
trace(“hello World”);
}
}
}
编译 java -jar asc.jar Test.as
却输出:
[Compiler] Error #1017: The definition of base class Object was not found.
Test.as, Ln 1, Col 1:
package {
^
1 error found
为什么呢,是因为类声明全写为public class Test extend Object因为继承Object所以省略了,但是Object类编译时没有包含进来。
我们可以 java -jar asc.jar -import ../../tamarin-tracing/core/builtin_full.abc Test.as
输出 Test.abc, 182 bytes written
这个builtin_full.abc是什么呢,它是怎么构建的,答案是tamarin-tracing/core/builtin.py脚本,关于这个脚本,我们以后再详细讨论。
3) -in = include the specified filename
(multiple -in arguments allowed)
编译时包含指定文件。允许多个in选项
这个-in跟-import差不多,但也不完全一样。-import 等于是在package {} 里写import ,而-in是在编译过程中加入主文件的附属文件,即编译多个文件的意思。
比如,再写一个TestImpl.as:
package com {
public class TestImpl {
public function TestImpl() : void {
var test : Test = new Test();
}
}
new TestImpl();
}
然后编译这个文件:
从2) 小节已知的知识我们可能要这样输入命令:
java -jar asc.jar -import ../../tamarin-tracing/core/builtin_full.as -import Test.as TestImpl.as
执行成功了,输出
TestImpl.abc, 201 bytes written
然而,运行它的时候,就出问题了:
./avmshell TestImpl.abc
输出
VerifyError: Error #1014: Class Test could not be found.
at TestImpl$iinit()
at global$init()
从输出信息来看,是Test类没有找到,但是,我们的确引入了Test.as了阿。
原因是因为 -in选项,编译的时候并没有让Test.as加入到此次编译过程中,而只是-import了Test.abc,相当于在TestImpl里加了import com.TestImpl;但Test.as并没有加入到项目中。
这时,我们应该使用-in选项:
java -jar asc.jar -import ../../tamarin-tracing/core/builtin_full.as -in Test.as TestImpl.as
输出
TestImpl.abc, 384 bytes written
字节明显比刚才的201 bytes多。
现在我们执行它:
./avmshell TestImpl.abc
输出
hello World
4) -swf classname,width,height[,fps] = emit a SWF file
编译成为swf文件,格式为-swf 类名, 宽, 高, [祯率]
大家记得,还有个mxmlc吧,这个也能编译swf,有什么区别呢?
我们制作一个文件来测试一下,MyTest.as:
package {
import flash.display.*;
import flash.text.*;
public class MyTest extends Sprite {
public function MyTest() {
var test:Test = new Test();
var text = new TextField();
text.width = 400;
text.x = 0;
text.y = 0;
text.text = 'This is only a Test';
addChild(text);
}
}
}
我们编译这个文件mxmlc MyTest.as
输出/home/test/dev/tamarin/build/shell/MySprite.swf (856 bytes)
这样就OK了,我们可以用flashplayer测试结果:flex-sdk/in/player/lnx/flashplayer MySprite.swf回车
屏幕显示This is only a test,控制台显示hello World
而我们用asc则是(为了方便,我已经不想用../../tamarin-tracing这类path了,直接把常用abc文件copy到当前目录下)
java -jar asc.jar -import -swf MyTest,400,300 -import builtin_full.abc -import playerglobal.abc -in Test.as MyTest.as
输出
MySprite.swf, 830 bytes written
测试结果,除了背景色不同外,结果相同。
关键是mxmlc读取flex-config.xml,而且更重要的是,它自动会寻找Test.as,而asc是不会的。
6) 其他操作
关于其他操作,还请大家去看flex-sdk的源码和tamarin的avmshell项目代码,过后,我们也有详细的讨论,请大家关注
好了,今天的asc命令行编译工具就介绍到这里,接下来的内容,就让我们更深入tamarin一些吧。
前的三章已经为我们介绍了tamarin和开源的flex-sdk的一些基本操作,本章我们将要更加深入地了解avmshell和asc编译器。
1、深入tamarin项目
到目前为止,我们仔细看看我们的tamarin-tracing的目录结构,可能我们从来都没深入了解也注意到他里面的文件:
|– build // make后的编译版本
| |– Makefile
| |– core
| |– extensions
| |– nanojit
| |– pcre
| |– platform
| |– shell // 前三章,我们一直在这里用./avmshell和java -jar asc.jar进行示例
| |– space
|– tamarin-tracing // tamarin源码工程
|– axscript
|– build
|– configure
|– configure.py
|– core // 核心文件夹
|– esc // esc的编译器,相对于asc
|– extensions
|– localization
|– manifest.mk
|– nanojit
|– pcre
|– platform // 平台文件夹,win的项目文件在这里可以找到
|– shell // shell文件夹
|– space // mmgc项目文件夹
|– test
|– utils // 工具文件夹
`– zlib
结构大体如上,其中
tamarin-tracing/core是程序的核心项目部分,主要构建builtin.abc部分,也有资料写作global.abc
tamarin-tracing/shell是shell项目部分,主要构建shell.abc,也有资料写作toplevel.abc
关于global.abc和toplevel.abc的构建请参考redtamarin项目(http://code.google.com/p/redtamarin/)
这是另一个不同于官方的tamarin实现。
2、定制我们自己的tamarin
大家是不是有一些头晕,tamarin就tamarin吧,怎么还有个redtamerin? 这是另一套完全不同的项目吗?
其实,关于tamarin项目的结果,只是最终生成了avmshell这个可以运行虚拟机的shell。而shell究竟内容如何,主要靠core和shell这两大块。
而core是核心,里面基本是雷打不动的代码,比较偏重虚拟机内的方法,如AS3的内建方法,而shell则是偏重于本地代码的实现,即不同平台上的shell的实现,类似AIR。
两部分合在一起,构成了tamarin的avmshell。
那么,我们完全可以自己制作一个属于自己的shell,或自己的Integrated Runtime环境,就像之前说的Yahoo!Widgets。
Goole Code上实现的叫redtamarin,我们大可以自己作一个bluetamarin。或者My Integrated Runtime 或叫 MIR,以区别AIR。这是完全可行的。
redtamarin在global.as中选用了
include “core\/builtin.as”
include “core\/Math.as”
include “core\/ErrorConstants.as”
include “core\/Error.as”
include “core\/RegExp.as”
include “core\/Date.as”
include “core\/XML.as”
编译成global.abc来代替官方的 tamarin-tracing/core下的builtin_full.abc
在toplevel.as中,用
include “shell\\toplevel.as”
include “shell\\Domain.as”
include “shell\\StringBuilder.as”
include “shell\\ByteArray.as”
include “shell\\IntArray.as”
include “shell\\UIntArray.as”
include “shell\\DoubleArray.as”
include “shell\\FloatArray.as”
include “shell\\ShortArray.as”
include “shell\\UShortArray.as”
include “shell\\Dictionary.as”
include “shell\\Endian.as”
include “shell\\Java.as”
//RedTamarin
include “shell\\standard.Errors.as”
include “shell\\standard.Operations.as”
include “shell\\standard.as”
编译成toplevel.abc代替了官方 tamarin-tracing/shell下的shell_full.abc,如此而已。
3、创建自己的build版本,定制avmshell
如果真要按照redtamarin这样定制下来,你会发现,需要编译的地方很多,出错的地方更多。可能大家会觉得没有足够的知识和耐心是不能作到的。
实际上呢,redtamarin已经是个趋向与成品的东西了,就是说它中间经历过的编译过程和产生的文件,事实上都被删掉了,只留下了最终的结果,所以你按照redtamarin编译是没法成功的。
其实我们没有必要从头来定制,core和shell的大部分代码我们都是很需要的,我们只需要在上面加上我们需要的就可以了,而tamarin-tracing也提供了编译和定制的工具。
我们看 tamarin-tracing/core下面有一个builtin.py,这个文件可以编译一些头文件和abc文件或声明内容。
1)它调用了forth语言编译器fc(在 tamarin-tracing/utils下),生成fpu和soft fp部分,关于forth语言,这里就不再赘述。
2)它使用asc.jar下的macromedia.asc.embedding.ScriptCompiler而不是macromedia.asc.embedding.Main(java -jar asc.jar默认的是执行Main)。
这个ScriptCompiler既可以编译abc又可以生成cpp和h文件,从而在make时进一步编译生成avmshell。
同理在tamarin-tracing/shell下也有shell.py,这个文件编译shell相关的abc和头文件或声明内容。
(注意,在运行这两个python脚本后生成的builtin_full.h和shell_full.h有BUG,稍后我们会详细说明这个问题)
我们通过修改源文件,并使用builtin.py和shell.py来快速地进行cpp和h文件的生成,然后新建空文件夹configure,再通过make就可以构建我们自己的avmshell了。
4、初步实践
首先,我们在build/shell的目录下(还是我们经常测试的那个目录)制作testShell.as:
import avmplus.*;
System.alert(“test alert”);
编译java-jar asc.jar testShell.as
testShell.abc, 100 bytes written
运行./avmshell testShell.abc
结果输出
TypeError: Error #1006: alert is not a function.
at global$init()
avmshell运行提示并没有定义这个alert方法。
事实上,shell里也没有这个alert方法。我们可以定义一个。
在tamarin-tracing/shell/shell.as中
package avmplus
{
public class System
{
public native static function exit(status:int):void
public native static function getAvmplusVersion():String
public native static function Debugger():void
public native static function isDebugger():Boolean
public native static function getTimer():uint
public native static function readLine():String
// 加入这个方法
public static function alert(text:String):void
{
// 方法体,自己定义
write(text);
}
然后编译这个shell的头文件
tamarin-tracing/shell/shell.py
输出
Building Full AS3
shell: 80361
Files: 18 Time: 2614ms
BEFORE 80361
AFTER 46010
SAVED 34351 43%
Building Min AS3
shell: 6504
Files: 6 Time: 836ms
BEFORE 6504
AFTER 3281
SAVED 3223 50%
然后新建一个build2目录去configure & make,
mkdir build2
cd build2
../tamarin-tracing/configure –enable-shell –enable-debugger
make
你会发现,编译错误,这是个BUG。
解决方法是,修改shell_full.h,把所有以AVMPLUS_NATIVE_METHOD_DECL(Atom开头的宏命令都替换成AVMPLUS_NATIVE_METHOD_DECL(BoxReturnType
比如AVMPLUS_NATIVE_METHOD_DECL(Atom, avmplus_Domain_private__load)替换为AVMPLUS_NATIVE_METHOD_DECL (BoxReturnType, avmplus_Domain_private__load)
好了,再make,就可以编译了
然后,我们就在build2/shell下,得到了一个新的avmshell
我们有两个不同avmshell了,两个不同的shell环境,想不想搞几个不同的avmshell发行版呢,
或者自己作一个特色的AIR出来?
这时候,我们可以针对两个build环境分别测试:
在build/shell下用./avmshell testShell.abc
结果输出
TypeError: Error #1006: alert is not a function.
at global$init()
在build2/shell下用./avmshell testShell.abc
结果输出
test alert
有些环境下的tamarin-tracing可能输出不出来结果,不过没关系
用./avmshell -Dastrace 1 testShell.abc
结果输出
87 AVMINF: MTHD global$init ()
88 AVMINF: MTHD Object$$cinit ()
88 AVMINF: MTHD Class$$cinit ()
90 AVMINF: MTHD Function$$cinit ()
90 AVMINF: MTHD Object$/private::_hideproto ()
160 AVMINF: MTHD Object$/private::init ()
161 AVMINF: MTHD Object$/private::_hideproto ()
161 AVMINF: MTHD private::MethodClosure$$cinit ()
164 AVMINF: MTHD Namespace$$cinit ()
164 AVMINF: MTHD Object$/private::_hideproto ()
165 AVMINF: MTHD QName$$cinit ()
165 AVMINF: MTHD Object$/private::_hideproto ()
166 AVMINF: MTHD Boolean$$cinit ()
166 AVMINF: MTHD Object$/private::_hideproto ()
171 AVMINF: MTHD Number$$cinit ()
181 AVMINF: MTHD Object$/private::_hideproto ()
183 AVMINF: MTHD int$$cinit ()
184 AVMINF: MTHD Object$/private::_hideproto ()
186 AVMINF: MTHD uint$$cinit ()
206 AVMINF: MTHD Object$/private::_hideproto ()
218 AVMINF: MTHD String$$cinit ()
219 AVMINF: MTHD Object$/private::_hideproto ()
232 AVMINF: MTHD Array$$cinit ()
233 AVMINF: MTHD Object$/private::_hideproto ()
236 AVMINF: MTHD private::FieldName$$cinit ()
236 AVMINF: MTHD private::StackFrame$$cinit ()
237 AVMINF: MTHD private::ArraySort$$cinit ()
286 AVMINF: MTHD global$init ()
286 AVMINF: MTHD global$init ()
286 AVMINF: MTHD avmplus::Domain$$cinit ()
286 AVMINF: MTHD global/private::getfiles ()
286 AVMINF: MTHD global$init ()
287 AVMINF: MTHD avmplus::System$$cinit ()
287 AVMINF: MTHD avmplus::System$/private::getArgv ()
287 AVMINF: MTHD avmplus::File$$cinit ()
287 AVMINF: MTHD flash.system::Capabilities$$cinit ()
287 AVMINF: MTHD Array/get length ()
287 AVMINF: MTHD Array/http://adobe.com/AS3/2006/builtin::shift ()
287 AVMINF: MTHD Array$/private::_shift ()
287 AVMINF: MTHD Array/get length ()
287 AVMINF: MTHD Array/set length ()
287 AVMINF: MTHD Array/private::_set_length ()
287 AVMINF: MTHD Array/http://adobe.com/AS3/2006/builtin::push ()
287 AVMINF: MTHD Array$/private::_push ()
287 AVMINF: MTHD Array/get length ()
287 AVMINF: MTHD Array/get length ()
287 AVMINF: MTHD Array/set length ()
287 AVMINF: MTHD Array/private::_set_length ()
288 AVMINF: MTHD Array/get length ()
288 AVMINF: MTHD Array/get length ()
288 AVMINF: MTHD Array/get length ()
288 AVMINF: MTHD avmplus::Domain/load ()
288 AVMINF: MTHD avmplus::Domain/private::_readAndLoad ()
288 AVMINF: MTHD global$init ()
288 AVMINF: MTHD flash.utils::ByteArray$$cinit ()
288 AVMINF: MTHD flash.utils::ByteArray$/readFile ()
288 AVMINF: MTHD global/_checkNull ()
288 AVMINF: MTHD flash.utils::ByteArray$iinit ()
288 AVMINF: MTHD Object$iinit ()
289 AVMINF: MTHD global$init ()
289 AVMINF: MTHD avmplus::System$/alert ()
289 AVMINF: MTHD avmplus::System$/write ()
289 AVMINF: MTHD global/_checkNull ()
testShell289 AVMINF: MTHD Array/get length ()
看这一行289 AVMINF: MTHD avmplus::System$/alert (),说明alert已经执行,并调用了289 AVMINF: MTHD avmplus::System$/write ()
好了,今天的内容到此结束。
今天主要稍微简单地深入了一下tamarin的各部分构成,以及如何定制自己的avmshell。
接下来,我们要深入了解native这个本地代码关键字以及如何让AVM2虚拟机来执行外部的本地代码。
上回书说道,如何在tamarin项目的shell中加入定制AS3代码,下面我们接着上回的说。这次,我们将要设计并使用本地代码。
1、修改shell子项目
这次让我们关注tamarin-tracing/shell这个目录。
上次简单地说道tamarin-tracing/shell/shell.py可以构建shell编译的相关C++和abc文件,这次我们来深入看看shell这个项目。
打开tamarin-tracing/shell/shell.py,我们看fullas3这个方法
def fullas3():
print
print “Building Full AS3″
# compile builtins
os.system(asc+fullconfig+” -d -abcfuture -import ../core/builtin_full.abc -builtin -out shell main.as shell.as fib.as ../extensions/Dictionary.as Endian.as “+zlibfiles+” Domain.as ByteArray.as”)
# run optimizer
os.system(abcopt+” ../core/builtin_full.abc shell.abc >shell_full.out”)
os.system(“tail -3 shell_full.out”)
mv(“shell.abc”, “shell_full_orig.abc”)
mv(“shell.abc2”, “shell_full.abc”)
mv(“shell.cpp2”, “shell_full.cpp”)
mv(“shell.h2”, “shell_full.h”)
rm(“shell.cpp”)
rm(“shell.h”)
一般我们编译都是用full这个配置来编译,比如builtin_full和shell_full,因为full包含了更全的内容。关于full的配置,从变量fullconfig=”-config CONFIG::Full=true”来区分编译内容,具体内容全部放在as文件中, 写法如下:
CONFIG::Full
private static function _join(o, sep):String
…{
var s:String = (sep === undefined) ? “,” : String(sep);
var out:String = “”;
for (var i:uint = 0, n:uint=o.length; i < n; i++) ...{ var x = o[i]; if (x != null) out += x; if (i+1 < n) out += s; } return out; } 这样编译通过-config CONFIG::Full=true或false来决定这个_join是否要参与AS3编译成c++或abc代码,这些代码的头定义最终织入到 shell_full.h和builtin_full.h或shell_min.h和builtin_min.h中。 从shell文件夹和core文件夹中的文件可以看出,文件命名规则是这样的: 以Number类为例,则有Number.as、NumberClass.cpp、NumberClass.h。 然后,让我们看看make编译shell的关键操作流程: 1、shell.py脚本会利用asc.jar中的macromedia.asc.embedding.ScriptCompiler先来编译这些as,然后生成的头全部放入shell_full.h或shell_min.h中(编译core项目生成builtin_full.h道理与之相同) 2、然后根据生成的h和cpp文件在shell文件夹的manifest.mk中编译(.net是在avmplus_9.vcproj项目文件中) 这跟之前提到的一样,先shell.py再make项目的做法相吻合。 2、制作native method 为了便于诠释,我们跟《Implementing Native Methods in Tamarin 》文章一样,采用以斐波那契数列为例。关于这篇文章,感兴趣的朋友请看http://www.bluishcoder.co.nz/2008/02/implementing-native-methods-in-tamarin.html 我们首先建立一个fib.as: package testing ...{ public function fib(n) ...{ if(n <= 1) return 1; else return fib(n-1) + fib(n-2); } public native function fib2(n:int):int; } 我们一口气建立了两个fib函数,一个是as3实现,另一个使用了native method,转入本地代码实现。这样便于我们去对比参考。 然后我们去修改shell.py,在fullas3方法中加入我们要实现的这个fib.as让其参与编译: os.system(asc+fullconfig+" -d -abcfuture -import ../core/builtin_full.abc -builtin -out shell main.as shell.as fib.as ../extensions/Dictionary.as Endian.as "+zlibfiles+" Domain.as ByteArray.as") 然后系统生成了shell_full.h,我们可以看到已经有fib2方法的头产生: struct null_fib2_args ...{ public: ScriptObjectp /**//*global0*/ self; private: int32_t self_pad; public: int32_t n; private: int32_t n_pad; public: StatusOut* status_out; }; AVMPLUS_NATIVE_METHOD_DECL(int32_t, null_fib2) 分别为一个结构体和一个宏。 然后我们实现这个fib2,fibimpl.cpp: #include "avmshell.h" #include
namespace avmshell
…{
int32_t native_fib(int32_t n) …{
if(n <= 1) return 1; else return native_fib(n-1)+native_fib(n-2); } AVMPLUS_NATIVE_METHOD(int32_t, null_fib2) ...{ return native_fib(args->n);
}
}
我们的实现中的宏定义为:
#define AVMPLUS_NATIVE_METHOD(ret, name)
ret FASTCALL name (const name##_args* args) /**//* no semi */
在fibimpl.cpp中也就相当于:
int32_t FASTCALL null_fib2 (const null_fib2_args* args) /**//**//**//* no semi */
{
return native_fib(args->n);
}
这样shell调用fib2就相当于调用null_fib2,null_fib2又相当于调用native_fib(args->n),args就是结构体指针null_fib2_args。
这种调用在tamarin里很常见,他们也具有一定的命名规则,如上一章在shell.as内有一个exit方法:
package avmplus
…{
public class System
…{
public native static function exit(status:int):void
………………
对应的SystemClass.cpp中就有:
AVMPLUS_NATIVE_METHOD(void, avmplus_System_exit)
…{
::exit(args->status);
}
命名规则是:包_类_方法。因为是public的所以没有表示范围
在这个System类内还有_exec方法,那么它的宏是:
AVMPLUS_NATIVE_METHOD(int32_t, avmplus_System_private__exec)
命名规则是:包_类_作用域_方法名。
注意,我们并没有准寻[foo].as、[foo]Class.cpp、[foo]Class.h这一命名规则,这说明这规则是编码规范而已。
然后,我们修改shell文件夹下的manifest.mk,在编译路径中加入这个fibimpl.cpp:
shell_CXXSRCS := $(shell_CXXSRCS)
$(curdir)/avmshell.cpp
$(curdir)/ByteArrayGlue.cpp
$(curdir)/ConsoleOutputStream.cpp
$(curdir)/DataIO.cpp
$(curdir)/DebugCLI.cpp
$(curdir)/DomainClass.cpp
$(curdir)/FileClass.cpp
$(curdir)/FileInputStream.cpp
$(curdir)/SystemClass.cpp
$(curdir)/fibimpl.cpp
$(curdir)/../extensions/DictionaryGlue.cpp
$(NULL)
然后我们make编译项目,注意上一篇提到的Atom BUG问题,这里不再赘述。
假设新的build的shell叫build2,区别与之前的build。在build2/shell目录下,制作fibtest.as和fibtest2.as:
$ cat fibtest.as
import testing.*;
print(“fib 30 = ” + fib(30));
$ cat fibtest2.as
import testing.*;
print(“fib 30 = ” + fib2(30));
分别测试基于as3的fib和基于native method的fib2:
$ ./shell/avmshell -lifespan fibtest.abc
fib 30 = 1346269
Run time was 2390 msec = 2.39 sec
$ ./shell/avmshell -lifespan fibtest2.abc
fib 30 = 1346269
Run time was 443 msec = 0.44 sec
$ ./shell/avmshell -lifespan -interp fibtest.abc
fib 30 = 1346269
Run time was 129007 msec = 129.01 sec
$ ./shell/avmshell -lifespan -interp fibtest2.abc
fib 30 = 1346269
Run time was 329 msec = 0.33 sec
发现native method明显快于as3 mathod(这是当然的,呵呵)
好了,我们诠释了《Implementing Native Methods in Tamarin 》一文,实践了fib2这个方法的从无到有的过程。
2、关于Atom的BUG
这两章一直有提到这个AVMPLUS_NATIVE_METHOD_DECL(Atom, ……)出错的问题。我们就在这节由这个问题引出更深层次的东西。
首先我们看AVMPLUS_NATIVE_METHOD_DECL的定义:
#define AVMPLUS_NATIVE_METHOD_DECL(ret, name)
enum …{ k_NativeRetType_##name = NativeRetType_##ret } ;
extern ret FASTCALL name (const name##_args* args); /**//* semi required here */
可以看到这个宏做两件事情,一件是用来做enum返回类型的定义,另一个是做extern方法定义,如:
AVMPLUS_NATIVE_METHOD_DECL(bool, avmplus_File_private__write),解析成:
enum { k_NativeRetType_avmplus_File_private__write = NativeRetType_bool }
extern bool FASTCALL avmplus_File_private__write(const avmplus_File_private__write_args* args)
而native method的返回类型如下:
typedef uint64_t BoxReturnType;
// a little syntactic sugar for mapping C++ ret types into a useful enum
enum NativeRetType
…{
NativeRetType_ScriptObjectp,
NativeRetType_bool,
NativeRetType_int32_t,
NativeRetType_uint32_t,
NativeRetType_Namespacep,
NativeRetType_double,
NativeRetType_BoxReturnType,
NativeRetType_Stringp,
NativeRetType_void
};
而且,对应的shell_full.h里就有对应的结构体:
struct avmplus_File_private__write_args
…{
public: ScriptObjectp /**//*File$*/ classself; private: int32_t classself_pad;
public: Stringp filename; private: int32_t filename_pad;
public: Stringp data; private: int32_t data_pad;
public: StatusOut* status_out;
};
这个结构体是是对应的as文件用shell.py生成的,就像刚才我们生成null_fib2_args一样。
这样,这一个PY脚本工具生成的宏就把整个的native method定义流程和结构串在了一起。
Atom的产生原因是定义as文件时返回了不明确的类型,比如Object类型或*类型。Atom的定义如下:
// NOTE, for the new native-method glue to work properly,
// Atom must be a unique type, NOT an alias onto int, intptr_t, etc.
// Please use the atomPtr, atomKind, etc functions in AtomConstants.h to
// operate on them.
typedef struct Atom_* Atom;
说明单是Atom是个抽象的不能使用的类型,而且,建议使用的是atomPtr和atomKind,而atomPtr又是#define atomPtr(a) ((void*)(uintptr_t(a) & ~7)),7为atomKind的enum数量,uintptr_t返回的是uint64_t。
为什么要用NativeRetType_BoxReturnType来替换呢,因为在解释器Interpreter.cpp里,处理NativeRetType_BoxReturnType为:
case NativeRetType_BoxReturnType:
// remember: plain object and any-typed values are handled as Box, not SO*
result.q = (*NativeMethodProc_BoxReturnType(f))(argv);
break;
可以看出,BoxReturnType为plain object(即Object基类型)和任何被类型化的值。所以换为BoxReturnType就不会出问题了。
今天详细地介绍了如何制作本地方法,并深入了解本地方法的来龙去脉和核心宏方法。
下面,我们将深入到源代码内部去看看tamarin的运作机制,并且将介绍其他子项目的功能和内容。精彩不容错过呦
============================================================================
最近特别忙,就没空出时间继续研究。不过以后会补上进度的。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏