« 2009年1月 | メイン | 2009年3月 »

2009年2月27日

相互再帰

末尾再帰の話続きです。

(コンパイラに最適化ができるという他に)何がうれしいのか、むしろプログラマからみるとかっこ悪いだけじゃないのか。と思って、Web上のリソースをいろいろあさってみました。末尾再帰に関する、Web上の日本語の資料でたぶんもっとも有名なもののひとつ、「なんでも再帰」を読んでみました。

「なんでも再帰」ではまず軽く末尾再帰の例が出てから、相互末尾再帰の例がでてきます。そしてだんだん複雑な例になっていきます。

なるほど状態遷移に使える、つまりStateパタンのように使えるのか。

...あれ、「天啓が降りてきた!」くらいにおもったのに、かいてみるとなんてことないな。

でも、わたしにとっては、「あ、Stateパタン」と思った瞬間に、関数がfirst-class objectであることと、末尾再帰の概念がセットになってすとんと腑に落ちたのでした。継続がいまだに腑に落ちないんですが、近々わたしの中ではこれもセットになりそうな予感。

2009年2月25日

末尾再帰ってかっこわるくね?

末尾再帰って言葉をはじめてきいたのがいつだったかよく覚えていませんが、最初の印象は「理解できるけどあたまにはいらない」でした。

今日なんとなく気が向いて、末尾再帰についてあらためて確認してみました。

まず何も考えずに、数字のリストの内容を素朴に足すsum1を定義してみます。

gosh>(define (sum1 lst)
		(cond 
		 ((null? lst) 0)
		 (else (+ (car lst) (sum1 (cdr lst))))))
sum1
gosh>(sum1 '(1 2 3 4 5 6 7 8 9 10))
55
gosh>

これがわたしの思う、素直な再帰です。で、末尾再帰にはなっていません。再帰的に呼ばれたsum1の値は、一個上のレベルで(car lst)と加算されるからです。

では、これを末尾再帰にするとどうなるでしょう。

gosh>(define (sum2 lst)
		(sum2-loop lst 0))
sum2
gosh> (define (sum2-loop lst n)
		(cond
		 ((null? lst) n)
		 (else (sum2-int (cdr lst) (+ n (car lst))))))
sum2-loop
gosh> (sum2 '(1 2 3 4 5 6 7 8 9 10))
55

sum2自体は、末尾再帰になっているsum2-loopを呼び出すだけです。sum2-loopでは末尾再帰にするために、ワークエリアになる引数「n」を用意しました

再帰で呼ばれるたびに、第2引数に値が足されていきます。最終的にリストが空になったら、その第2引数をそのままかえす。

...久しぶりに末尾再帰の概念を確認してみました。やっぱりこれ、ループの表現変えただけじゃん。戻り値を保持するための引数ってかっこわるいじゃん。ループと等価だからループに最適化できる、って見るからにあたりまえじゃん(最適化の実装はあたりまえ、じゃないだろうけど)。再帰な問題を再帰でかく喜びというか驚きがかんじられないじゃん。

と、あらためて思いました。

しかし、わたしは間違っていた、末尾再帰(的なモデルの意義)の神髄の片鱗を、きょうはじめて実感できたかも! と思ったのでした。

以下次号。

2009年2月20日

ひとり勉強会の方針

このブログ(別館)はじめてから3年です。思い出したようにしか更新していませんでしたが、今年からこころを入れ替えました。

唐突にはじめたVirtualMachine本のひとり読書会もそれなりに進んでいます。このあたりで自分むけメモとして、今年の「勉強」の方針を書いておきます。

原則編

このブログをはじめる前から数えると10年以上、長らく迷走していましたが、昨年末に簡単な原則に辿り着きました。

あくまでも趣味
わたしは仕事でプログラムを書きますが、個人的にやることはあくまでも趣味。もちろん、もしかすると仕事で役立つかもしれないけど、それは結果的にたまたまそうなるかもしれないだけ。
自分が楽しいと思うことしかやらない
趣味だからな。「お勉強」じゃないのだ。
途中で投げない
趣味だからこそ真剣に。本当に楽しいことなら投げないし。
色気を出さない(外部の基準でうごかない)
これからはXXが流行るぞ、とか、いまみんな△△読んでるなあ、とか、これやっとくと食いっぱぐれないかも、とか、プログラマとしてはこれしっとかないとまずいらしいぞ、とか、そういう、「楽しい」以外の動機で何かをやろうとしない。
短時間に手を広げすぎない
楽しそうな知らないことはいくらでもあるので、手を広げすぎるとどれもまともにできなくなります。

実践編

この半年くらいでやるつもりのこと・やりたいことは以下の通り。

VMについて学ぶ
言語VMだけじゃなくてISAに対するVMも一通り。教材は今読んでいるこの本
VM何か作る
本読むだけじゃつまらんから、何か作りたい。現実的には言語VMかな。CLIの実装もやってみたい。現実的かどうかはわからないけど、ハードウェア層のすぐ上で動作する言語VMもつくってみたい
VMのソースをいくつか読む
どの程度の深みまで読むかは読みながら考える。読む対象はMono, Lua, Factor
Lispをまた学ぶ
Little Schemerをとっかかりに、継続・マクロを使えるようになること。Y combinatorは分からなくてもよし。
視覚化をがんばる
道具はR言語とProcessing。

ここに書いている以外のことは、iPhone楽しそうだし、Haskellも面白そうなんだけど、(原則として)短期的にはやらない。時間は限られているからね。

2009年2月13日

知らないことメモ

ソースコード読みをしていて思ったこと

  • setjmp/longjmp使った事ねえからよくわからん
  • autoconfについても知らない
  • glib何をするものぞ

情けないなあー

2009年2月11日

gtagsを使うことにした

久々に、まじめにコード読もうと思って、etagsを使おうとして、こんなに面倒だったっけ? と思い、GNU globalをいれてみることにしました。

インストール

  1. GNU global 5.7.4のダウンロード
  2. configure/make/make install
  3. emacsの設定

emacsの設定でやったこと:
gtags.elをemacsのロードパスが通ったところにいれる

emacs.elに次のことをかく

;; gtsgs
(load "gtags")
(define-key gtags-mode-map [?\C-\M-.] 'gtags-find-tag-other-window)
(define-key gtags-mode-map "\M-r" 'gtags-find-rtag)
(add-hook 'c-mode-hook
		  '(lambda ()
			 (gtags-mode 1)
			 (gtags-make-complete-list)
			 ))

使い方

まず、読みたいソースのてっぺんでgtags -vする。
(これでgtagsが使うファイルが作られる)

それから、emacsで何かソースファイルを開く。

関数名のところでM-.すると、定義されてる場所をみつけてくれる。
M-rすると、参照元をみつけてくれる。

2009年2月10日

VirtualMachine本: Chapter 3 Process Virtual Machines

Virtual Machines: Versatile Platforms For Systems And Processes (The Morgan Kaufmann Series in Computer Architecture and Design)Virtual Machines: Versatile Platforms For Systems And Processes

VM本第三章やっと読み終わりました。ProcessVMについてです。

いろいろなVMがあるけど、ここではProcess or Program VMについて語られます。「仮想ABI」です。たとえばIA-32/WindowsのアプリケーションをItaniumのWindows上で動作させるものです。

3.1 Virtual Machine Implementation

Process VMのよくある構成について、主なブロック。

loader
guestのコードをロードする。
initialization
ロードされたあとに、初期化をするモジュール。code cacheとか、trapの登録とか、いろいろな前準備をする
emulation engine
インタープリットまたは・およびバイナリトランスレーションをする。プレデコードとかトランスレート結果はcode cacheに保持される。
code cache manager
コードキャッシュはたいてい限られているので、その管理をする。
profile database
ダイナミックに収集されたコードのプロファイル情報。最適化に使われる。
OS Call Emulator
OSとのインタラクションをエミュレートする。
Exception Emulator
Exception(trapや割り込み)をエミュレートする。
Side tables
トランスレートの信仰とともに生成されるデータ。用途はいろいろ。

3.2 Compatibility

ネイティブなプラットフォームとVM環境の互換性について。

3.2.1 Levels of Compatibility

"intrinsic compatibility"と"extrinsic compatibility"がある。

intrinsic compatibilityは"complete transparency"ともいわれる。多くの場合は完全な透過性はオーバースペック。

extrinsic compatibilityは、何らかの条件がつく。たとえば、特定のアプリケーションは動作するよ、とcertifyするなど。

3.2.2 A Comptibility Framework

intrinsicだろうがextrinsicだろうが、互換性があることを証明するのは大変むつかしい。その話をするためのフレームワークとして、まずはVMを構成要素に分解する。そして、各構成要素について(1)native環境とのステートマップ(2)ステート間遷移のマップをおこなう。このステートは、user-managedとOS-managedに分けることができる。

State Mapping

user-managedなステートマップはそんなに難しくない。guestのレジスタがhostのメモリにマップされるなどの場合があるが。OS-managed stateは、考え方が同じだがもうちょっと複雑。

Operations

user codeからOSへ制御がうつるポイントがキー。このマップがつくれれば、そのポイントでの状態が同等かどうかに集中できる。

Sufficient Compatibility Conditions

制御ポイントが明確になると、次のことがいえる:

  1. user code → OSのポイントで、ゲストの状態がホストの状態とマップ上同じである。ここからいえる重要なこと: 命令の粒度ではなく、OSへコントロールが渡る単位で状態の同等性をメンテナンスすればよい。
  2. OS → user codeのポイントでも、状態が同等。このとき、グラフィックとかネットワークとかみたいに、状態が同等なだけじゃなくてオペレーションの順番が同等である必要があるものもある。
Discussion

ここまでをふまえた議論。

3.2.3 Implementation Dependences

ISAの実装が、機能の差として顕れるケースがある。

たとえば、コードセグメントへの書き込みが、コードキャッシュにすぐ反映されない。

このような実装差が問題になるのは、たとえば、CPUの種別判定に使われる時。

逆に、仕様上は「コードキャッシュに反映されない」となっていても、実在の実装がすべてコードキャッシュに反映させているケースもある。そうすると、仕様に従って実装されたVMでは、現実のコード(自己変更含むもの)がうごかないかもしれない。「仕様に従っている」はいいわけにならない。

3.3 State Mapping

guest → targetの状態マップ。状態とはレジスタとメモリのことで、要はリソースなので、リソースマップと呼ぶこともある。

ここでのメモリは、論理メモリで実メモリスペースの話じゃない。

3.3.1 Register Mapping

レジスタマッピングはstraightforward(ってなんて訳すのがよいかな)。target側に充分なレジスタ数があれば、全部マップすればよいし、ダメならメモリにマップしたり、ダイナミックにマップを変更したりする。

3.3.2 Memory Address Space Mapping

メモリ空間のエミュレーションは、いろんなやり方がある。ソフトウェアの役割が大きいやりかたほど遅くなり、ハードウェアに任せる部分が多いほどパフォーマンスがよくなる。

Runtime Software-Supported Translation Tables

一番フレキシブル。ランタイムソフトウェアがメモリ管理。

でもバイナリトランレート環境ではかなり遅い。最後の手段。

Direct Translation Methods

単純なマッピング。同一アドレスか、単にオフセットを与えるかだけ。

Compatibility Issues

メモリマッピングとアドレス変換に何を使うか、はパフォーマンス・互換性の要求と深い関係がある。

メモリサイズや、ランタイムがアドレス空間を共用するか、など。

extrinsic compatibilityでよい場合も多い。

3.4 Memory Architecture Emulation

メモリアーキテクチャのエミュレーションで、考慮しなきゃいけないのは:

  • アドレス空間の構成: セグメントがあるのか、連続なのか。
  • アクセス権: R/W/Eがあるのか。RWのみか。
  • 保護・アロケーションの粒度:メモリブロックのサイズ
3.4.1 Memory Protection

メモリプロテクションは、もし変換テーブルが用意されていれば簡単。しかし直接またはオフセットアドレッシングだとそうはいかない。ホストのメモリプロテクション機能に頼ることもできるが、ページサイズがホスト・ゲストで異なるとか、実行中にメモリの権限が変更されることもある。

3.4.2 Self-Referencing and Self-Modifying Code

自己参照はオリジナルの参照をしているからOK。自己書き換えについて、基本的なやりかたはRead onlyにしておくこと。例外のハンドリングで自己書き換えを扱うことができる。

psude-self-modifing code

psude-self-modifingという概念がある。code pageのなかに、実際にデータが混じっているもの。デバイスドライバや組み込みのコードに見られる。psude-self-modifing codeについては、「頻繁にwrite exceptionがおきるとき、それがpsude-self-modifingかどうかチェックして、その場合はwrite protectionをはずす」という手がある。

Fine-Grain Write Protection

ページ単位ではなく、もっと細かいwrite protectionをソースのコードに用意する。ページ毎にbit mask

True Self-modifing Code

ほんとうにSelf-modifing codeがある場合(code regionにデータがあるのではなく、実際にcodeがかきかわる場合)は、idiom recognitionでself modifyじゃないコードに書き換える。

Protecting Runtime Memory

Omniware VMという例では、2の累乗サイズのページごとにアクセス権を設定。それにより、アドレスのシフト演算で権限チェックが簡単にできる。

ランタイム実行中と、エミュレーション中で権限を切り替えるやりかたがある(エミュレーション中はランタイムのアドレスにはアクセスできない、そしてCode Cacheは実行可能。ランタイム実行中は、Code Cacheは読み書き可能、など)。

レジスタがメモリ上にマップされているときの問題もある。

3.5 Instruction Emulation

(続く)

2009年2月 9日

Concatenative/Applicative

昔々、FORTHという言語が好きでした。
なんでもスタックベースで、あたまの中で容易に実行モデルがつくれるのがきもちよかったのだと思われます。

FORTH(Forthって書くんだっけ)も生き残っていますが、派生した言語もいくつかあります。その中でも比較的最近で、なんだかリッチそうな言語Factorについて、ちょっと調べはじめました。

まず、Factorは「concatenative language」だと名乗っています。そりゃなんだ? Concatenative.orgというところに説明があったので読んでみました。

applicable programming language
引数に関数を適用するタイプ。普通のプログラミング言語。C, Python, ML, Haskell, Java
concatenative programming language
single piece of dataを操作する関数を組み合わせることで評価する。この関数から関数へ渡されるデータは、スタックの形をしていることが多い。そしてconcatenative languageでは、この関数の組み立てはプログラムをつなげることでおこなわれる。Forth, Joy, PostScript, Cat, Factor

concatenativeとstack言語は違うの? → 似ているけど、違うクラスの言語。しかしほぼ同じと考えて良い、とのこと。しかしそういわれると違いがなんなのか気になるなあ。stack以外でも理論的には構成できる、ってとこかな?

2009年2月 4日

VirtualMachine本のひとり勉強会

VM本読書記録がいくら自分用メモとはいえ、粒度がむちゃくちゃで読みにくいので、いったんVM本の全体構成をおさらいしてみましょう。(そういうことは先にやれ>おれ)

現時点(2009.2.4)では、Chapter 3の途中です。読み進むにつれ、このエントリはかきかえていきます。

Chapter 1: Introduction to Virtual Machines

Chapter1のメモ

タイトル通り、仮想マシンへのイントロダクション。コンピュータのアーキテクチャ、「仮想」とはなんであるか、仮想マシンのいろいろ、そして本書の構成について。

Chapter 2: Emulation: Interpretation and Binary Translation

Chapter2のメモ

命令セットのエミュレーション全般。直観的なインタープリットから始まり、threaded, predecode, binary translationなどの方式や、命令セットのエミュレーションにおけるさまざまな問題について概観します。

Chapter 3: Process Virtual Machines

Chapter3のメモ

プロセスVMを構成するはなし。まずは全体の構成からはじまります。それから互換性・状態のマッピング・エミュレーションいろいろ(メモリアーキテクチャ・命令・例外・OS)・コードキャッシュの管理・そしてシステム環境への埋め込み。FX!32の実例をとりあげてから、まとめです。

Chapter 4: Dynamic Binary Optimization

ダイナミックバイナリ最適化の話。ここでは主に複数のbasic blockをまたがる最適化について。

まずは最適化の前提としてプロファイルで「どこがよく実行されているのか」を知る。そしてsuperblockをはじめとするブロック最適化。それをベースにした最適化フレームワーク。フレームワークの上でのテクニックとしてのコード再配置。そしてコードの最適化いろいろ(余分なブランチ削除、定数展開、コピー展開等々)。さらに、同一ISAでの最適化についての話題。

以下、じわじわ更新していきます。

Chapter 5: High-Level Language Virtual Machine Architecture

Chapter 6: High-Level Language Virtual Machine Implementation

Chapter 7: Codesigned Virtual Machines

Chapter 8: System Virtual Machines

Chapter 9: Multiprocessor Virtualization

Chapter 10: Emerging Applications

Appendix A: Real Machines

実マシンの復習。

Virtual Machines: Versatile Platforms For Systems And Processes (The Morgan Kaufmann Series in Computer Architecture and Design)Virtual Machines: Versatile Platforms For Systems And Processes

VirtualMachine本: Chapter 2 Emulation: Interpretation and Binary Translation

Virtual Machines: Versatile Platforms For Systems And Processes (The Morgan Kaufmann Series in Computer Architecture and Design)Virtual Machines: Versatile Platforms For Systems And Processes

やっとダイナミックなバイナリトランスレートの話がでてきましたよ。

(2009.2.9 Chapter毎に整理しました)

Source ISAとTarget ISAが異なるときの話。しかし、そうじゃない場合にもこのテクニックは使える。binary optimizationとか、特権命令実行時のemulation的動作とか。

2.1 Basic Interpretation

普通のエミュレーション。メインループがあって、デコード・命令ディスパッチを実行。ブランチが多すぎて遅い。

この章で突然「FORTHは"threaded code" interpretation modelで有名」と出てきて驚く。FORTHは昔だいぶ遊んだけど、それ知らないぞ(と思ったのですが、threaded codeのところを読んだら納得できました)

2.2 Threaded Interpretation

各Instruction emulationの最後に、decodeして対応するインストラクションを実行する。テーブルをひき、そこへ飛ぶコードを追加する。メインループはもういらない。これは、デコード結果からテーブルをひいてジャンプ、なので "indirect" threaded interpretationと呼ばれる。

2.3 Predecoding and Direct Threaded Interpretation

ループは消えたけど、毎回デコードのオーバーヘッドがある。

2.3.1

そこでプレデコード。デコードして扱いやすい形で再度格納。

2.3.2 Direct Threaded Interpretation

デコード結果の「オペランド」のところに、そのオペランドを実行するアドレスを直接いれる。メモリルックアップが1回減らせる。

2.4 Interpreting a Complex Instruction set

ここまではPowerPCを例にしていたが、IA32みたいなCISCでは大変。

IA32だと、General Decodeしてから、特殊な処理に分岐するのが基本的な動き。

2.4.2 Threaded Interpretation

RISCに比べてdecode処理が複雑ででかい。各instruction interpreterの最後にdecode処理を書いていたら、インタプリタがでかくなる。

ということで、一般的なケースに特化したdecode-and-dispatchルーチンをいれる。特殊なケースはブランチしてとばす。

Direct Threaded Interpretationをやろうとするといろいろ問題。とくに2つ:

  1. predecoded instructionは長くなる傾向がある。メモリをいっぱいくう。 instruction types毎に中間コード用意するなど あるいは、小さいインストラクションに分解するなど → これはCISCからRISCへのbinary translateに似ている
  2. code discovery。実行しないと命令の境界が分からない場合があり、プレデコード困難。 binary translationでも問題になるので、あとでくわしく取り扱う。

また、2段階のdecode-dispatchになる。これもbinary translationと非常に似ている。 binary translationはポータビリティに欠ける、が、retargetable binary translatorが開発されている (p46)

2.4.3 High-performance IA-32 Interpreter

FX!32をベースにした例。オリジナルのAlphaじゃなくて、PowerPCのアセンブラで記述されているが、PowerPCをよく知らないのでほとんど理解できない。IA32の1インストラクションをAlphaの30インストラクション相当で実行できるらしい。

2.5 Binary Translation

2.4まではインタプリタだけど、target ISAのバイナリコードに変換するとパフォーマンスがあがる。

インタプリタと違って、source ISA状態のマッピングをtarget ISAのレジスタに直接おくことで、コンテクストブロックをメモリから読み込むオーバーヘッドを削除できる。

2.6 Code Discovery And Dynamic Translation

2.6.1 The Code Discovery Problem

スタティックにプレデコードしたりバイナリトランスレートしたりすることを考えてみる。(特にCISCなISAでは)これはかならずしも簡単じゃない(それどころか不可能な場合もある)。命令境界がどこかわからないし、生成されたコードのなかにデータとかパディングがはいっていることだってある。

RISCであれば、1命令1ワードだったりするが、たとえばIA32だと、任意のバイトが本当に命令境界なのか判断する方法はない。

2.6.2 The Code-Location Problem

間接ジャンプだと、ターゲット側に展開された命令列のどこに飛べばいいかわからない(ただしRISCやJavaVMではこういう問題は起こらない)

2.6.3 Incremental Predecoding and Translation

2.6節のハイライト! (ってそんなに気合いをいれるほどのことはない)。プレデコードとトランスレーションはきわめて似たプロセスなので、この本では「トランスレーション」と呼びます。

Emulation Manager、インタプリタとトランスレータ、トランスレートされたコード(のキャッシュ)、そしてソースPCからターゲットPCへのマッピングを示すハッシュテーブルをそろえれば、ダイナミックにインクリメンタルなトランスレートができます。

Emulation Managerは、マップを使ってコードキャッシュを探し、ヒットすればトランスレートされたブロックを呼びます。ヒットしなければ、インタプリタ・トランスレータを呼んで、次のブロックをインタープリト・トランスレートし、マップにトランスレートされた結果をいれます。

このときの、トランスレートの単位は「Dynamic Basic Blocks」。ブランチで入ってきたポイントから、ブランチで出るポイントまでを(動的に)認識したものです(これにたいして、Static Basic Blocksは、静的構造を解析してエントリポイント・エグジットポイントで切ったもの)。

Dynamic Basic Blocksの特徴として「他の大きなブロックの一部が、新たに小さなブロックになることがある」です。これは、大きなブロックの途中に、エントリポイントがある場合などですね。(p57の図がわかりやすい) このダブりがあることで、すでにトランスレートされたコードの途中にブランチしたら、みたいな複雑な処理がいらなくなっています

こんどは、ブロックごとに「キャッシュを探す」「Emulation Managerに戻る」オーバーヘッドをへらすことが考えられます。キャッシュを探す、については、SourceのPCをTargetのレジスタにいれとくとか、コードブロックの最後に次のSource PCを置いておき、そこをlink register経由でアクセスする方法とかがあります。

他にも問題がいろいろ。これらはChapter3や4で扱われます。

  • self-modifying code
  • self-referencing code
  • precise traps

2.6.4: Same-ISA Emulation

同じISAでもEmulationがありうる。Emulation Managerが常にコントロールをもち、かつモニタ(= code management)できる。この一例が"simulation"。2.9でcase studyとして扱われる。

2つめの例は、ISAが同じだがOSが異なる場合。このときはsystem callをemulationする。

3つめの例は、特権命令を扱うエミュレーション(Chapter 8)。

4つめは、Chapter10であつかう"program shepherding"。セキュリティ的監視。

さいごが、ランタイムでのbinary optimization。

Chapter 3に突入したけど、ここでの復習が追いつかない!

2.7 Control Transfer Optimization

2.6までの「シンプルな」トランスレート方法では、Source PC/Target PCのルックアップや、ブロックごとにEmulation Managerへコントロールが戻ることがどうしても起きる。このオーバーヘッドを減らす方法いろいろ。

2.7.1 Translation Chaining

インタプリンタのスレッディングと同じようなテクニック(名前から見当つくよね)。トランスレートされたブロックをチェーンするのだ。

次のブロックがまだトランスレートされていない場合は、通常のスタブコードが入る。

2.7.2 Software Indirect Jump Prediction

ソフトウェア分岐予測! これは本質的には「inline caching」の実装である。分岐先のコードを、出現しそうな順に、ifのなかで即値で埋め込んでおくのです。

なので、profilingとセットでやる必要がある。

2.7.3 Shadow Stack

sourceのstackへ戻り値を格納すると同時に、target側でEmulation Managerが管理する"shadow stack"にtargetの戻り先PCを格納する。stackからpopするときは値のチェックをしてconsistencyをみる。

VM本2章の終わりのほうです。

2.8 Instruction Set Issues

インストラクションセットエミュレーションについて、まだまだいろいろあるけどそのうちの一部を扱います。

2.8.1 Register Architectures

レジスタはストレージヒエラルキのてっぺんにいるので、パフォーマンスに影響がでかい。

レジスタ割り当ての戦略についてbinary translation, interpreterのそれぞれで、一般論を。

2.8.2 Condition Codes

フラグの類について。sourceとtargetでISAが違えば、同じではない。MIPSみたいにcondition codeがないようなものもある。

source instructionの実行のたびにcondition codeを更新する手もあるが、これは遅いし、通常必要でもない。lazy evaluationがよく使われるテクニック。condition codeに影響がある、最近実行された命令とそのオペランドを記録しておき、必要になったらcondition codeを生成する。

この方法でも、実行中にtrapが発生した場合に問題がある。trap発生したら、preciseなソース状態を作らなくてはならない。このへんはあと(4.5.2)で議論する。

2.8.3 Data Formats and Arithmetic

浮動小数点のフォーマットはかなり統一されているが、たとえばIA32では80bitの中間結果がつかわれるが、ほかの多くのISAでは64bitみたいな差がある。

ほかにもいろいろ。PowerPCでは連続した乗算・加算のインストラクションがあるが、これはエミュレートできない場合がある(途中でオーバーフローしちゃうとか)。アドレッシングモードが違うとか、即値の長さが違うとか。

2.8.4 Memory Address Resolution

sourceとTargetでアクセスできるメモリの単位が違う。たとえばbyte単位でのメモリアクセスができないISAもある。

2.8.5Memory Data Alignment

メモリのアラインメントもISAで違う。いろんなやりかたがあるけど、プロファイルをとるような方法もある。Chapter4で議論する。

2.8.6 Byte Order

BigEndian, LittleEndianなISAがある。

両方サポートしているISAもある。

2.8.7 Addressing Architecture

サイズが違う、特権レベルが違うなどいろいろ。

2.9 Case Study: Shade and the Role of Emulation During Simulation

"Shade"という、high-performanceなsimulationのケーススタディ。

"simulation"は、「内部動作までのシミュレート」を意味する。emulationは表面の動作のみ。

あまり細かいことはかいていないが、code cacheのメカニズムがシンプル。source PCからhash経由で、translated codeの場所がひかれる。hashのさすさきには、n個の"source/target"ペアがはいってる。新しく使われたのが、せんとうにくる。もしいっぱいになれば、n個目が押し出される。そういうわけで、translateされたけど、danglingになるtranslated codeがある(本文中では"orphan"って表現されてる)。

code cacheはあたまからつめられていく。もし最後に到達したら、単純にすべてフラッシュされる。そういうわけで、danglingなコードはそのうち消える。

このメカニズムはシンプルだけど、効率ほんとにいいのか? と疑問に思うが、いいんだろうな、確かめるためには論文にあたればよさそう。

2.10 Summary: Performance Tradeoffs

2章のまとめ。つぎのものたちが出てきています。

Decode-and-Dispatch
メモリ要求少ない、スタートアップ速い、定常動作遅い、ポータビリティ高い
Indirect Threaded Interpreter
メモリ要求少ない、スタートアップ速い、定常動作遅い(Decode-Dispatchよりはまし)、ポータビリティ高い
Direct Threaded Interpreter with Predecoding
メモリ要求多い、スタートアップ遅い、定常動作普通、ポータビリティ普通
Binary Translation
メモリ要求多い、スタートアップとても遅い、定常動作速い、ポータビリティダメ
Virtual Machine本ひとり勉強会