言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

ActionScript アセンブラ abcasm で 3 + 4

今日は flash で遊ぶのは一休みして、Tamarin を使って純粋に avm2 のアセンブラを試してみました。今時の言語のバーチャルマシンの仕組みが分かって良かったです。

まず、以下の作業が楽になるようにコマンドにエイリアスを作ります。

alias asc='java -jar ~/src/flex_sdk_3/lib/asc.jar'
alias abcdump='avmshell ~/src/tamarin-central/utils/abcdump.abc --'
alias abcasm='~/src/tamarin-central/utils/abcasm/abcasm.sh'
alias avmshell='~/src/tamarin-central/objdir-release/shell/avmshell'

Hello World

まず最初に簡単な Hello World プログラムを書きます。

// hello.abs

function hello()
{
	getlocal 0                  // stack: this
	pushscope                   // stack:
	findpropstrict print        // stack: (shell_toplevel.as)

	pushstring "Hello, World!!" // stack: (shell_toplevel.as), "Hello, World!!"
	callproperty print(1)       // stack:

	returnvoid
}

実行結果は次のようになります。

$ abcasm hello.abs && avmshell hello.abc
hello.abs
Hello, World!!

さて、このプログラムを見て行きましょう。VM は abc ファイルを読み込んでリンクした後、ファイルの一番最後の関数を実行します。読み込み時に実行しなくて良い場合は、最後にダミーの関数が必要です。

関数が呼ばれると、三つのデータ領域が用意されます。特に scope stack というのが高級感を感じますね。

  • operand stack : 関数が自由に使える作業領域です。上のコメントに命令結果のスタックの状態を書きました。
  • scope stack : 名前を検索するオブジェクトです。レキシカルスコープに使います。
  • local registers : 関数が自由に使える作業虜域です。引き数が入っています。local 0 は常に this です。

プログラムの最初の getlocal 0 と pushscope はお約束のコードで、this を scope stack に積みます。このようなオブジェクトメソッドじゃなくて単純な関数の場合は global が渡ります。それから print 関数が所属するオブジェクトを findpropstrict で探します。

ActionScript の関数は全て Object.methodName() の形をしています。この例のように、レシーバーが無い単なる関数の場合も、内部的に print を知っているオブジェクトにメッセージを送るという形式になります。この、「print を知っているオブジェクトを探す」のが findpropstrict です。print 関数自体を探す訳じゃないです。具体的には、Tamarin の shell_toplevel.as 内で print を定義しているスコープオブジェクト(正式用語不明)がスタックに積まれます。

後は簡単で、文字列 "Hello, World!!" を operand stack に積んで、引き数サイズ 1 を指定して print 関数を読んでいます。一見、findpropstrict と callproperty は対応する必要があるように見えますが、実際にはソース上で print と同じスコープにある trace などを callproperty で呼んでもエラーにはなりません。この辺りの理解は難所だと思います。最後に returnvoid で終わりです。

イントロスペクション

上の例で、getlocal や findpropstrict の動作を調べるのがとても大変でした。というのも、avmshell の表示がヘボくて、良くわからないオブジェクトはみんな [object global] と表示されてしまうのです。これでは嫌なのでオブジェクトの中身を調べる次のプログラムを用意します。

// describe.as
package {
    import avmplus.*;
    public function describe(v) {
	print("** Description of " + v.toString());
	print(describeType(v, FLASH10_FLAGS).toString());
    }
}

実験台になるプログラムはこんな感じ

// misc.abs
function hello()
{
	getlocal 0
	pushscope

	findpropstrict describe
	pushstring "Hello, World!!" // Description of a string
	callproperty describe(1)

	findpropstrict describe
	getlocal 0
	callproperty describe(1) // Description of the first argument

	findpropstrict describe
	findpropstrict print
	callproperty describe(1) // Description of the scope for print

	returnvoid
}

実行結果です。

$ asc describe.as 

describe.abc, 206 bytes written
$ abcasm misc.abs && avmshell describe.abc misc.abc
misc.abs
** Description of Hello, World!!
<type name="String" isDynamic="false" isFinal="true" isStatic="false" base="Object">
  <extendsClass type="Object"/>
  <accessor access="readonly" type="int" declaredBy="String" name="length"/>
  <constructor>
    <parameter index="1" type="*" optional="true"/>
  </constructor>
</type>
** Description of [object global]
<type name="global" isDynamic="true" isFinal="true" isStatic="false" base="Object">
  <extendsClass type="Object"/>
</type>
** Description of [object global]
<type name="global" isDynamic="true" isFinal="true" isStatic="false" base="Object">
  <extendsClass type="Object"/>
  <constant type="flash.system::Capabilities" name="Capabilities" uri="flash.system"/>
  <constant type="avmplus::System" name="System" uri="avmplus"/>
  <constant type="avmplus::File" name="File" uri="avmplus"/>
  <method returnType="uint" declaredBy="global" name="getTimer"/>
  <method returnType="*" declaredBy="global" name="debugger" uri="avmplus"/>
  <method returnType="*" declaredBy="global" name="trace"/>
  <method returnType="Class" declaredBy="global" name="getClassByName">
    <parameter index="1" type="String" optional="false"/>
  </method>
  <method returnType="String" declaredBy="global" name="readLine"/>
  <method returnType="*" declaredBy="global" name="print"/>
</type>

3 + 4

ここまでして得た知識を最大限使って 3 + 4 を計算するとこうなります。

function hello()
{
	getlocal 0            // stack: this
	pushscope             // stack:
	findpropstrict print  // stack: (shell_toplevel.as)

	pushuint 3            // stack: (shell_toplevel.as), 3
	pushuint 4            // stack: (shell_toplevel.as), 3, 4
	add                   // stack: (shell_toplevel.as), 7
	callproperty print(1) // stack:

	returnvoid
}
$ abcasm 3plus4.abs && avmshell 3plus4.abc
3plus4.abs
7

豆知識: エラーが数字で分かりにくいので、下にリンクを挙げたソースを見るとよい。

使った AVM2 アセンブラ命令一覧

  • getlocal0 ( -- value) : register0 の内容を operand stack にプッシュ
  • pushscope (value -- ) : operand stack をポップして scope stack にプッシュ
  • findpropstrict multiname (-- value) : scope stack から multiname を元にスコープオブジェクトを検索して operand stack にプッシュ
  • pushuint uint (-- uint) : 整数を operand stack にプッシュ
  • add (value value -- value) : 足し算
  • callproperty multiname arg_count (obj args.. -- value) : obj から multiname を検索して実行
  • returnvoid (--) : undefined を返す