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

第34章. ホスト側のコルーチンプロトコル

この章の目的

この章を読み終える頃には、ホストからyieldやloopスクリプトを動かせるようになり、実行時エラーから回復できるようになります。この章を読み終える頃には、ホストからyieldやloopスクリプトを動かせるようになり、実行時エラーから回復できるようになります。

call, resume, VmState

ホストはVMに対して2つの入口を持ちます。call(&[Value])が実行を始め、resume(Value)がyieldのあとに続けます。両方ともResult<VmState, VmError>を返し、VmStateは3つのヴァリアントを持ちます。ホストはVMに対して2つの入口を持ちます。call(&[Value])が実行を始め、resume(Value)がyieldのあとに続けます。両方ともResult<VmState, VmError>を返し、VmStateは3つのヴァリアントを持ちます。

#![allow(unused)]
fn main() {
pub enum VmState {
    Finished(Value),
    Yielded(Value),
    Reset,
}
}

これらは第15章の3つの関数カテゴリに対応します。

  • 原子的なfnスクリプトは最後まで走ります — callがFinished(value)を返します。原子的なfnスクリプトは最後まで走ります — callがFinished(value)を返します。
  • yieldスクリプトは値を渡します — callがYielded(value)を返し、ホストはresume(input)で続けます。yieldスクリプトは値を渡します — callがYielded(value)を返し、ホストはresume(input)で続けます。
  • loopスクリプトはサイクルごとにyieldし、本体の終わりでリセットします。callがYieldedを返し、resumeが次のyieldまで動かし、本体が1周完了すると次の呼び出しがResetを返します。loopスクリプトはサイクルごとにyieldし、本体の終わりでリセットします。callがYieldedを返し、resumeが次のyieldまで動かし、本体が1周完了すると次の呼び出しがResetを返します。

ドライブループ

ホストはVmStateに対するmatchでyieldするスクリプトを動かします。

#![allow(unused)]
fn main() {
let mut state = vm.call(&[Value::Int(seed)])?;
loop {
    match state {
        VmState::Yielded(out) => {
            let reply = host_response(&out);
            state = vm.resume(reply)?;
        }
        VmState::Reset => {
            state = vm.resume(Value::Int(next_input))?;
        }
        VmState::Finished(value) => {
            handle_result(value);
            break;
        }
    }
}
}

ピアノロールのティックループはこのパターンです。16分音符ティックごとに1回、現在のティック番号でresumeを呼び、スクリプトが1サイクル走ってyieldし、ホストは次のティック境界まで眠ります。スクリプトの本体が1サイクル完了すると、状態はResetになります。これがホットスワップが起こりうる境界、第37章の話題です。ピアノロールのティックループはこのパターンです。16分音符ティックごとに1回、現在のティック番号でresumeを呼び、スクリプトが1サイクル走ってyieldし、ホストは次のティック境界まで眠ります。スクリプトの本体が1サイクル完了すると、状態はResetになります。これがホットスワップが起こりうる境界、第37章の話題です。

対話の型

resumeに渡す値とYieldedが運ぶ値は、第16章で導入したスクリプトの「対話」の両半分です。ホストとスクリプトはこれら2つの型に同意していなければなりません。同意はRustコンパイラには確認されません。どちらも実行時のValue列挙型として運ばれるからです。スクリプトが期待する型の再開値を渡すのは、ホスト作者の責任です。resumeに渡す値とYieldedが運ぶ値は、第16章で導入したスクリプトの「対話」の両半分です。ホストとスクリプトはこれら2つの型に同意していなければなりません。同意はRustコンパイラには確認されません。どちらも実行時のValue列挙型として運ばれるからです。スクリプトが期待する型の再開値を渡すのは、ホスト作者の責任です。

エラーからの回復

callやresumeからの実行時エラーはErr(VmError)を返します。VMは中間状態に残されます。ホストには2つの選択肢があります。callやresumeからの実行時エラーはErr(VmError)を返します。VMは中間状態に残されます。ホストには2つの選択肢があります。

  • VMを捨てて作り直す。アリーナに対して新しいVMを構築すると、アリーナがリセットされます。VMを捨てて作り直す。アリーナに対して新しいVMを構築すると、アリーナがリセットされます。
  • vm.reset_after_error()を呼ぶ。これがオペランドスタック、コールフレーム、アリーナをクリアしつつ、データセグメントは保持します。vm.reset_after_error()を呼ぶ。これがオペランドスタック、コールフレーム、アリーナをクリアしつつ、データセグメントは保持します。
#![allow(unused)]
fn main() {
match vm.call(&[arg]) {
    Ok(state) => handle_state(state),
    Err(VmError::TypeError(msg)) => {
        eprintln!("script error: {}", msg);
        vm.reset_after_error();
    }
    Err(other) => return Err(other.into()),
}
}

VmErrorは実行時の状態を列挙しています — StackUnderflow、TypeError、DivisionByZero、IndexOutOfBounds、FieldNotFound、NoMatch、NativeError、InvalidBytecode、Trap、VerifyError、LoadError。このうち、VerifyErrorとLoadErrorは構築のとき、スクリプトコードが走る前に発生します。それ以外は実行中に発生します。VmErrorは実行時の状態を列挙しています — StackUnderflow、TypeError、DivisionByZero、IndexOutOfBounds、FieldNotFound、NoMatch、NativeError、InvalidBytecode、Trap、VerifyError、LoadError。このうち、VerifyErrorとLoadErrorは構築のとき、スクリプトコードが走る前に発生します。それ以外は実行中に発生します。

この章のまとめ

  • callがVMを始動し、resumeがyieldのあとに続けます。
  • VmStateはFinished、Yielded、Resetで、3つの関数カテゴリに対応します。VmStateはFinished、Yielded、Resetで、3つの関数カテゴリに対応します。
  • ホストはyieldスクリプトをmatch-then-resumeループで動かします。
  • 対話の型を守るのはホストの責任です。
  • reset_after_errorはデータセグメントを保持しながらVMを回復します。

次の章ではアリーナの大きさを決めます。