kobako 0.12.1 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +11 -0
  4. data/Cargo.lock +15 -2
  5. data/Cargo.toml +6 -2
  6. data/README.md +1 -1
  7. data/crates/kobako-runtime/CHANGELOG.md +8 -0
  8. data/crates/kobako-runtime/Cargo.toml +23 -0
  9. data/crates/kobako-runtime/README.md +34 -0
  10. data/crates/kobako-runtime/src/dispatch.rs +22 -0
  11. data/crates/kobako-runtime/src/error.rs +64 -0
  12. data/crates/kobako-runtime/src/lib.rs +16 -0
  13. data/crates/kobako-runtime/src/runtime.rs +50 -0
  14. data/crates/kobako-runtime/src/snapshot.rs +46 -0
  15. data/crates/kobako-runtime/src/yielder.rs +22 -0
  16. data/crates/kobako-wasmtime/CHANGELOG.md +8 -0
  17. data/crates/kobako-wasmtime/Cargo.toml +62 -0
  18. data/crates/kobako-wasmtime/README.md +32 -0
  19. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/ambient.rs +3 -3
  20. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/cache.rs +30 -41
  21. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/capture.rs +2 -2
  22. data/crates/kobako-wasmtime/src/config.rs +25 -0
  23. data/crates/kobako-wasmtime/src/dispatch.rs +110 -0
  24. data/crates/kobako-wasmtime/src/driver.rs +285 -0
  25. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/exports.rs +5 -6
  26. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/frames.rs +70 -82
  27. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/guest_mem.rs +38 -15
  28. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/instance_pre.rs +13 -21
  29. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/invocation.rs +54 -49
  30. data/crates/kobako-wasmtime/src/lib.rs +47 -0
  31. data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/trap.rs +29 -35
  32. data/data/kobako.wasm +0 -0
  33. data/ext/kobako/Cargo.toml +9 -32
  34. data/ext/kobako/src/runtime/bridge.rs +150 -0
  35. data/ext/kobako/src/runtime/errors.rs +45 -13
  36. data/ext/kobako/src/runtime.rs +156 -406
  37. data/ext/kobako/src/snapshot.rs +27 -62
  38. data/lib/kobako/catalog/handles.rb +3 -3
  39. data/lib/kobako/catalog/namespaces.rb +4 -0
  40. data/lib/kobako/catalog/snippets.rb +4 -0
  41. data/lib/kobako/codec/encoder.rb +5 -1
  42. data/lib/kobako/codec/factory.rb +41 -13
  43. data/lib/kobako/codec/handle_walk.rb +4 -0
  44. data/lib/kobako/errors.rb +18 -16
  45. data/lib/kobako/sandbox.rb +20 -18
  46. data/lib/kobako/sandbox_options.rb +25 -9
  47. data/lib/kobako/snapshot.rb +7 -13
  48. data/lib/kobako/transport/dispatcher.rb +2 -2
  49. data/lib/kobako/transport/response.rb +14 -14
  50. data/lib/kobako/transport/run.rb +2 -6
  51. data/lib/kobako/transport/yield.rb +1 -1
  52. data/lib/kobako/transport/yielder.rb +2 -2
  53. data/lib/kobako/version.rb +1 -1
  54. data/release-please-config.json +48 -3
  55. data/sig/kobako/codec/factory.rbs +3 -0
  56. data/sig/kobako/errors.rbs +7 -14
  57. data/sig/kobako/runtime.rbs +8 -3
  58. data/sig/kobako/sandbox.rbs +2 -2
  59. data/sig/kobako/sandbox_options.rbs +4 -2
  60. data/sig/kobako/snapshot.rbs +0 -3
  61. data/sig/kobako/transport/dispatcher.rbs +1 -1
  62. data/sig/kobako/transport/run.rbs +2 -2
  63. data/sig/kobako/transport/yielder.rbs +2 -2
  64. data/sig/kobako/transport.rbs +8 -0
  65. metadata +27 -12
  66. data/ext/kobako/src/runtime/config.rs +0 -25
  67. data/ext/kobako/src/runtime/dispatch.rs +0 -211
@@ -1,64 +1,60 @@
1
- //! Per-invocation byte-shuttle between Ruby and guest linear memory: it
2
- //! resolves the required `memory` / ABI-export handles, writes the `#run`
3
- //! envelope into a freshly allocated guest buffer, builds the stdin frame
4
- //! stream plus stdout / stderr capture pipes for the WASI context, and
5
- //! reads the OUTCOME_BUFFER back out. The ext owns no wire codec — these
6
- //! helpers move raw bytes; Ruby decodes them.
7
-
8
- use magnus::{Error as MagnusError, RString, Ruby};
1
+ //! Per-invocation byte-shuttle between the host and guest linear memory:
2
+ //! it resolves the required `memory` / ABI-export handles, writes the
3
+ //! `#run` envelope into a freshly allocated guest buffer, builds the
4
+ //! stdin frame stream plus stdout / stderr capture pipes for the WASI
5
+ //! context, and reads the OUTCOME_BUFFER back out. The driver owns no
6
+ //! wire codec — these helpers move raw bytes; the frontend decodes them.
7
+
9
8
  use wasmtime::{AsContextMut, Memory, Store as WtStore, TypedFunc};
10
9
  use wasmtime_wasi::p2::pipe::{MemoryInputPipe, MemoryOutputPipe};
11
10
  use wasmtime_wasi::WasiCtxBuilder;
12
11
 
13
- use super::config::Config;
14
- use super::exports::Exports;
15
- use super::invocation::Invocation;
16
- use super::rstring_to_vec;
17
- use super::{ambient, capture, errors, guest_mem};
18
-
19
- /// Return the resolved `memory` export handle, or raise
20
- /// `Kobako::TrapError` when the loaded module exports no linear
21
- /// memory — the "not a Kobako-shaped runtime" failure mode
22
- /// (`SANDBOX_RUNTIME_NOT_KOBAKO`).
23
- fn require_memory(ruby: &Ruby, exports: &Exports) -> Result<Memory, MagnusError> {
12
+ use crate::config::Config;
13
+ use crate::exports::Exports;
14
+ use crate::invocation::Invocation;
15
+ use crate::{ambient, capture, guest_mem};
16
+ use kobako_runtime::error::{Error, SetupError, Trap};
17
+
18
+ /// Return the resolved `memory` export handle, or a `Trap` when the loaded
19
+ /// module exports no linear memory — the "not a Kobako-shaped runtime"
20
+ /// failure mode (`SANDBOX_RUNTIME_NOT_KOBAKO`).
21
+ fn require_memory(exports: &Exports) -> Result<Memory, Trap> {
24
22
  exports
25
23
  .memory
26
- .ok_or_else(|| errors::trap_err(ruby, SANDBOX_RUNTIME_NOT_KOBAKO))
24
+ .ok_or_else(|| Trap::Other(SANDBOX_RUNTIME_NOT_KOBAKO.to_string()))
27
25
  }
28
26
 
29
27
  /// Allocate a `len`-byte buffer in guest linear memory via
30
28
  /// `__kobako_alloc`, copy `envelope` into it, and return `(ptr, len)`
31
29
  /// as `i32` values matching the `__kobako_run(env_ptr, env_len)` ABI.
32
- /// Raises `Kobako::TrapError` when the allocation hook is missing or
33
- /// itself traps, and `Kobako::SandboxError` when the hook runs but
34
- /// cannot reserve the buffer (`__kobako_alloc` returns 0) an
35
- /// intact runtime, not an engine fault.
30
+ /// Returns a `Trap` when the allocation hook is missing or itself traps
31
+ /// (an engine fault), and a runtime-intact `SetupError` when the hook runs
32
+ /// but cannot reserve the buffer (`__kobako_alloc` returns 0). The ext
33
+ /// boundary maps these to `Kobako::TrapError` / `Kobako::SandboxError`.
36
34
  pub(crate) fn write_envelope(
37
- ruby: &Ruby,
38
35
  store: &mut WtStore<Invocation>,
39
36
  exports: &Exports,
40
- envelope: RString,
41
- ) -> Result<(i32, i32), MagnusError> {
42
- let bytes = rstring_to_vec(envelope);
43
- let len_i32 =
44
- guest_mem::checked_payload_len(bytes.len()).map_err(|msg| errors::trap_err(ruby, msg))?;
37
+ envelope: &[u8],
38
+ ) -> Result<(i32, i32), Error> {
39
+ let len_i32 = guest_mem::checked_payload_len(envelope.len())
40
+ .map_err(|msg| Trap::Other(msg.to_string()))?;
45
41
 
46
- let alloc = require_export(ruby, exports.alloc.as_ref())?;
47
- let memory = require_memory(ruby, exports)?;
42
+ let alloc = require_export(exports.alloc.as_ref())?;
43
+ let memory = require_memory(exports)?;
48
44
 
49
45
  let ptr = alloc
50
- .call(store.as_context_mut(), bytes.len() as u32)
51
- .map_err(|e| errors::trap_err(ruby, format!("failed to allocate input buffer: {}", e)))?;
46
+ .call(store.as_context_mut(), envelope.len() as u32)
47
+ .map_err(|e| Trap::Other(format!("failed to allocate input buffer: {e}")))?;
52
48
  if ptr == 0 {
53
- return Err(errors::sandbox_err(
54
- ruby,
55
- "could not allocate input buffer (out of memory)",
56
- ));
49
+ return Err(SetupError::Intact(
50
+ "could not allocate input buffer (out of memory)".to_string(),
51
+ )
52
+ .into());
57
53
  }
58
54
  let data = memory.data_mut(store.as_context_mut());
59
- let range = guest_mem::guest_buffer_range(ptr as usize, bytes.len(), data.len())
60
- .map_err(|msg| errors::trap_err(ruby, msg))?;
61
- data[range].copy_from_slice(&bytes);
55
+ let range = guest_mem::guest_buffer_range(ptr as usize, envelope.len(), data.len())
56
+ .map_err(|msg| Trap::Other(msg.to_string()))?;
57
+ data[range].copy_from_slice(envelope);
62
58
 
63
59
  Ok((ptr as i32, len_i32))
64
60
  }
@@ -73,24 +69,23 @@ pub(crate) fn write_envelope(
73
69
  /// `capture::clip_capture` can distinguish "wrote exactly cap bytes"
74
70
  /// from "exceeded cap"; uncapped channels fall back to `usize::MAX` and
75
71
  /// rely on `memory_limit` for the real ceiling.
76
- /// Raises `Kobako::TrapError` when any frame exceeds the 16 MiB cap that
77
- /// keeps its `u32` length prefix from wrapping.
72
+ /// Returns a `Trap` when any frame exceeds the 16 MiB cap that keeps its
73
+ /// `u32` length prefix from wrapping (boundary → `Kobako::TrapError`).
78
74
  pub(crate) fn install_wasi_frames(
79
75
  store: &mut WtStore<Invocation>,
80
76
  config: &Config,
81
- frames: &[Vec<u8>],
82
- ) -> Result<(), MagnusError> {
83
- let ruby = Ruby::get().expect("Ruby thread");
77
+ frames: &[&[u8]],
78
+ ) -> Result<(), Trap> {
84
79
  // Every frame carries the same 16 MiB cap as the `#run` envelope
85
80
  // (`write_envelope`): the length prefix is a `u32`, so a frame past
86
81
  // the cap would silently wrap and corrupt the stdin frame stream.
87
- for frame in frames {
88
- guest_mem::checked_payload_len(frame.len()).map_err(|msg| errors::trap_err(&ruby, msg))?;
82
+ for &frame in frames {
83
+ guest_mem::checked_payload_len(frame.len()).map_err(|msg| Trap::Other(msg.to_string()))?;
89
84
  }
90
85
 
91
- let total: usize = frames.iter().map(|f| 4 + f.len()).sum();
86
+ let total: usize = frames.iter().map(|&f| 4 + f.len()).sum();
92
87
  let mut stdin_content: Vec<u8> = Vec::with_capacity(total);
93
- for frame in frames {
88
+ for &frame in frames {
94
89
  stdin_content.extend_from_slice(&(frame.len() as u32).to_be_bytes());
95
90
  stdin_content.extend_from_slice(frame);
96
91
  }
@@ -117,37 +112,31 @@ pub(crate) fn install_wasi_frames(
117
112
  }
118
113
 
119
114
  /// Invoke `__kobako_take_outcome`, decode the packed `(ptr<<32)|len`
120
- /// u64, and copy the OUTCOME_BUFFER slice out of guest memory. Raises
121
- /// `Kobako::TrapError` when the export is missing, `len` exceeds the
122
- /// 16 MiB single-dispatch cap, the `ptr`/`len` arithmetic overflows,
123
- /// the slice falls outside live memory, or the `memory` export itself
124
- /// is absent.
115
+ /// u64, and copy the OUTCOME_BUFFER slice out of guest memory. Returns a
116
+ /// `Trap` (boundary → `Kobako::TrapError`) when the export is missing,
117
+ /// `len` exceeds the 16 MiB single-dispatch cap, the `ptr`/`len`
118
+ /// arithmetic overflows, the slice falls outside live memory, or the
119
+ /// `memory` export itself is absent.
125
120
  pub(crate) fn fetch_outcome_bytes(
126
- ruby: &Ruby,
127
121
  store: &mut WtStore<Invocation>,
128
122
  exports: &Exports,
129
- ) -> Result<Vec<u8>, MagnusError> {
130
- let take = require_export(ruby, exports.take_outcome.as_ref())?;
131
- let mem = require_memory(ruby, exports)?;
123
+ ) -> Result<Vec<u8>, Trap> {
124
+ let take = require_export(exports.take_outcome.as_ref())?;
125
+ let mem = require_memory(exports)?;
132
126
 
133
127
  let packed = take
134
128
  .call(store.as_context_mut(), ())
135
- .map_err(|e| errors::trap_err(ruby, format!("failed to read the Sandbox result: {}", e)))?;
129
+ .map_err(|e| Trap::Other(format!("failed to read the Sandbox result: {e}")))?;
136
130
  let (ptr, len) = guest_mem::unpack_outcome_packed(packed);
137
131
  if len > guest_mem::MAX_DISPATCH_PAYLOAD {
138
- return Err(errors::trap_err(
139
- ruby,
140
- "result payload exceeds the 16 MiB limit",
132
+ return Err(Trap::Other(
133
+ "result payload exceeds the 16 MiB limit".to_string(),
141
134
  ));
142
135
  }
143
136
 
144
137
  let data = mem.data(store.as_context_mut());
145
- let range = guest_mem::guest_buffer_range(ptr, len, data.len()).map_err(|msg| {
146
- errors::trap_err(
147
- ruby,
148
- format!("the Sandbox result is out of bounds: {}", msg),
149
- )
150
- })?;
138
+ let range = guest_mem::guest_buffer_range(ptr, len, data.len())
139
+ .map_err(|msg| Trap::Other(format!("the Sandbox result is out of bounds: {msg}")))?;
151
140
  Ok(data[range].to_vec())
152
141
  }
153
142
 
@@ -168,21 +157,20 @@ const SANDBOX_RUNTIME_MISSING_HOOKS: &str = "Sandbox runtime is missing required
168
157
  const SANDBOX_RUNTIME_NOT_KOBAKO: &str =
169
158
  "the loaded Wasm module is not a Kobako-compatible runtime";
170
159
 
171
- /// Return the resolved `TypedFunc` for an ABI export, or raise
172
- /// `Kobako::TrapError` when the option is `None`. Both run-path
173
- /// methods (`#eval`, `#run`) plus the `build_snapshot` readout that
174
- /// drains `OUTCOME_BUFFER` share the same "missing export → Ruby
175
- /// error" boilerplate; this helper collapses those sites onto one
176
- /// safe entry. The user-facing message is intentionally export-
177
- /// agnostic (see `SANDBOX_RUNTIME_MISSING_HOOKS`) — the ABI symbol
178
- /// name is not actionable to callers, so it is not threaded in.
179
- pub(crate) fn require_export<'a, Params, Results>(
180
- ruby: &Ruby,
181
- export: Option<&'a TypedFunc<Params, Results>>,
182
- ) -> Result<&'a TypedFunc<Params, Results>, MagnusError>
160
+ /// Return the resolved `TypedFunc` for an ABI export, or a `Trap`
161
+ /// (boundary → `Kobako::TrapError`) when the option is `None`. Both
162
+ /// run-path methods (`#eval`, `#run`) plus the `build_snapshot` readout
163
+ /// that drains `OUTCOME_BUFFER` share the same "missing export" handling;
164
+ /// this helper collapses those sites onto one safe entry. The user-facing
165
+ /// message is intentionally export-agnostic (see
166
+ /// `SANDBOX_RUNTIME_MISSING_HOOKS`) — the ABI symbol name is not
167
+ /// actionable to callers, so it is not threaded in.
168
+ pub(crate) fn require_export<Params, Results>(
169
+ export: Option<&TypedFunc<Params, Results>>,
170
+ ) -> Result<&TypedFunc<Params, Results>, Trap>
183
171
  where
184
172
  Params: wasmtime::WasmParams,
185
173
  Results: wasmtime::WasmResults,
186
174
  {
187
- export.ok_or_else(|| errors::trap_err(ruby, SANDBOX_RUNTIME_MISSING_HOOKS))
175
+ export.ok_or_else(|| Trap::Other(SANDBOX_RUNTIME_MISSING_HOOKS.to_string()))
188
176
  }
@@ -2,16 +2,39 @@
2
2
  //!
3
3
  //! Both directions of a host↔guest buffer handoff that run *inside* a wasm
4
4
  //! callback frame go through here: writing the transport Response back
5
- //! (`super::dispatch`) and shipping block-yield args into the guest
5
+ //! (`crate::dispatch`) and shipping block-yield args into the guest
6
6
  //! (`drive_yield`, below) performed the same `__kobako_alloc` +
7
7
  //! bounds-check + `memory.write` dance with only the diagnostic strings
8
- //! differing. The Store-based write path (`Runtime::write_envelope`) is a
9
- //! separate beast — it holds the cached `Store`, not a `Caller` — and stays
10
- //! in `runtime.rs`.
8
+ //! differing. The Store-based write path (`frames::write_envelope`) is a
9
+ //! separate beast — it holds the per-invocation `Store`, not a `Caller` —
10
+ //! and stays in `frames`.
11
11
 
12
12
  use wasmtime::{Caller, Extern, Memory};
13
13
 
14
- use super::invocation::Invocation;
14
+ use crate::invocation::Invocation;
15
+ use kobako_runtime::error::Trap;
16
+ use kobako_runtime::yielder::Yielder;
17
+
18
+ /// The wasmtime-backed `Yielder` (`kobako_runtime::yielder`): a
19
+ /// frame-scoped wrapper over the dispatch `Caller` that drives a block-yield
20
+ /// round-trip through `drive_yield`. Built per `__kobako_dispatch` frame and
21
+ /// handed to the dispatch handler, so nested dispatch frames each
22
+ /// carry their own and stack on the Rust call stack with no shared slot.
23
+ pub(crate) struct CallerYielder<'a, 'c> {
24
+ caller: &'a mut Caller<'c, Invocation>,
25
+ }
26
+
27
+ impl<'a, 'c> CallerYielder<'a, 'c> {
28
+ pub(crate) fn new(caller: &'a mut Caller<'c, Invocation>) -> Self {
29
+ Self { caller }
30
+ }
31
+ }
32
+
33
+ impl Yielder for CallerYielder<'_, '_> {
34
+ fn yield_block(&mut self, args: &[u8]) -> Result<Vec<u8>, Trap> {
35
+ drive_yield(self.caller, args).map_err(|msg| Trap::Other(msg.to_string()))
36
+ }
37
+ }
15
38
 
16
39
  /// User-facing reason when a required guest export (the allocation or
17
40
  /// block-yield hook) is absent or has the wrong signature — the loaded
@@ -37,7 +60,7 @@ fn memory_export(caller: &mut Caller<'_, Invocation>) -> Result<Memory, &'static
37
60
  /// copy `bytes` in. Returns the guest pointer. Every failure path carries a
38
61
  /// `&'static str` reason so the caller can surface a diagnostic rather than
39
62
  /// a silent fault.
40
- pub(super) fn alloc_and_write(
63
+ pub(crate) fn alloc_and_write(
41
64
  caller: &mut Caller<'_, Invocation>,
42
65
  bytes: &[u8],
43
66
  ) -> Result<u32, &'static str> {
@@ -67,7 +90,7 @@ pub(super) fn alloc_and_write(
67
90
  /// diagnostic instead of a lumped one; a guest-claimed length past the
68
91
  /// 16 MiB cap is a wire violation that names the cap (the caller walks
69
92
  /// the trap path on any `Err`).
70
- pub(super) fn read(
93
+ pub(crate) fn read(
71
94
  caller: &mut Caller<'_, Invocation>,
72
95
  ptr: i32,
73
96
  len: i32,
@@ -93,15 +116,15 @@ pub(super) fn read(
93
116
  /// transfer larger than this is a wire violation — the Host Gem walks
94
117
  /// the trap path rather than allocate or copy the buffer. Held as a
95
118
  /// constant for now; a future SPEC anchor may let the Host App raise it.
96
- pub(super) const MAX_DISPATCH_PAYLOAD: usize = 16 * 1024 * 1024;
119
+ pub(crate) const MAX_DISPATCH_PAYLOAD: usize = 16 * 1024 * 1024;
97
120
 
98
121
  /// Validate a payload length against `MAX_DISPATCH_PAYLOAD` and narrow it
99
122
  /// to `i32` — the signed wasm ABI width for the guest buffer parameters.
100
123
  /// Every host *write* boundary (`alloc_and_write`, `drive_yield`,
101
- /// `Runtime::write_envelope`) routes its length through here so the
124
+ /// `frames::write_envelope`) routes its length through here so the
102
125
  /// wire-violation reason is uniform; the *read* boundaries compare
103
126
  /// against `MAX_DISPATCH_PAYLOAD` directly.
104
- pub(super) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
127
+ pub(crate) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
105
128
  if len > MAX_DISPATCH_PAYLOAD {
106
129
  return Err("payload exceeds the 16 MiB limit");
107
130
  }
@@ -111,9 +134,9 @@ pub(super) fn checked_payload_len(len: usize) -> Result<i32, &'static str> {
111
134
 
112
135
  /// Compute the half-open range `[ptr, ptr + len)` for a guest linear-memory
113
136
  /// copy, validating that the arithmetic does not overflow and the range
114
- /// fits inside `mem_size`. Shared by `Runtime::write_envelope` (write side)
115
- /// and `Runtime::fetch_outcome_bytes` (read side).
116
- pub(super) fn guest_buffer_range(
137
+ /// fits inside `mem_size`. Shared by `frames::write_envelope` (write side)
138
+ /// and `frames::fetch_outcome_bytes` (read side).
139
+ pub(crate) fn guest_buffer_range(
117
140
  ptr: usize,
118
141
  len: usize,
119
142
  mem_size: usize,
@@ -128,7 +151,7 @@ pub(super) fn guest_buffer_range(
128
151
  /// Unpack the `(ptr, len)` u64 returned by `__kobako_take_outcome`:
129
152
  /// high 32 bits = ptr, low 32 bits = len. Mirrors the guest-side
130
153
  /// `unpack_u64` in `wasm/kobako-core/src/abi.rs`.
131
- pub(super) fn unpack_outcome_packed(packed: u64) -> (usize, usize) {
154
+ pub(crate) fn unpack_outcome_packed(packed: u64) -> (usize, usize) {
132
155
  let ptr = (packed >> 32) as u32 as usize;
133
156
  let len = packed as u32 as usize;
134
157
  (ptr, len)
@@ -139,7 +162,7 @@ pub(super) fn unpack_outcome_packed(packed: u64) -> (usize, usize) {
139
162
  /// the guest produced and return it. Mirrors `dispatch::write_response`'s
140
163
  /// allocator dance but in the opposite direction — the host is the
141
164
  /// *initiator* of this round-trip, not the responder.
142
- pub(super) fn drive_yield(
165
+ pub(crate) fn drive_yield(
143
166
  caller: &mut Caller<'_, Invocation>,
144
167
  args: &[u8],
145
168
  ) -> Result<Vec<u8>, &'static str> {
@@ -6,23 +6,22 @@
6
6
  //! same Guest Binary — both host closures read all their state from
7
7
  //! the `Invocation` inside the calling Store, never from the Runtime.
8
8
  //! Caching the resolved `InstancePre` per path leaves only the
9
- //! `instantiate` call itself on the `Runtime.from_path` hot path.
9
+ //! `instantiate` call itself on the `Driver::new` hot path.
10
10
  //!
11
- //! Concurrency: see `super::cache` — under Ruby's GVL the Mutex serves
11
+ //! Concurrency: see `crate::cache` — under Ruby's GVL the Mutex serves
12
12
  //! `Sync` bounds rather than real contention.
13
13
 
14
14
  use std::collections::HashMap;
15
15
  use std::path::{Path, PathBuf};
16
16
  use std::sync::{Mutex, OnceLock};
17
17
 
18
- use magnus::{Error as MagnusError, Ruby};
19
18
  use wasmtime::{Caller, InstancePre, Linker};
20
19
  use wasmtime_wasi::p1;
21
20
 
22
- use super::cache::{cached_module, shared_engine};
23
- use super::errors::setup_err;
24
- use super::invocation::Invocation;
25
- use super::{dispatch, trap};
21
+ use crate::cache::{cached_module, shared_engine};
22
+ use crate::invocation::Invocation;
23
+ use crate::{dispatch, trap};
24
+ use kobako_runtime::error::SetupError;
26
25
 
27
26
  static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocation>>>> =
28
27
  OnceLock::new();
@@ -30,8 +29,8 @@ static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocatio
30
29
  /// Look up `path` in the per-path `InstancePre` cache, wiring the
31
30
  /// Linker and resolving the Module's imports on a miss. Compilation
32
31
  /// faults surface through `cached_module`; import-resolution faults
33
- /// raise `Kobako::SetupError`.
34
- pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>, MagnusError> {
32
+ /// return a runtime-dead `SetupError` (boundary → `Kobako::SetupError`).
33
+ pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>, SetupError> {
35
34
  let cache = INSTANCE_PRE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
36
35
 
37
36
  if let Some(pre) = cache
@@ -45,10 +44,9 @@ pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>
45
44
 
46
45
  let module = cached_module(path)?;
47
46
  let linker = build_linker()?;
48
- let ruby = Ruby::get().expect("Ruby thread");
49
47
  let pre = linker
50
48
  .instantiate_pre(&module)
51
- .map_err(|e| trap::instantiate_err(&ruby, e))?;
49
+ .map_err(trap::instantiate_err)?;
52
50
  cache
53
51
  .lock()
54
52
  .expect("instance_pre cache mutex poisoned")
@@ -58,17 +56,16 @@ pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>
58
56
 
59
57
  /// Build the host-import `Linker` every Guest Binary instantiates
60
58
  /// against.
61
- fn build_linker() -> Result<Linker<Invocation>, MagnusError> {
62
- let ruby = Ruby::get().expect("Ruby thread");
59
+ fn build_linker() -> Result<Linker<Invocation>, SetupError> {
63
60
  let mut linker: Linker<Invocation> = Linker::new(shared_engine()?);
64
61
 
65
62
  // Wire the wasmtime-wasi preview1 WASI imports. Routes guest fd 1/2
66
63
  // to the MemoryOutputPipes set up before each run via
67
- // `Runtime::eval`. The closure pulls a `&mut WasiP1Ctx` out of
64
+ // `Driver::invoke`. The closure pulls a `&mut WasiP1Ctx` out of
68
65
  // Invocation; the panic semantics live inside `Invocation::wasi_mut`
69
66
  // so the wiring stays honest about its precondition.
70
67
  p1::add_to_linker_sync(&mut linker, |state: &mut Invocation| state.wasi_mut())
71
- .map_err(|e| setup_err(&ruby, format!("failed to set up the WASI runtime: {}", e)))?;
68
+ .map_err(|e| SetupError::Dead(format!("failed to set up the WASI runtime: {e}")))?;
72
69
 
73
70
  // `__kobako_dispatch` host import. Signature per docs/wire-codec.md
74
71
  // § ABI Signatures:
@@ -87,12 +84,7 @@ fn build_linker() -> Result<Linker<Invocation>, MagnusError> {
87
84
  dispatch::handle(&mut caller, req_ptr, req_len)
88
85
  },
89
86
  )
90
- .map_err(|e| {
91
- setup_err(
92
- &ruby,
93
- format!("failed to set up the host callback bridge: {}", e),
94
- )
95
- })?;
87
+ .map_err(|e| SetupError::Dead(format!("failed to set up the host callback bridge: {e}")))?;
96
88
 
97
89
  Ok(linker)
98
90
  }