Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第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を返します。どこかの段階で失敗すると、型付きのエラーになります — LexErrorParseErrorCompileError、またはVm::newからのVmError::VerifyError、です。各段階はResultを返します。どこかの段階で失敗すると、型付きのエラーになります — LexErrorParseErrorCompileError、またはVm::newからのVmError::VerifyError、です。

検証は構築のときに行われる

Vm::newが、第5部の保証が強制される場所です。返す前に構造検証器と資源境界検証器を走らせます。検証器が却下するプログラムは、VMを得ることはありません。Vm::newErr(VmError::VerifyError(message))を返し、メッセージはWHY_REJECTED.mdに書いてあるとおりのものです。その時点でスクリプトコードは1命令も走っていません。ホストはプログラムが受け入れられないことを、実行の途中ではなく、構築の時点で知ります。Vm::newが、第5部の保証が強制される場所です。返す前に構造検証器と資源境界検証器を走らせます。検証器が却下するプログラムは、VMを得ることはありません。Vm::newErr(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&[]で呼びます。原子的モジュールは最後まで走り、callVmState::Finished(value)を返して、戻り値を運びます。callは引数のスライスを取ります。パラメータを取らないfn main&[]で呼びます。原子的モジュールは最後まで走り、callVmState::Finished(value)を返して、戻り値を運びます。

yieldloopモジュールは最初のcallでは終わりません。代わりにVmState::Yieldedを返し、動かすには再開プロトコルが必要です。第34章でそれを扱います。多くの本物のスクリプトに必要なネイティブ関数を先に扱います、次の章で。yieldloopモジュールは最初のcallでは終わりません。代わりにVmState::Yieldedを返し、動かすには再開プロトコルが必要です。第34章でそれを扱います。多くの本物のスクリプトに必要なネイティブ関数を先に扱います、次の章で。

この章のまとめ

  • VMの構築は4段階 — tokenizeparsecompileVm::new、です。
  • Vm::newが検証を行います。却下されるプログラムはここで失敗し、コードは走りません。Vm::newが検証を行います。却下されるプログラムはここで失敗し、コードは走りません。
  • VMはアリーナを借り、アリーナはVMより長く生きなければなりません。
  • call(&[])は原子的モジュールを動かしてVmState::Finishedを返します。

次の章ではスクリプトが呼ぶホスト関数を登録します。