第32章. VMの構築とモジュールの実行
この章の目的
この章を読み終える頃には、VM構築の各段階と、VMとアリーナのライフタイムの関係を理解しているはずです。この章を読み終える頃には、VM構築の各段階と、VMとアリーナのライフタイムの関係を理解しているはずです。
4つの段階
ソースからVMを構築するのは4段階で、各段階で別の型ができます。
#![allow(unused)]
fn main() {
let tokens = tokenize(SOURCE)?; // Vec<Token>
let program = parse(&tokens)?; // Program
let module = compile(&program)?; // Module
let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
let mut vm = Vm::new(module, &arena)?;
}
tokenizeがソーステキストをVec<Token>に変えます。parseがそれをProgram、つまり構文木に変えます。compileがそれをModule、つまりバイトコードオブジェクトに変えます。Vm::newがモジュールを消費し、アリーナを借りて、構造検証と資源境界検証を行い、呼び出せる状態のVMを返します。Vm::newがモジュールを消費し、アリーナを借りて、構造検証と資源境界検証を行い、呼び出せる状態のVMを返します。
各段階はResultを返します。どこかの段階で失敗すると、型付きのエラーになります — LexError、ParseError、CompileError、またはVm::newからのVmError::VerifyError、です。各段階はResultを返します。どこかの段階で失敗すると、型付きのエラーになります — LexError、ParseError、CompileError、またはVm::newからのVmError::VerifyError、です。
検証は構築のときに行われる
Vm::newが、第5部の保証が強制される場所です。返す前に構造検証器と資源境界検証器を走らせます。検証器が却下するプログラムは、VMを得ることはありません。Vm::newがErr(VmError::VerifyError(message))を返し、メッセージはWHY_REJECTED.mdに書いてあるとおりのものです。その時点でスクリプトコードは1命令も走っていません。ホストはプログラムが受け入れられないことを、実行の途中ではなく、構築の時点で知ります。Vm::newが、第5部の保証が強制される場所です。返す前に構造検証器と資源境界検証器を走らせます。検証器が却下するプログラムは、VMを得ることはありません。Vm::newがErr(VmError::VerifyError(message))を返し、メッセージはWHY_REJECTED.mdに書いてあるとおりのものです。その時点でスクリプトコードは1命令も走っていません。ホストはプログラムが受け入れられないことを、実行の途中ではなく、構築の時点で知ります。
アリーナとそのライフタイム
アリーナはVMがオペランドスタックと動的文字列に使う、有界の作業メモリです。ホストが作って、VMが借ります。アリーナはVMがオペランドスタックと動的文字列に使う、有界の作業メモリです。ホストが作って、VMが借ります。
借りはRustの借用検査器が強制します。Vmは'arenaライフタイムパラメータを持っていて、アリーナはVMより長く生きなければなりません。実際には、アリーナをVMより前に宣言してVMより後にドロップする、ということになります。普通のブロックスコープがそれを自動でやってくれます。借りはRustの借用検査器が強制します。Vmは'arenaライフタイムパラメータを持っていて、アリーナはVMより長く生きなければなりません。実際には、アリーナをVMより前に宣言してVMより後にドロップする、ということになります。普通のブロックスコープがそれを自動でやってくれます。
#![allow(unused)]
fn main() {
let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
let mut vm = Vm::new(module, &arena)?;
// vmはここで使われる; arenaはそれより長く生きる
}
第35章でアリーナの容量の選び方を扱います。今のところはDEFAULT_ARENA_CAPACITY、64キロバイトで足ります。第35章でアリーナの容量の選び方を扱います。今のところはDEFAULT_ARENA_CAPACITY、64キロバイトで足ります。
原子的モジュールを動かす
構築されると、原子的なfn mainモジュールはcallで動かします。
#![allow(unused)]
fn main() {
match vm.call(&[])? {
VmState::Finished(value) => { /* valueを使う */ }
other => panic!("expected Finished, got {:?}", other),
}
}
callは引数のスライスを取ります。パラメータを取らないfn mainは&[]で呼びます。原子的モジュールは最後まで走り、callがVmState::Finished(value)を返して、戻り値を運びます。callは引数のスライスを取ります。パラメータを取らないfn mainは&[]で呼びます。原子的モジュールは最後まで走り、callがVmState::Finished(value)を返して、戻り値を運びます。
yieldやloopモジュールは最初のcallでは終わりません。代わりにVmState::Yieldedを返し、動かすには再開プロトコルが必要です。第34章でそれを扱います。多くの本物のスクリプトに必要なネイティブ関数を先に扱います、次の章で。yieldやloopモジュールは最初のcallでは終わりません。代わりにVmState::Yieldedを返し、動かすには再開プロトコルが必要です。第34章でそれを扱います。多くの本物のスクリプトに必要なネイティブ関数を先に扱います、次の章で。
この章のまとめ
- VMの構築は4段階 —
tokenize、parse、compile、Vm::new、です。 Vm::newが検証を行います。却下されるプログラムはここで失敗し、コードは走りません。Vm::newが検証を行います。却下されるプログラムはここで失敗し、コードは走りません。- VMはアリーナを借り、アリーナはVMより長く生きなければなりません。
call(&[])は原子的モジュールを動かしてVmState::Finishedを返します。
次の章ではスクリプトが呼ぶホスト関数を登録します。