OS X で Squeak VM の外部プラグインのビルド
Little Smalltalk で VM のソースを眺めていたら、むらむら(違)と Squeak VM をビルドしたくなったのですが、初心者にはとうてい無理っぽかったので、外部プラグイン(OS X 向けには ○○.bundle とかいうやつ。仮想マシンレベルで Squeak システムの機能を拡張する)のビルドで我慢しておくことしました。以下はそのメモ。
いろいろと参考にさせていただきました。ありがとうございます。
- Squeaker's Scrapbook - MacOSX版VMのビルドについて
- 自由自在 Sqeuak プログラミング - 14 章、VM プラグインの作成
- ど素人のためのど素人によるSqueak入門 - VM Plugin
- 2ちゃんねる - [荒井権]すくいーく・スクイーク[アラン・ケイ] (387-391)
VMMaker のインストール
Squeak システムを起動し、まず デスクトップメニュー → save as.. → SqueakPlugin-dev-vmm.image などと名前を変えて保存します。
次に、デスクトップメニュー → open... → SqueakMap Package Loader より VMMaker を選択して install します。
Slang でプラグインを書く
Slang というのは、C 言語に変換可能な Smalltalk のサブセット言語です。プラグインのみならず、Squeak VM のコアな部分も、これで書かれています(VMMaker と一緒にインストールされる Interpreter、ObjectMemory クラス)。
- ど素人のためのど素人によるSqueak入門 - VM Plugin/Slang
まずブラウザを開き(デスクトップをクリックして cmd + b )、VM プラグインを定義するためのクラスを作ります。名前は「自由自在…」にならって MyFirstPlugin としました。
SmartSyntaxInterpreterPlugin subclass: #MyFirstPlugin instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Plugin-Lesson'
数字を返すだけでは少々物足りないので、やはり「自由自在…」から階乗の例を拝借。
MyFirstPlugin >> primitiveFactorial: ii | ret | self primitive: 'primitiveFactorial' parameters: #(SmallInteger). ii <= 1 ifTrue: [^ 1 asOop: SmallInteger]. ret := 1. 1 to: ii do: [:idx | ret := ret * idx]. ^ ret asOop: SmallInteger
なお、#asOop: の OOP は、この文脈では object-oriented pointer の略です。
動作チェック用のクラスをこしらえて、メソッドを書きます。ここではクラスメソッドにしました。
Object subclass: #MyFirstPluginChecker instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Plugin-Lesson'
MyFirstPluginChecker class >> factorial: anInteger <primitive: 'primitiveFactorial' module: 'MyFirstPlugin'> self primitiveFailed. ^ (Smalltalk at: #MyFirstPlugin ifAbsent: [^ self error: 'can''t find simulator class.']) doPrimitive: #primitiveFactorial: withArguments: (Array with: anInteger) "MyFirstPluginChecker factorial: 10"
動作チェックをしてみましょう。
MyFirstPluginChecker factorial: 10
この式を print it (cmd + p) すると、いったんプリミティブの失敗を告げるノーティファイア(エラー表示)が現れます。プラグインを作っていないので当たり前ですね。かまわず Proceed ボタンを押すと、記述した Slang コードを Smalltalk コードとして実行した結果が式の直後に挿入されます。
=> 3628800
ここでいったんデスクトップメニュー → save などとして仮想イメージを保存しておきます。
必要なファイルの入手
OS X 環境に Subversion をインストールしてない場合は、あらかじめインストールしておく必要があります。私はこちらで公開されているパッケージを使わせていただきました。
ターミナルで次のコマンドを実行します。
$ svn co http://squeakvm.org/svn/squeak/trunk dirname
すると、~/dirname(dirname は適宜決めてください)以下に platforms というフォルダが出来ます。これのエイリアスを冒頭で保存した SqueakPlugin-dev-vmm.image と同じフォルダに作ります。
VMMaker の起動
再び Squeak システムに戻り、デスクトップメニュー → open... → VMMaker で VMMaker を起動します。もし「MessageNotUnderstood: SystemDictionary>>wordSize」というノーティファイアが現れるようなら MacOSPowerPCOS9VMMaker >> #initialize の、
is64BitVM := Smalltalk wordSize == 8.
のところを、
is64BitVM := false "Smalltalk wordSize == 8".
と書き換えて accept (cmd + s) してから再度 VMMaker の起動を試みてください。
Path to platforms code: 欄が先ほど作成した platforms のエイリアスを指しているかを念のため確認しておきましょう。
Slang コードの C コードへの変換
下段左側の「Plugins not built」というリストペインから、MyFirstPlugin を探してクリックして選択します。再びマウスボタンをプレスしてドラッグすると、#MyFirstPlugin という青い半透明のラベルをつかめるので、そのまま右端の External Plugins ペインにドラッグしてドロップインします。
この状態で、上段右手の External Plugins ボタンをクリックすると変換が行なわれ、./src32/plugins/MyFirstPlugin に MyFirstPlugin.c というファイルが現れます。
MyFirstPlugin.c の中身を見ると、上の Slang コード(MyFirstPlugin >> #primitiveFactorial:)がこんなふうな C コードに変換されていることが確認できるでしょう。
EXPORT(sqInt) primitiveFactorial(void) { sqInt idx; sqInt ret; sqInt ii; sqInt _return_value; ii = interpreterProxy->stackIntegerValue(0); if (interpreterProxy->failed()) { return null; } if (ii <= 1) { _return_value = interpreterProxy->integerObjectOf(1); if (interpreterProxy->failed()) { return null; } interpreterProxy->popthenPush(2, _return_value); return null; } ret = 1; for (idx = 1; idx <= ii; idx += 1) { ret = ret * idx; } _return_value = interpreterProxy->integerObjectOf(ret); if (interpreterProxy->failed()) { return null; } interpreterProxy->popthenPush(2, _return_value); return null; }
Xcode でビルド
Xcode を起動し、MyFirstPlugin という名前で Carbon Bundle な新しいプロジェクトを作成します。
左側リストペインの「ターゲット」から 「MyFirstPlugin」を選択し、上段の「情報」ボタンを押して情報パネルを開き、さらに「ビルド」タブに切り替えてから次の設定をします。
- ヘッダ検索パス → /Developer/Headers/FlatCarbon
- パッケージ情報を強制的に作成 → チェックをオン
続けて「プロパティ」タブに切換え、
- 識別子 → com.yourname.MyFirstPlugin
- クリエイタ → FAST
と変更して(yourname は適宜決めてください)、情報パネルを閉じます。
最後に、先ほどの MyFirstPlugin.c に加えて、次のファイルたちを右側リストペインの Source フォルダにドロップインするなどして追加すれば、準備は完了です。
- ~/dirname/platforms/Cross/vm から
- sqMemoryAccess.h
- sqVirtualMachine.h
- ~/dirname/platforms/Mac OS/vm から
- config.h
- sqConfig.h
- sqPlatformSpecific.h
- ~/dirname/platforms/unix/src32/vm から
- interp.h
ビルドボタンを押してビルドすると、~/MyFirstPlugin/build フォルダに MyFirstPlugin.bundle というファイルが出来ています。これを Squeak VM のフォルダに移動します。Squeak VM のフォルダは、Squeak 起動中にドックの Squeak アイコンを長押しして現れるポップアップから「Finder に表示」を選択することで呼び出すことが可能です。
MyFirstPlugin.bundle を移動(またはコピー)したら、Squeak システムを念のため、デスクトップメニュー → save and quit で保存後終了し、再起動します。
VM プラグインの動作確認
再び、次のコードを print it (cmd + p) してみます。
MyFirstPluginChecker factorial: 10
もしプラグインの読み込みがうまくいってプリミティブの呼び出しが正常なら、今度はノーティファイアなしに結果を即座に返してくるはずです。
=> 3628800
Smalltalk バイトコードとの速度比較をしたいときは、次のようにして比べたい式を(適当な回数の繰り返し式にしたのち、さらに)ブロックで囲んでから timeToRun メッセージを送信します。レシーバブロック内の式の評価にかかった時間をミリ秒の単位の数値で返してきてくれます。
[1e5 timesRepeat: [10 factorial]] timeToRun
[1e5 timesRepeat: [MyFirstPluginChecker factorial: 10]] timeToRun