• tamarin系列之5] 植入本地方法实现


     tamarin系列之5] 植入本地方法实现
    分类: Tamarin 1086人阅读 评论(0) 收藏 举报

    上回书说道,如何在tamarin项目的shell中加入定制AS3代码,下面我们接着上回的说。这次,我们将要设计并使用本地代码。

    1、修改shell子项目

    这次让我们关注tamarin-tracing/shell这个目录。

    上次简单地说道tamarin-tracing/shell/shell.py可以构建shell编译的相关C++和abc文件,这次我们来深入看看shell这个项目。

    打开tamarin-tracing/shell/shell.py,我们看fullas3这个方法

    1. def fullas3():
    2. print
    3. print "Building Full AS3"
    4. # compile builtins
    5. 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")
    6. # run optimizer
    7. os.system(abcopt+" ../core/builtin_full.abc shell.abc >shell_full.out")
    8. os.system("tail -3 shell_full.out")
    9. mv("shell.abc", "shell_full_orig.abc")
    10. mv("shell.abc2", "shell_full.abc")
    11. mv("shell.cpp2", "shell_full.cpp")
    12. mv("shell.h2", "shell_full.h")
    13. rm("shell.cpp")
    14. rm("shell.h")

    一般我们编译都是用full这个配置来编译,比如builtin_full和shell_full,因为full包含了更全的内容。关于full的配置,从变量fullconfig="-config CONFIG::Full=true"来区分编译内容,具体内容全部放在as文件中, 写法如下:

    1. CONFIG::Full
    2. private static function _join(o, sep):String {
    3. var s:String = (sep === undefined) ? "," : String(sep);
    4. var out:String = "";
    5. for (var i:uint = 0, n:uint=o.length; i < n; i++){
    6. var x = o[i];
    7. if (x != null)
    8. out += x;
    9. if (i+1 < n)
    10. out += s;
    11. }
    12. return out;
    13. }

    这样编译通过-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:

    1. package testing {
    2. public function fib(n) {
    3. if(n <= 1)
    4. return 1;
    5. else
    6. return fib(n-1) + fib(n-2);
    7. }
    8. public native function fib2(n:int):int;
    9. }

    我们一口气建立了两个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方法的头产生:

    1. struct null_fib2_args {
    2. public: ScriptObjectp /**//*global0*/ self;
    3. private: int32_t self_pad;
    4. public: int32_t n;
    5. private: int32_t n_pad;
    6. public: StatusOut* status_out;
    7. };
    8. AVMPLUS_NATIVE_METHOD_DECL(int32_t, null_fib2)

    分别为一个结构体和一个宏。

    然后我们实现这个fib2,fibimpl.cpp:

    1. #include "avmshell.h"
    2. #include <stdlib.h>
    3. namespace avmshell {
    4. int32_t native_fib(int32_t n) {
    5. if(n <= 1)
    6. return 1;
    7. else
    8. return native_fib(n-1)+native_fib(n-2);
    9. }
    10. AVMPLUS_NATIVE_METHOD(int32_t, null_fib2) {
    11. return native_fib(args->n);
    12. }
    13. }

    我们的实现中的宏定义为:

    1. #define AVMPLUS_NATIVE_METHOD(ret, name)
    2. ret FASTCALL name (const name##_args* args) /**//* no semi */

    在fibimpl.cpp中也就相当于:

    1. int32_t FASTCALL null_fib2 (const null_fib2_args* args) /**//**//**//* no semi */
    2. {
    3. return native_fib(args->n);
    4. }

    这样shell调用fib2就相当于调用null_fib2,null_fib2又相当于调用native_fib(args->n),args就是结构体指针null_fib2_args。

    这种调用在tamarin里很常见,他们也具有一定的命名规则,如上一章在shell.as内有一个exit方法:

    1. package avmplus {
    2. public class System {
    3. public native static function exit(status:int):void
    4. ………………

    对应的SystemClass.cpp中就有:

    1. AVMPLUS_NATIVE_METHOD(void, avmplus_System_exit) {
    2. ::exit(args->status);
    3. }

    命名规则是:包_类_方法。因为是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:

    1. shell_CXXSRCS := $(shell_CXXSRCS)
    2. $(curdir)/avmshell.cpp
    3. $(curdir)/ByteArrayGlue.cpp
    4. $(curdir)/ConsoleOutputStream.cpp
    5. $(curdir)/DataIO.cpp
    6. $(curdir)/DebugCLI.cpp
    7. $(curdir)/DomainClass.cpp
    8. $(curdir)/FileClass.cpp
    9. $(curdir)/FileInputStream.cpp
    10. $(curdir)/SystemClass.cpp
    11. $(curdir)/fibimpl.cpp
    12. $(curdir)/../extensions/DictionaryGlue.cpp
    13. $(NULL)

    然后我们make编译项目,注意上一篇提到的Atom BUG问题,这里不再赘述。

    假设新的build的shell叫build2,区别与之前的build。在build2/shell目录下,制作fibtest.as和fibtest2.as:

    1. $ cat fibtest.as
    2. import testing.*;
    3. print("fib 30 = " + fib(30));
    4. $ cat fibtest2.as
    5. import testing.*;
    6. print("fib 30 = " + fib2(30));

    分别测试基于as3的fib和基于native method的fib2:

    1. $ ./shell/avmshell -lifespan fibtest.abc
    2. fib 30 = 1346269
    3. Run time was 2390 msec = 2.39 sec
    4. $ ./shell/avmshell -lifespan fibtest2.abc
    5. fib 30 = 1346269
    6. Run time was 443 msec = 0.44 sec
    7. $ ./shell/avmshell -lifespan -interp fibtest.abc
    8. fib 30 = 1346269
    9. Run time was 129007 msec = 129.01 sec
    10. $ ./shell/avmshell -lifespan -interp fibtest2.abc
    11. fib 30 = 1346269
    12. 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的定义:

    1. #define AVMPLUS_NATIVE_METHOD_DECL(ret, name)
    2. enum { k_NativeRetType_##name = NativeRetType_##ret } ;
    3. 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的返回类型如下:

    1. typedef uint64_t BoxReturnType;
    2. // a little syntactic sugar for mapping C++ ret types into a useful enum
    3. enum NativeRetType {
    4. NativeRetType_ScriptObjectp,
    5. NativeRetType_bool,
    6. NativeRetType_int32_t,
    7. NativeRetType_uint32_t,
    8. NativeRetType_Namespacep,
    9. NativeRetType_double,
    10. NativeRetType_BoxReturnType,
    11. NativeRetType_Stringp,
    12. NativeRetType_void
    13. };

    而且,对应的shell_full.h里就有对应的结构体:

    1. struct avmplus_File_private__write_args {
    2. public: ScriptObjectp /**//*File$*/ classself;
    3. private: int32_t classself_pad;
    4. public: Stringp filename;
    5. private: int32_t filename_pad;
    6. public: Stringp data;
    7. private: int32_t data_pad;
    8. public: StatusOut* status_out;
    9. };

    这个结构体是是对应的as文件用shell.py生成的,就像刚才我们生成null_fib2_args一样。

    这样,这一个PY脚本工具生成的宏就把整个的native method定义流程和结构串在了一起。

    Atom的产生原因是定义as文件时返回了不明确的类型,比如Object类型或*类型。Atom的定义如下:

    1. // NOTE, for the new native-method glue to work properly,
    2. // Atom must be a unique type, NOT an alias onto int, intptr_t, etc.
    3. // Please use the atomPtr, atomKind, etc functions in AtomConstants.h to
    4. // operate on them.
    5. 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为:

    1. case NativeRetType_BoxReturnType:
    2. // remember: plain object and any-typed values are handled as Box, not SO*
    3. result.q = (*NativeMethodProc_BoxReturnType(f))(argv);
    4. break;

    可以看出,BoxReturnType为plain object(即Object基类型)和任何被类型化的值。所以换为BoxReturnType就不会出问题了。

    今天详细地介绍了如何制作本地方法,并深入了解本地方法的来龙去脉和核心宏方法。

    下面,我们将深入到源代码内部去看看tamarin的运作机制,并且将介绍其他子项目的功能和内容。精彩不容错过呦

  • 相关阅读:
    【JLOI2011】飞行路线
    P3369 【模板】普通平衡树
    P1144 最短路计数
    P1462 通往奥格瑞玛的道路
    【NOIP2017】宝藏
    P1120 小木棍
    P3919 【模板】可持久化数组(可持久化线段树/平衡树)
    P3834 【模板】可持久化线段树 1(主席树)
    矩阵清零--进军硅谷
    二维数组搜素--进军硅谷
  • 原文地址:https://www.cnblogs.com/xiayong123/p/3717185.html
Copyright © 2020-2023  润新知