@wasm-oj/server 0.2.0

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/THIRD_PARTY_NOTICES.md +302 -0
  4. package/crates/runtime-core/Cargo.lock +5099 -0
  5. package/crates/runtime-core/Cargo.toml +66 -0
  6. package/crates/runtime-core/README.md +47 -0
  7. package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
  8. package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
  9. package/crates/runtime-core/src/capabilities.rs +118 -0
  10. package/crates/runtime-core/src/compiler.rs +658 -0
  11. package/crates/runtime-core/src/contract.rs +5 -0
  12. package/crates/runtime-core/src/deterministic.rs +1051 -0
  13. package/crates/runtime-core/src/error.rs +58 -0
  14. package/crates/runtime-core/src/filesystem.rs +547 -0
  15. package/crates/runtime-core/src/filesystem_quota.rs +167 -0
  16. package/crates/runtime-core/src/go_compiler_session.rs +297 -0
  17. package/crates/runtime-core/src/interactive.rs +1019 -0
  18. package/crates/runtime-core/src/judge_package.rs +1539 -0
  19. package/crates/runtime-core/src/lib.rs +98 -0
  20. package/crates/runtime-core/src/memory.rs +84 -0
  21. package/crates/runtime-core/src/meter.rs +549 -0
  22. package/crates/runtime-core/src/module_imports.rs +149 -0
  23. package/crates/runtime-core/src/module_policy.rs +714 -0
  24. package/crates/runtime-core/src/output.rs +204 -0
  25. package/crates/runtime-core/src/run/mod.rs +208 -0
  26. package/crates/runtime-core/src/run/native.rs +260 -0
  27. package/crates/runtime-core/src/run/web.rs +229 -0
  28. package/crates/runtime-core/src/run/web_runtime.rs +109 -0
  29. package/crates/runtime-core/src/types.rs +268 -0
  30. package/crates/runtime-core/src/web.rs +83 -0
  31. package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
  32. package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
  33. package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
  34. package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
  35. package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
  36. package/dist/go-stage.mjs +193 -0
  37. package/dist/index.d.ts +221 -0
  38. package/dist/index.js +4393 -0
  39. package/dist/java-stage.mjs +111 -0
  40. package/dist/python-stage.mjs +90 -0
  41. package/dist/rustc-stage.mjs +301 -0
  42. package/dist/server-build-stage.mjs +2564 -0
  43. package/dist/server-runner-stage.mjs +155 -0
  44. package/licenses/fflate-MIT.txt +21 -0
  45. package/licenses/runtime-core-dependencies.html +6253 -0
  46. package/licenses/runtime-core-dependencies.json +3041 -0
  47. package/licenses/wasmer-sdk-MIT.txt +21 -0
  48. package/licenses/wasmer-sdk-dependencies.html +6901 -0
  49. package/licenses/wasmer-sdk-dependencies.json +3013 -0
  50. package/package.json +70 -0
  51. package/rust-toolchain.toml +5 -0
  52. package/testdata/wojjdg02-v2-text.hex +1 -0
  53. package/vendor/shared-buffer/Cargo.toml +22 -0
  54. package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
  55. package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
  56. package/vendor/shared-buffer/README.md +34 -0
  57. package/vendor/shared-buffer/src/lib.rs +58 -0
  58. package/vendor/shared-buffer/src/mmap.rs +250 -0
  59. package/vendor/shared-buffer/src/owned.rs +389 -0
  60. package/vendor/virtual-fs/Cargo.toml +181 -0
  61. package/vendor/virtual-fs/LICENSE +25 -0
  62. package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
  63. package/vendor/virtual-fs/src/arc_file.rs +182 -0
  64. package/vendor/virtual-fs/src/arc_fs.rs +68 -0
  65. package/vendor/virtual-fs/src/buffer_file.rs +103 -0
  66. package/vendor/virtual-fs/src/builder.rs +232 -0
  67. package/vendor/virtual-fs/src/combine_file.rs +101 -0
  68. package/vendor/virtual-fs/src/cow_file.rs +345 -0
  69. package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
  70. package/vendor/virtual-fs/src/empty_fs.rs +81 -0
  71. package/vendor/virtual-fs/src/filesystems.rs +108 -0
  72. package/vendor/virtual-fs/src/host_fs.rs +1390 -0
  73. package/vendor/virtual-fs/src/lib.rs +782 -0
  74. package/vendor/virtual-fs/src/limiter.rs +252 -0
  75. package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
  76. package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
  77. package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
  78. package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
  79. package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
  80. package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
  81. package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
  82. package/vendor/virtual-fs/src/null_file.rs +87 -0
  83. package/vendor/virtual-fs/src/ops.rs +364 -0
  84. package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
  85. package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
  86. package/vendor/virtual-fs/src/pipe.rs +603 -0
  87. package/vendor/virtual-fs/src/random_file.rs +88 -0
  88. package/vendor/virtual-fs/src/special_file.rs +108 -0
  89. package/vendor/virtual-fs/src/static_file.rs +133 -0
  90. package/vendor/virtual-fs/src/static_fs.rs +460 -0
  91. package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
  92. package/vendor/virtual-fs/src/trace_fs.rs +258 -0
  93. package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
  94. package/vendor/virtual-fs/src/zero_file.rs +90 -0
@@ -0,0 +1,1051 @@
1
+ use crate::{
2
+ RunError,
3
+ module_policy::{
4
+ INTERACTIVE_WASIP1_DETERMINISTIC_NAMESPACE, INTERACTIVE_WASIX32_DETERMINISTIC_NAMESPACE,
5
+ INTERACTIVE_WASIX64_DETERMINISTIC_NAMESPACE,
6
+ },
7
+ types::DeterminismConfig,
8
+ };
9
+ use std::sync::{Arc, Mutex};
10
+ use wasmer::{
11
+ AsStoreMut, Extern, Function, FunctionEnv, FunctionEnvMut, Imports, Memory, Memory32, Memory64,
12
+ MemorySize, RuntimeError, Value, WasmPtr,
13
+ };
14
+ use wasmer_wasix::wasmer_wasix_types::wasi::{
15
+ Clockid, Errno, Event, EventUnion, Eventtype, Subclockflags, Subscription, SubscriptionUnion,
16
+ };
17
+
18
+ const FSTFLAGS_ATIM: u32 = 1 << 0;
19
+ const FSTFLAGS_ATIM_NOW: u32 = 1 << 1;
20
+ const FSTFLAGS_MTIM: u32 = 1 << 2;
21
+ const FSTFLAGS_MTIM_NOW: u32 = 1 << 3;
22
+
23
+ /// Compiler processes use one contract-fixed deterministic environment.
24
+ ///
25
+ /// Project execution determinism is intentionally excluded from build cache
26
+ /// identities, so no project-controlled seed or clock may reach a compiler.
27
+ pub(crate) const COMPILER_DETERMINISM: DeterminismConfig = DeterminismConfig {
28
+ random_seed: 0x5eed_1234,
29
+ realtime_epoch_ms: 946_684_800_000,
30
+ clock_step_ns: 1_000_000,
31
+ };
32
+
33
+ #[derive(Debug)]
34
+ struct SplitMix64 {
35
+ state: u64,
36
+ }
37
+
38
+ impl SplitMix64 {
39
+ fn next(&mut self) -> u64 {
40
+ let mut value = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
41
+ self.state = value;
42
+ value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
43
+ value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
44
+ value ^ (value >> 31)
45
+ }
46
+
47
+ fn fill(&mut self, output: &mut [u8]) {
48
+ for chunk in output.chunks_mut(8) {
49
+ let bytes = self.next().to_le_bytes();
50
+ chunk.copy_from_slice(&bytes[..chunk.len()]);
51
+ }
52
+ }
53
+ }
54
+
55
+ #[derive(Debug)]
56
+ struct VirtualClockState {
57
+ epoch_ns: u64,
58
+ step_ns: u64,
59
+ elapsed_ns: u64,
60
+ limit_ns: u64,
61
+ limit_exceeded: bool,
62
+ }
63
+
64
+ impl VirtualClockState {
65
+ fn supports(clock_id: u32) -> bool {
66
+ matches!(
67
+ clock_id,
68
+ value if value == Clockid::Realtime as u32
69
+ || value == Clockid::Monotonic as u32
70
+ || value == Clockid::ProcessCputimeId as u32
71
+ || value == Clockid::ThreadCputimeId as u32
72
+ )
73
+ }
74
+
75
+ fn value(&self, clock_id: u32) -> Result<u64, RuntimeError> {
76
+ let origin = if clock_id == Clockid::Realtime as u32 {
77
+ self.epoch_ns
78
+ } else {
79
+ 0
80
+ };
81
+ origin
82
+ .checked_add(self.elapsed_ns)
83
+ .ok_or_else(|| RuntimeError::new("WASM-OJ virtual clock overflowed"))
84
+ }
85
+
86
+ fn advance_to(&mut self, target_ns: u64) -> Result<(), RuntimeError> {
87
+ if target_ns <= self.elapsed_ns {
88
+ return Ok(());
89
+ }
90
+ if target_ns > self.limit_ns {
91
+ self.elapsed_ns = self.limit_ns;
92
+ self.limit_exceeded = true;
93
+ return Err(RuntimeError::new("WASM-OJ logical time budget exhausted"));
94
+ }
95
+ self.elapsed_ns = target_ns;
96
+ Ok(())
97
+ }
98
+ }
99
+
100
+ #[derive(Clone, Debug)]
101
+ pub(crate) struct VirtualClock {
102
+ state: Arc<Mutex<VirtualClockState>>,
103
+ }
104
+
105
+ impl VirtualClock {
106
+ pub(crate) fn new(config: &DeterminismConfig, logical_time_limit_ms: u64) -> Self {
107
+ Self {
108
+ state: Arc::new(Mutex::new(VirtualClockState {
109
+ epoch_ns: config
110
+ .realtime_epoch_ms
111
+ .checked_mul(1_000_000)
112
+ .expect("validated realtime epoch fits a WASI timestamp"),
113
+ step_ns: config.clock_step_ns,
114
+ elapsed_ns: 0,
115
+ limit_ns: logical_time_limit_ms
116
+ .checked_mul(1_000_000)
117
+ .expect("validated logical time limit fits a JavaScript safe integer"),
118
+ limit_exceeded: false,
119
+ })),
120
+ }
121
+ }
122
+
123
+ pub(crate) fn unbounded(config: &DeterminismConfig) -> Self {
124
+ Self {
125
+ state: Arc::new(Mutex::new(VirtualClockState {
126
+ epoch_ns: config
127
+ .realtime_epoch_ms
128
+ .checked_mul(1_000_000)
129
+ .expect("compiler realtime epoch fits a WASI timestamp"),
130
+ step_ns: config.clock_step_ns,
131
+ elapsed_ns: 0,
132
+ limit_ns: u64::MAX,
133
+ limit_exceeded: false,
134
+ })),
135
+ }
136
+ }
137
+
138
+ fn observe(&self, clock_id: u32) -> Result<u64, RuntimeError> {
139
+ let mut state = self
140
+ .state
141
+ .lock()
142
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
143
+ let value = state.value(clock_id)?;
144
+ let target = state
145
+ .elapsed_ns
146
+ .checked_add(state.step_ns)
147
+ .ok_or_else(|| RuntimeError::new("WASM-OJ virtual clock overflowed"))?;
148
+ state.advance_to(target)?;
149
+ Ok(value)
150
+ }
151
+
152
+ fn advance(&self, duration_ns: u64) -> Result<(), RuntimeError> {
153
+ let mut state = self
154
+ .state
155
+ .lock()
156
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
157
+ let target = state.elapsed_ns.saturating_add(duration_ns);
158
+ state.advance_to(target)
159
+ }
160
+
161
+ fn advance_to(&self, target_ns: u64) -> Result<(), RuntimeError> {
162
+ self.state
163
+ .lock()
164
+ .map_err(|error| RuntimeError::new(error.to_string()))?
165
+ .advance_to(target_ns)
166
+ }
167
+
168
+ fn deadline(
169
+ &self,
170
+ clock: wasmer_wasix::wasmer_wasix_types::wasi::SubscriptionClock,
171
+ ) -> Result<u64, i32> {
172
+ let state = self.state.lock().map_err(|_| Errno::Io as i32)?;
173
+ if clock.clock_id != Clockid::Realtime && clock.clock_id != Clockid::Monotonic {
174
+ return Err(Errno::Inval as i32);
175
+ }
176
+ if clock
177
+ .flags
178
+ .contains(Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
179
+ {
180
+ let target = if clock.clock_id == Clockid::Realtime {
181
+ clock.timeout.saturating_sub(state.epoch_ns)
182
+ } else {
183
+ clock.timeout
184
+ };
185
+ Ok(target.max(state.elapsed_ns))
186
+ } else {
187
+ Ok(state.elapsed_ns.saturating_add(clock.timeout))
188
+ }
189
+ }
190
+
191
+ pub(crate) fn elapsed_ns(&self) -> Result<u64, RunError> {
192
+ Ok(self
193
+ .state
194
+ .lock()
195
+ .map_err(|error| RunError::Runtime(error.to_string()))?
196
+ .elapsed_ns)
197
+ }
198
+
199
+ pub(crate) fn limit_exceeded(&self) -> Result<bool, RunError> {
200
+ Ok(self
201
+ .state
202
+ .lock()
203
+ .map_err(|error| RunError::Runtime(error.to_string()))?
204
+ .limit_exceeded)
205
+ }
206
+ }
207
+
208
+ pub struct DeterministicEnv {
209
+ memory: Arc<Mutex<Option<Memory>>>,
210
+ random: Mutex<DeterministicRandom>,
211
+ clock: VirtualClock,
212
+ }
213
+
214
+ #[derive(Debug)]
215
+ struct DeterministicRandom {
216
+ startup: SplitMix64,
217
+ startup_remaining: u64,
218
+ user: SplitMix64,
219
+ }
220
+
221
+ impl DeterministicRandom {
222
+ fn fill(&mut self, output: &mut [u8]) {
223
+ let startup_length = usize::try_from(self.startup_remaining.min(output.len() as u64))
224
+ .expect("startup entropy chunk fits usize");
225
+ self.startup.fill(&mut output[..startup_length]);
226
+ self.startup_remaining = self.startup_remaining.saturating_sub(startup_length as u64);
227
+ self.user.fill(&mut output[startup_length..]);
228
+ }
229
+ }
230
+
231
+ struct SetTimesEnv {
232
+ original: Function,
233
+ clock: VirtualClock,
234
+ }
235
+
236
+ struct PollEnv {
237
+ original: Option<Function>,
238
+ memory: Arc<Mutex<Option<Memory>>>,
239
+ clock: VirtualClock,
240
+ }
241
+
242
+ #[derive(Clone, Copy)]
243
+ struct ClockSubscription {
244
+ subscription: Subscription,
245
+ deadline_ns: u64,
246
+ }
247
+
248
+ fn call_original_poll<M: MemorySize>(
249
+ env: &mut FunctionEnvMut<PollEnv>,
250
+ subscriptions: WasmPtr<Subscription, M>,
251
+ events: WasmPtr<Event, M>,
252
+ subscription_count: M::Offset,
253
+ event_count: WasmPtr<M::Offset, M>,
254
+ ) -> Result<i32, RuntimeError> {
255
+ let Some(original) = env.data().original.clone() else {
256
+ return Ok(Errno::Notsup as i32);
257
+ };
258
+ let pointer = |offset: u64| {
259
+ if M::is_64bit() {
260
+ Value::I64(offset as i64)
261
+ } else {
262
+ Value::I32(offset as i32)
263
+ }
264
+ };
265
+ let results = original.call(
266
+ env,
267
+ &[
268
+ pointer(subscriptions.offset().into()),
269
+ pointer(events.offset().into()),
270
+ pointer(subscription_count.into()),
271
+ pointer(event_count.offset().into()),
272
+ ],
273
+ )?;
274
+ match results.as_ref() {
275
+ [Value::I32(errno)] => Ok(*errno),
276
+ _ => Err(RuntimeError::new(
277
+ "WASI poll_oneoff returned an invalid result",
278
+ )),
279
+ }
280
+ }
281
+
282
+ fn deterministic_poll_oneoff<M: MemorySize>(
283
+ mut env: FunctionEnvMut<PollEnv>,
284
+ subscriptions: WasmPtr<Subscription, M>,
285
+ events: WasmPtr<Event, M>,
286
+ subscription_count: M::Offset,
287
+ event_count: WasmPtr<M::Offset, M>,
288
+ ) -> Result<i32, RuntimeError> {
289
+ let memory = env
290
+ .data()
291
+ .memory
292
+ .lock()
293
+ .map_err(|error| RuntimeError::new(error.to_string()))?
294
+ .clone();
295
+ let Some(memory) = memory else {
296
+ return Ok(Errno::Fault as i32);
297
+ };
298
+ let view = memory.view(&env);
299
+ let input = subscriptions
300
+ .slice(&view, subscription_count)
301
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
302
+ let mut originals = Vec::with_capacity(input.len() as usize);
303
+ let mut clock_subscriptions = Vec::new();
304
+ let mut has_non_clock = false;
305
+ for index in 0..input.len() {
306
+ let subscription = input
307
+ .index(index)
308
+ .read()
309
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
310
+ originals.push(subscription);
311
+ if subscription.type_ == Eventtype::Clock {
312
+ let clock = unsafe { subscription.data.clock };
313
+ let deadline_ns = match env.data().clock.deadline(clock) {
314
+ Ok(value) => value,
315
+ Err(errno) => return Ok(errno),
316
+ };
317
+ clock_subscriptions.push(ClockSubscription {
318
+ subscription,
319
+ deadline_ns,
320
+ });
321
+ } else {
322
+ has_non_clock = true;
323
+ }
324
+ }
325
+
326
+ if clock_subscriptions.is_empty() {
327
+ return call_original_poll(
328
+ &mut env,
329
+ subscriptions,
330
+ events,
331
+ subscription_count,
332
+ event_count,
333
+ );
334
+ }
335
+
336
+ if has_non_clock && env.data().original.is_some() {
337
+ for (index, original) in originals.iter().copied().enumerate() {
338
+ if original.type_ != Eventtype::Clock {
339
+ continue;
340
+ }
341
+ let mut clock = unsafe { original.data.clock };
342
+ clock.timeout = 1;
343
+ clock.flags = Subclockflags::from_bits_preserve(0);
344
+ input
345
+ .index(index as u64)
346
+ .write(Subscription {
347
+ data: SubscriptionUnion { clock },
348
+ ..original
349
+ })
350
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
351
+ }
352
+ let probe = call_original_poll(
353
+ &mut env,
354
+ subscriptions,
355
+ events,
356
+ subscription_count,
357
+ event_count,
358
+ );
359
+ let restore_view = memory.view(&env);
360
+ let restore_input = subscriptions
361
+ .slice(&restore_view, subscription_count)
362
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
363
+ for (index, original) in originals.iter().copied().enumerate() {
364
+ restore_input
365
+ .index(index as u64)
366
+ .write(original)
367
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
368
+ }
369
+ let errno = probe?;
370
+ if errno != Errno::Success as i32 {
371
+ return Ok(errno);
372
+ }
373
+
374
+ let count = event_count
375
+ .read(&restore_view)
376
+ .map_err(|error| RuntimeError::new(error.to_string()))?
377
+ .into();
378
+ let output = events
379
+ .slice(&restore_view, subscription_count)
380
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
381
+ let mut fd_events = 0_u64;
382
+ for index in 0..count {
383
+ let event = output
384
+ .index(index)
385
+ .read()
386
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
387
+ if event.type_ == Eventtype::Clock {
388
+ continue;
389
+ }
390
+ output
391
+ .index(fd_events)
392
+ .write(event)
393
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
394
+ fd_events += 1;
395
+ }
396
+ if fd_events > 0 {
397
+ event_count
398
+ .write(
399
+ &restore_view,
400
+ M::Offset::try_from(fd_events).map_err(|_| {
401
+ RuntimeError::new("deterministic poll event count exceeds guest range")
402
+ })?,
403
+ )
404
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
405
+ return Ok(Errno::Success as i32);
406
+ }
407
+ }
408
+
409
+ let deadline_ns = clock_subscriptions
410
+ .iter()
411
+ .map(|subscription| subscription.deadline_ns)
412
+ .min()
413
+ .expect("clock subscription list is non-empty");
414
+ env.data().clock.advance_to(deadline_ns)?;
415
+ let elapsed_ns = env
416
+ .data()
417
+ .clock
418
+ .state
419
+ .lock()
420
+ .map_err(|error| RuntimeError::new(error.to_string()))?
421
+ .elapsed_ns;
422
+ let final_view = memory.view(&env);
423
+ let output = events
424
+ .slice(&final_view, subscription_count)
425
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
426
+ let mut written = 0_u64;
427
+ for clock in clock_subscriptions {
428
+ if clock.deadline_ns > elapsed_ns {
429
+ continue;
430
+ }
431
+ output
432
+ .index(written)
433
+ .write(Event {
434
+ userdata: clock.subscription.userdata,
435
+ error: Errno::Success,
436
+ type_: Eventtype::Clock,
437
+ u: EventUnion { clock: 0 },
438
+ })
439
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
440
+ written += 1;
441
+ }
442
+ event_count
443
+ .write(
444
+ &final_view,
445
+ M::Offset::try_from(written).map_err(|_| {
446
+ RuntimeError::new("deterministic poll event count exceeds guest range")
447
+ })?,
448
+ )
449
+ .map_err(|error| RuntimeError::new(error.to_string()))?;
450
+ Ok(Errno::Success as i32)
451
+ }
452
+
453
+ fn deterministic_poll_oneoff_32(
454
+ env: FunctionEnvMut<PollEnv>,
455
+ subscriptions: WasmPtr<Subscription, Memory32>,
456
+ events: WasmPtr<Event, Memory32>,
457
+ subscription_count: u32,
458
+ event_count: WasmPtr<u32, Memory32>,
459
+ ) -> Result<i32, RuntimeError> {
460
+ deterministic_poll_oneoff(env, subscriptions, events, subscription_count, event_count)
461
+ }
462
+
463
+ fn deterministic_poll_oneoff_64(
464
+ env: FunctionEnvMut<PollEnv>,
465
+ subscriptions: WasmPtr<Subscription, Memory64>,
466
+ events: WasmPtr<Event, Memory64>,
467
+ subscription_count: u64,
468
+ event_count: WasmPtr<u64, Memory64>,
469
+ ) -> Result<i32, RuntimeError> {
470
+ deterministic_poll_oneoff(env, subscriptions, events, subscription_count, event_count)
471
+ }
472
+
473
+ fn write_u64<M: MemorySize>(
474
+ env: &FunctionEnvMut<DeterministicEnv>,
475
+ pointer: WasmPtr<u64, M>,
476
+ value: u64,
477
+ ) -> i32 {
478
+ let Ok(memory) = env.data().memory.lock() else {
479
+ return 1;
480
+ };
481
+ let Some(memory) = memory.as_ref() else {
482
+ return 1;
483
+ };
484
+ pointer
485
+ .write(&memory.view(env), value)
486
+ .map(|_| 0)
487
+ .unwrap_or(1)
488
+ }
489
+
490
+ fn write_u32<M: MemorySize>(
491
+ env: &FunctionEnvMut<DeterministicEnv>,
492
+ pointer: WasmPtr<u32, M>,
493
+ value: u32,
494
+ ) -> i32 {
495
+ let Ok(memory) = env.data().memory.lock() else {
496
+ return 1;
497
+ };
498
+ let Some(memory) = memory.as_ref() else {
499
+ return 1;
500
+ };
501
+ pointer
502
+ .write(&memory.view(env), value)
503
+ .map(|_| 0)
504
+ .unwrap_or(1)
505
+ }
506
+
507
+ fn clock_time_get_32(
508
+ env: FunctionEnvMut<DeterministicEnv>,
509
+ clock_id: u32,
510
+ _precision: u64,
511
+ result: WasmPtr<u64, Memory32>,
512
+ ) -> Result<i32, RuntimeError> {
513
+ if !VirtualClockState::supports(clock_id) {
514
+ return Ok(Errno::Inval as i32);
515
+ }
516
+ let value = env.data().clock.observe(clock_id)?;
517
+ Ok(write_u64(&env, result, value))
518
+ }
519
+
520
+ fn clock_time_get_64(
521
+ env: FunctionEnvMut<DeterministicEnv>,
522
+ clock_id: u32,
523
+ _precision: u64,
524
+ result: WasmPtr<u64, Memory64>,
525
+ ) -> Result<i32, RuntimeError> {
526
+ if !VirtualClockState::supports(clock_id) {
527
+ return Ok(Errno::Inval as i32);
528
+ }
529
+ let value = env.data().clock.observe(clock_id)?;
530
+ Ok(write_u64(&env, result, value))
531
+ }
532
+
533
+ fn clock_res_get_32(
534
+ env: FunctionEnvMut<DeterministicEnv>,
535
+ clock_id: u32,
536
+ result: WasmPtr<u64, Memory32>,
537
+ ) -> i32 {
538
+ if !VirtualClockState::supports(clock_id) {
539
+ return Errno::Inval as i32;
540
+ }
541
+ let resolution = match env.data().clock.state.lock() {
542
+ Ok(clock) => clock.step_ns,
543
+ Err(_) => return 1,
544
+ };
545
+ write_u64(&env, result, resolution)
546
+ }
547
+
548
+ fn clock_res_get_64(
549
+ env: FunctionEnvMut<DeterministicEnv>,
550
+ clock_id: u32,
551
+ result: WasmPtr<u64, Memory64>,
552
+ ) -> i32 {
553
+ if !VirtualClockState::supports(clock_id) {
554
+ return Errno::Inval as i32;
555
+ }
556
+ let resolution = match env.data().clock.state.lock() {
557
+ Ok(clock) => clock.step_ns,
558
+ Err(_) => return 1,
559
+ };
560
+ write_u64(&env, result, resolution)
561
+ }
562
+
563
+ fn random_get<M: MemorySize>(
564
+ env: FunctionEnvMut<DeterministicEnv>,
565
+ result: WasmPtr<u8, M>,
566
+ length: M::Offset,
567
+ ) -> i32 {
568
+ let Ok(memory) = env.data().memory.lock() else {
569
+ return 1;
570
+ };
571
+ let Some(memory) = memory.as_ref() else {
572
+ return 1;
573
+ };
574
+ let view = memory.view(&env);
575
+ let Ok(destination) = result.slice(&view, length) else {
576
+ return 1;
577
+ };
578
+
579
+ let Ok(mut random) = env.data().random.lock() else {
580
+ return 1;
581
+ };
582
+ let mut chunk = [0_u8; 4_096];
583
+ let mut written = 0_u64;
584
+ while written < destination.len() {
585
+ let end = written
586
+ .saturating_add(chunk.len() as u64)
587
+ .min(destination.len());
588
+ let chunk_length = usize::try_from(end - written).expect("random chunk length fits usize");
589
+ random.fill(&mut chunk[..chunk_length]);
590
+ if destination
591
+ .subslice(written..end)
592
+ .write_slice(&chunk[..chunk_length])
593
+ .is_err()
594
+ {
595
+ return 1;
596
+ }
597
+ written = end;
598
+ }
599
+ 0
600
+ }
601
+
602
+ fn random_get_32(
603
+ env: FunctionEnvMut<DeterministicEnv>,
604
+ result: WasmPtr<u8, Memory32>,
605
+ length: u32,
606
+ ) -> i32 {
607
+ random_get(env, result, length)
608
+ }
609
+
610
+ fn random_get_64(
611
+ env: FunctionEnvMut<DeterministicEnv>,
612
+ result: WasmPtr<u8, Memory64>,
613
+ length: u64,
614
+ ) -> i32 {
615
+ random_get(env, result, length)
616
+ }
617
+
618
+ fn thread_id_32(env: FunctionEnvMut<DeterministicEnv>, result: WasmPtr<u32, Memory32>) -> i32 {
619
+ write_u32(&env, result, 1)
620
+ }
621
+
622
+ fn thread_id_64(env: FunctionEnvMut<DeterministicEnv>, result: WasmPtr<u32, Memory64>) -> i32 {
623
+ write_u32(&env, result, 1)
624
+ }
625
+
626
+ fn thread_parallelism_32(
627
+ env: FunctionEnvMut<DeterministicEnv>,
628
+ result: WasmPtr<u32, Memory32>,
629
+ ) -> i32 {
630
+ write_u32(&env, result, 1)
631
+ }
632
+
633
+ fn thread_parallelism_64(
634
+ env: FunctionEnvMut<DeterministicEnv>,
635
+ result: WasmPtr<u64, Memory64>,
636
+ ) -> i32 {
637
+ write_u64(&env, result, 1)
638
+ }
639
+
640
+ fn thread_sleep(
641
+ env: FunctionEnvMut<DeterministicEnv>,
642
+ duration_ns: u64,
643
+ ) -> Result<i32, RuntimeError> {
644
+ env.data().clock.advance(duration_ns)?;
645
+ Ok(0)
646
+ }
647
+
648
+ fn deterministic_fd_filestat_set_times(
649
+ mut env: FunctionEnvMut<SetTimesEnv>,
650
+ fd: i32,
651
+ atime: i64,
652
+ mtime: i64,
653
+ flags: i32,
654
+ ) -> Result<i32, RuntimeError> {
655
+ let Some((atime, mtime, flags)) = deterministic_set_times_arguments(&env, atime, mtime, flags)?
656
+ else {
657
+ return Ok(1);
658
+ };
659
+ call_original_errno(
660
+ &mut env,
661
+ &[
662
+ Value::I32(fd),
663
+ Value::I64(atime),
664
+ Value::I64(mtime),
665
+ Value::I32(flags),
666
+ ],
667
+ )
668
+ }
669
+
670
+ // The host function must retain the exact WASI import signature.
671
+ #[allow(clippy::too_many_arguments)]
672
+ fn deterministic_path_filestat_set_times_32(
673
+ mut env: FunctionEnvMut<SetTimesEnv>,
674
+ fd: i32,
675
+ lookup_flags: i32,
676
+ path: i32,
677
+ path_length: i32,
678
+ atime: i64,
679
+ mtime: i64,
680
+ flags: i32,
681
+ ) -> Result<i32, RuntimeError> {
682
+ let Some((atime, mtime, flags)) = deterministic_set_times_arguments(&env, atime, mtime, flags)?
683
+ else {
684
+ return Ok(1);
685
+ };
686
+ call_original_errno(
687
+ &mut env,
688
+ &[
689
+ Value::I32(fd),
690
+ Value::I32(lookup_flags),
691
+ Value::I32(path),
692
+ Value::I32(path_length),
693
+ Value::I64(atime),
694
+ Value::I64(mtime),
695
+ Value::I32(flags),
696
+ ],
697
+ )
698
+ }
699
+
700
+ // The host function must retain the exact WASIX64 import signature.
701
+ #[allow(clippy::too_many_arguments)]
702
+ fn deterministic_path_filestat_set_times_64(
703
+ mut env: FunctionEnvMut<SetTimesEnv>,
704
+ fd: i32,
705
+ lookup_flags: i32,
706
+ path: i64,
707
+ path_length: i64,
708
+ atime: i64,
709
+ mtime: i64,
710
+ flags: i32,
711
+ ) -> Result<i32, RuntimeError> {
712
+ let Some((atime, mtime, flags)) = deterministic_set_times_arguments(&env, atime, mtime, flags)?
713
+ else {
714
+ return Ok(1);
715
+ };
716
+ call_original_errno(
717
+ &mut env,
718
+ &[
719
+ Value::I32(fd),
720
+ Value::I32(lookup_flags),
721
+ Value::I64(path),
722
+ Value::I64(path_length),
723
+ Value::I64(atime),
724
+ Value::I64(mtime),
725
+ Value::I32(flags),
726
+ ],
727
+ )
728
+ }
729
+
730
+ fn deterministic_set_times_arguments(
731
+ env: &FunctionEnvMut<SetTimesEnv>,
732
+ mut atime: i64,
733
+ mut mtime: i64,
734
+ flags: i32,
735
+ ) -> Result<Option<(i64, i64, i32)>, RuntimeError> {
736
+ let mut bits = flags as u32;
737
+ let invalid = (bits & FSTFLAGS_ATIM != 0 && bits & FSTFLAGS_ATIM_NOW != 0)
738
+ || (bits & FSTFLAGS_MTIM != 0 && bits & FSTFLAGS_MTIM_NOW != 0);
739
+ if invalid || bits & (FSTFLAGS_ATIM_NOW | FSTFLAGS_MTIM_NOW) == 0 {
740
+ return Ok(Some((atime, mtime, flags)));
741
+ }
742
+
743
+ if bits & FSTFLAGS_ATIM_NOW != 0 {
744
+ atime = env.data().clock.observe(Clockid::Realtime as u32)? as i64;
745
+ bits = (bits | FSTFLAGS_ATIM) & !FSTFLAGS_ATIM_NOW;
746
+ }
747
+ if bits & FSTFLAGS_MTIM_NOW != 0 {
748
+ mtime = env.data().clock.observe(Clockid::Realtime as u32)? as i64;
749
+ bits = (bits | FSTFLAGS_MTIM) & !FSTFLAGS_MTIM_NOW;
750
+ }
751
+ Ok(Some((atime, mtime, bits as i32)))
752
+ }
753
+
754
+ fn call_original_errno(
755
+ env: &mut FunctionEnvMut<SetTimesEnv>,
756
+ arguments: &[Value],
757
+ ) -> Result<i32, RuntimeError> {
758
+ let original = env.data().original.clone();
759
+ let results = original.call(env, arguments)?;
760
+ match results.as_ref() {
761
+ [Value::I32(errno)] => Ok(*errno),
762
+ _ => Err(RuntimeError::new(
763
+ "WASI filestat setter returned an invalid result",
764
+ )),
765
+ }
766
+ }
767
+
768
+ fn attach_deterministic_set_times_32(
769
+ store: &mut impl AsStoreMut,
770
+ imports: &mut Imports,
771
+ namespace: &str,
772
+ clock: &VirtualClock,
773
+ ) {
774
+ if let Some(Extern::Function(original)) = imports.get_export(namespace, "fd_filestat_set_times")
775
+ {
776
+ let env = FunctionEnv::new(
777
+ &mut *store,
778
+ SetTimesEnv {
779
+ original,
780
+ clock: clock.clone(),
781
+ },
782
+ );
783
+ imports.define(
784
+ namespace,
785
+ "fd_filestat_set_times",
786
+ Function::new_typed_with_env(&mut *store, &env, deterministic_fd_filestat_set_times),
787
+ );
788
+ }
789
+ if let Some(Extern::Function(original)) =
790
+ imports.get_export(namespace, "path_filestat_set_times")
791
+ {
792
+ let env = FunctionEnv::new(
793
+ &mut *store,
794
+ SetTimesEnv {
795
+ original,
796
+ clock: clock.clone(),
797
+ },
798
+ );
799
+ imports.define(
800
+ namespace,
801
+ "path_filestat_set_times",
802
+ Function::new_typed_with_env(store, &env, deterministic_path_filestat_set_times_32),
803
+ );
804
+ }
805
+ }
806
+
807
+ fn attach_deterministic_set_times_64(
808
+ store: &mut impl AsStoreMut,
809
+ imports: &mut Imports,
810
+ namespace: &str,
811
+ clock: &VirtualClock,
812
+ ) {
813
+ if let Some(Extern::Function(original)) = imports.get_export(namespace, "fd_filestat_set_times")
814
+ {
815
+ let env = FunctionEnv::new(
816
+ &mut *store,
817
+ SetTimesEnv {
818
+ original,
819
+ clock: clock.clone(),
820
+ },
821
+ );
822
+ imports.define(
823
+ namespace,
824
+ "fd_filestat_set_times",
825
+ Function::new_typed_with_env(&mut *store, &env, deterministic_fd_filestat_set_times),
826
+ );
827
+ }
828
+ if let Some(Extern::Function(original)) =
829
+ imports.get_export(namespace, "path_filestat_set_times")
830
+ {
831
+ let env = FunctionEnv::new(
832
+ &mut *store,
833
+ SetTimesEnv {
834
+ original,
835
+ clock: clock.clone(),
836
+ },
837
+ );
838
+ imports.define(
839
+ namespace,
840
+ "path_filestat_set_times",
841
+ Function::new_typed_with_env(store, &env, deterministic_path_filestat_set_times_64),
842
+ );
843
+ }
844
+ }
845
+
846
+ fn attach_deterministic_poll_32(
847
+ store: &mut impl AsStoreMut,
848
+ imports: &mut Imports,
849
+ namespace: &str,
850
+ memory: &Arc<Mutex<Option<Memory>>>,
851
+ clock: &VirtualClock,
852
+ ) {
853
+ let original = match imports.get_export(namespace, "poll_oneoff") {
854
+ Some(Extern::Function(function)) => Some(function),
855
+ _ => None,
856
+ };
857
+ let env = FunctionEnv::new(
858
+ &mut *store,
859
+ PollEnv {
860
+ original,
861
+ memory: memory.clone(),
862
+ clock: clock.clone(),
863
+ },
864
+ );
865
+ imports.define(
866
+ namespace,
867
+ "poll_oneoff",
868
+ Function::new_typed_with_env(store, &env, deterministic_poll_oneoff_32),
869
+ );
870
+ }
871
+
872
+ fn attach_deterministic_poll_64(
873
+ store: &mut impl AsStoreMut,
874
+ imports: &mut Imports,
875
+ namespace: &str,
876
+ memory: &Arc<Mutex<Option<Memory>>>,
877
+ clock: &VirtualClock,
878
+ ) {
879
+ let original = match imports.get_export(namespace, "poll_oneoff") {
880
+ Some(Extern::Function(function)) => Some(function),
881
+ _ => None,
882
+ };
883
+ let env = FunctionEnv::new(
884
+ &mut *store,
885
+ PollEnv {
886
+ original,
887
+ memory: memory.clone(),
888
+ clock: clock.clone(),
889
+ },
890
+ );
891
+ imports.define(
892
+ namespace,
893
+ "poll_oneoff",
894
+ Function::new_typed_with_env(store, &env, deterministic_poll_oneoff_64),
895
+ );
896
+ }
897
+
898
+ pub fn attach_deterministic_imports(
899
+ store: &mut impl AsStoreMut,
900
+ imports: &mut Imports,
901
+ memory: Arc<Mutex<Option<Memory>>>,
902
+ config: &DeterminismConfig,
903
+ clock: VirtualClock,
904
+ startup_entropy_bytes: u64,
905
+ ) {
906
+ let poll_memory = memory.clone();
907
+ let env = FunctionEnv::new(
908
+ store,
909
+ DeterministicEnv {
910
+ memory,
911
+ random: Mutex::new(DeterministicRandom {
912
+ startup: SplitMix64 {
913
+ state: 0x464f_5247_455f_474f,
914
+ },
915
+ startup_remaining: startup_entropy_bytes,
916
+ user: SplitMix64 {
917
+ state: config.random_seed,
918
+ },
919
+ }),
920
+ clock: clock.clone(),
921
+ },
922
+ );
923
+
924
+ for namespace in ["wasi_snapshot_preview1", "wasix_32v1"] {
925
+ imports.define(
926
+ namespace,
927
+ "clock_time_get",
928
+ Function::new_typed_with_env(store, &env, clock_time_get_32),
929
+ );
930
+ imports.define(
931
+ namespace,
932
+ "clock_res_get",
933
+ Function::new_typed_with_env(store, &env, clock_res_get_32),
934
+ );
935
+ imports.define(
936
+ namespace,
937
+ "random_get",
938
+ Function::new_typed_with_env(store, &env, random_get_32),
939
+ );
940
+ attach_deterministic_poll_32(store, imports, namespace, &poll_memory, &clock);
941
+ attach_deterministic_set_times_32(store, imports, namespace, &clock);
942
+ }
943
+ imports.define(
944
+ "wasix_32v1",
945
+ "thread_id",
946
+ Function::new_typed_with_env(store, &env, thread_id_32),
947
+ );
948
+ imports.define(
949
+ "wasix_32v1",
950
+ "thread_parallelism",
951
+ Function::new_typed_with_env(store, &env, thread_parallelism_32),
952
+ );
953
+ imports.define(
954
+ "wasix_32v1",
955
+ "thread_sleep",
956
+ Function::new_typed_with_env(store, &env, thread_sleep),
957
+ );
958
+ imports.define(
959
+ "wasix_64v1",
960
+ "clock_time_get",
961
+ Function::new_typed_with_env(store, &env, clock_time_get_64),
962
+ );
963
+ imports.define(
964
+ "wasix_64v1",
965
+ "clock_res_get",
966
+ Function::new_typed_with_env(store, &env, clock_res_get_64),
967
+ );
968
+ imports.define(
969
+ "wasix_64v1",
970
+ "random_get",
971
+ Function::new_typed_with_env(store, &env, random_get_64),
972
+ );
973
+ attach_deterministic_poll_64(store, imports, "wasix_64v1", &poll_memory, &clock);
974
+ attach_deterministic_set_times_64(store, imports, "wasix_64v1", &clock);
975
+ imports.define(
976
+ "wasix_64v1",
977
+ "thread_id",
978
+ Function::new_typed_with_env(store, &env, thread_id_64),
979
+ );
980
+ imports.define(
981
+ "wasix_64v1",
982
+ "thread_parallelism",
983
+ Function::new_typed_with_env(store, &env, thread_parallelism_64),
984
+ );
985
+ imports.define(
986
+ "wasix_64v1",
987
+ "thread_sleep",
988
+ Function::new_typed_with_env(store, &env, thread_sleep),
989
+ );
990
+ }
991
+
992
+ pub(crate) fn attach_interactive_deterministic_imports(
993
+ store: &mut impl AsStoreMut,
994
+ imports: &mut Imports,
995
+ memory: Arc<Mutex<Option<Memory>>>,
996
+ config: &DeterminismConfig,
997
+ clock: VirtualClock,
998
+ startup_entropy_bytes: u64,
999
+ ) {
1000
+ attach_deterministic_imports(store, imports, memory, config, clock, startup_entropy_bytes);
1001
+ for (source, target, names) in [
1002
+ (
1003
+ "wasi_snapshot_preview1",
1004
+ INTERACTIVE_WASIP1_DETERMINISTIC_NAMESPACE,
1005
+ &[
1006
+ "clock_res_get",
1007
+ "clock_time_get",
1008
+ "fd_filestat_set_times",
1009
+ "path_filestat_set_times",
1010
+ "poll_oneoff",
1011
+ "random_get",
1012
+ ][..],
1013
+ ),
1014
+ (
1015
+ "wasix_32v1",
1016
+ INTERACTIVE_WASIX32_DETERMINISTIC_NAMESPACE,
1017
+ &[
1018
+ "clock_res_get",
1019
+ "clock_time_get",
1020
+ "fd_filestat_set_times",
1021
+ "path_filestat_set_times",
1022
+ "poll_oneoff",
1023
+ "random_get",
1024
+ "thread_id",
1025
+ "thread_parallelism",
1026
+ "thread_sleep",
1027
+ ][..],
1028
+ ),
1029
+ (
1030
+ "wasix_64v1",
1031
+ INTERACTIVE_WASIX64_DETERMINISTIC_NAMESPACE,
1032
+ &[
1033
+ "clock_res_get",
1034
+ "clock_time_get",
1035
+ "fd_filestat_set_times",
1036
+ "path_filestat_set_times",
1037
+ "poll_oneoff",
1038
+ "random_get",
1039
+ "thread_id",
1040
+ "thread_parallelism",
1041
+ "thread_sleep",
1042
+ ][..],
1043
+ ),
1044
+ ] {
1045
+ for name in names {
1046
+ if let Some(export) = imports.get_export(source, name) {
1047
+ imports.define(target, name, export);
1048
+ }
1049
+ }
1050
+ }
1051
+ }