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章. ホスト側のコルーチンプロトコル

この章の目的

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

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

ドライブループ

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

#![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列挙型として運ばれるからです。スクリプトが期待する型の再開値を渡すのは、ホスト作者の責任です。

エラーからの回復

callresumeからの実行時エラーはErr(VmError)を返します。VMは中間状態に残されます。ホストには2つの選択肢があります。callresumeからの実行時エラーは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は実行時の状態を列挙しています — StackUnderflowTypeErrorDivisionByZeroIndexOutOfBoundsFieldNotFoundNoMatchNativeErrorInvalidBytecodeTrapVerifyErrorLoadError。このうち、VerifyErrorLoadErrorは構築のとき、スクリプトコードが走る前に発生します。それ以外は実行中に発生します。VmErrorは実行時の状態を列挙しています — StackUnderflowTypeErrorDivisionByZeroIndexOutOfBoundsFieldNotFoundNoMatchNativeErrorInvalidBytecodeTrapVerifyErrorLoadError。このうち、VerifyErrorLoadErrorは構築のとき、スクリプトコードが走る前に発生します。それ以外は実行中に発生します。

この章のまとめ

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

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