@wasm-oj/server 0.2.4 → 0.2.5

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.
@@ -3,7 +3,9 @@ use crate::deterministic::{VirtualClock, attach_interactive_deterministic_import
3
3
  use crate::filesystem::{
4
4
  RuntimeProjectFilesystem, is_normalized_guest_path, runtime_project_files,
5
5
  };
6
- use crate::meter::{CostPoints, MeterState, instrument_wasm, meter_state, remaining_points};
6
+ use crate::meter::{
7
+ CostPoints, MeterState, attach_safepoint, instrument_wasm, meter_state, remaining_points,
8
+ };
7
9
  use crate::module_imports::attach_declared_memory_imports;
8
10
  use crate::module_policy::{
9
11
  DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit,
@@ -121,6 +123,9 @@ struct InteractiveOutput {
121
123
  }
122
124
 
123
125
  impl AsyncWrite for InteractiveOutput {
126
+ /// Once the peer has closed its stdin, for example by exiting, the pipe reports a broken
127
+ /// pipe. The bytes are already in the transcript, so the write succeeds and they are dropped,
128
+ /// as when a judge keeps draining a pipe whose reader is gone.
124
129
  fn poll_write(
125
130
  mut self: Pin<&mut Self>,
126
131
  context: &mut Context<'_>,
@@ -128,7 +133,12 @@ impl AsyncWrite for InteractiveOutput {
128
133
  ) -> Poll<io::Result<usize>> {
129
134
  match Pin::new(&mut self.capture).poll_write(context, buffer) {
130
135
  Poll::Ready(Ok(written)) => {
131
- Pin::new(&mut self.pipe).poll_write(context, &buffer[..written])
136
+ match Pin::new(&mut self.pipe).poll_write(context, &buffer[..written]) {
137
+ Poll::Ready(Err(error)) if error.kind() == io::ErrorKind::BrokenPipe => {
138
+ Poll::Ready(Ok(written))
139
+ }
140
+ result => result,
141
+ }
132
142
  }
133
143
  result => result,
134
144
  }
@@ -563,6 +573,7 @@ fn interactive_runtime(
563
573
  clock.clone(),
564
574
  startup_entropy_bytes,
565
575
  );
576
+ attach_safepoint(store, &mut imports);
566
577
  attach_capability_denials(store, module, &mut imports).map_err(io::Error::other)?;
567
578
  attach_declared_memory_imports(store, module, &mut imports).map_err(io::Error::other)?;
568
579
  Ok(imports)
@@ -1120,6 +1131,128 @@ mod tests {
1120
1131
  assert_eq!(interactive.metrics.cost, standalone.metrics.cost);
1121
1132
  }
1122
1133
 
1134
+ const DRAIN_STDIN: &str = r#"
1135
+ (func $drain_stdin (result i32)
1136
+ (local $total i32)
1137
+ (loop $again
1138
+ (i32.store (i32.const 0) (i32.add (i32.const 256) (local.get $total)))
1139
+ (i32.store (i32.const 4) (i32.const 64))
1140
+ (if (call $fd_read (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 8))
1141
+ (then (call $exit (i32.const 3))))
1142
+ (local.set $total (i32.add (local.get $total) (i32.load (i32.const 8))))
1143
+ (br_if $again (i32.load (i32.const 8))))
1144
+ local.get $total)"#;
1145
+
1146
+ fn peer_program(body: &str, data: &str) -> Vec<u8> {
1147
+ wat::parse_str(format!(
1148
+ r#"(module
1149
+ (import "wasi_snapshot_preview1" "fd_read"
1150
+ (func $fd_read (param i32 i32 i32 i32) (result i32)))
1151
+ (import "wasi_snapshot_preview1" "fd_write"
1152
+ (func $fd_write (param i32 i32 i32 i32) (result i32)))
1153
+ (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32)))
1154
+ (memory (export "memory") 1)
1155
+ (data (i32.const 128) "{data}")
1156
+ {DRAIN_STDIN}
1157
+ (func $write (param $length i32) (result i32)
1158
+ (i32.store (i32.const 16) (i32.const 128))
1159
+ (i32.store (i32.const 20) (local.get $length))
1160
+ (call $fd_write (i32.const 1) (i32.const 16) (i32.const 1) (i32.const 24)))
1161
+ (func (export "_start") {body}))"#
1162
+ ))
1163
+ .unwrap()
1164
+ }
1165
+
1166
+ fn interact_pair(contestant: Vec<u8>, interactor: Vec<u8>) -> crate::InteractiveResult {
1167
+ tokio::runtime::Builder::new_current_thread()
1168
+ .enable_all()
1169
+ .build()
1170
+ .unwrap()
1171
+ .block_on(interact(InteractiveRequest {
1172
+ contestant: program(contestant),
1173
+ interactor: program(interactor),
1174
+ determinism: DeterminismConfig {
1175
+ random_seed: 7,
1176
+ realtime_epoch_ms: 946_684_800_000,
1177
+ clock_step_ns: 1_000_000,
1178
+ },
1179
+ }))
1180
+ .unwrap()
1181
+ }
1182
+
1183
+ #[test]
1184
+ fn interactor_writes_to_an_exited_contestant_without_a_broken_pipe() {
1185
+ let contestant = peer_program("(drop (call $write (i32.const 2)))", "1\\n");
1186
+ let interactor = peer_program(
1187
+ r#"(local $errno i32)
1188
+ (drop (call $drain_stdin))
1189
+ (local.set $errno (call $write (i32.const 8)))
1190
+ (if (local.get $errno) (then (call $exit (local.get $errno))))
1191
+ (if (i32.ne (i32.load (i32.const 24)) (i32.const 8)) (then (call $exit (i32.const 4))))
1192
+ (call $exit (i32.const 42))"#,
1193
+ "correct\\n",
1194
+ );
1195
+
1196
+ let result = interact_pair(contestant, interactor);
1197
+
1198
+ assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
1199
+ assert_eq!(result.contestant.code, 0);
1200
+ assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
1201
+ assert_eq!(result.interactor.code, 42);
1202
+ assert_eq!(result.contestant_to_interactor, b"1\n");
1203
+ assert_eq!(result.interactor_to_contestant, b"correct\n");
1204
+ assert_eq!(result.interactor.metrics.protocol_bytes, 8);
1205
+ }
1206
+
1207
+ #[test]
1208
+ fn contestant_reads_buffered_bytes_then_eof_after_the_interactor_exits() {
1209
+ let contestant = peer_program(
1210
+ r#"(local $total i32)
1211
+ (local.set $total (call $drain_stdin))
1212
+ (call $exit (select (i32.const 0) (i32.const 1)
1213
+ (i32.and
1214
+ (i32.eq (local.get $total) (i32.const 4))
1215
+ (i32.eq (i32.load (i32.const 256)) (i32.const 0x0a657962)))))"#,
1216
+ "",
1217
+ );
1218
+ let interactor = peer_program("(drop (call $write (i32.const 4)))", "bye\\n");
1219
+
1220
+ let result = interact_pair(contestant, interactor);
1221
+
1222
+ assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
1223
+ assert_eq!(result.interactor.code, 0);
1224
+ assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
1225
+ assert_eq!(result.contestant.code, 0);
1226
+ assert_eq!(result.interactor_to_contestant, b"bye\n");
1227
+ }
1228
+
1229
+ #[test]
1230
+ fn contestant_writes_to_an_exited_interactor_without_a_broken_pipe() {
1231
+ let contestant = peer_program(
1232
+ r#"(local $round i32)
1233
+ (local $errno i32)
1234
+ (drop (call $drain_stdin))
1235
+ (loop $again
1236
+ (local.set $errno (call $write (i32.const 2)))
1237
+ (if (local.get $errno) (then (call $exit (local.get $errno))))
1238
+ (if (i32.ne (i32.load (i32.const 24)) (i32.const 2)) (then (call $exit (i32.const 4))))
1239
+ (local.set $round (i32.add (local.get $round) (i32.const 1)))
1240
+ (br_if $again (i32.lt_u (local.get $round) (i32.const 3))))
1241
+ (call $exit (i32.const 42))"#,
1242
+ "x\\n",
1243
+ );
1244
+ let interactor = peer_program("(drop (call $write (i32.const 4)))", "bye\\n");
1245
+
1246
+ let result = interact_pair(contestant, interactor);
1247
+
1248
+ assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
1249
+ assert_eq!(result.interactor.code, 0);
1250
+ assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
1251
+ assert_eq!(result.contestant.code, 42);
1252
+ assert_eq!(result.contestant_to_interactor, b"x\nx\nx\n");
1253
+ assert_eq!(result.interactor_to_contestant, b"bye\n");
1254
+ }
1255
+
1123
1256
  fn program(wasm: Vec<u8>) -> InteractiveProgram {
1124
1257
  InteractiveProgram {
1125
1258
  wasm,
@@ -9,7 +9,7 @@ use wasm_encoder::reencode::{Error as ReencodeError, Reencode};
9
9
  use wasm_encoder::{Encode, Section};
10
10
  #[cfg(target_arch = "wasm32")]
11
11
  use wasmer::js::AsJs;
12
- use wasmer::{AsStoreMut, Global, Instance};
12
+ use wasmer::{AsStoreMut, Function, Global, Imports, Instance};
13
13
 
14
14
  pub const METER_MODEL: &str = "weighted";
15
15
  const METERING_MODULE: &str = "wasm_oj_metering";
@@ -88,30 +88,424 @@ impl std::fmt::Display for MeterInitializationError {
88
88
 
89
89
  impl std::error::Error for MeterInitializationError {}
90
90
 
91
+ /// Units a program may spend between safepoints.
92
+ pub const SAFEPOINT_INTERVAL: i64 = 1 << 20;
93
+ const SAFEPOINT_NAME: &str = "safepoint";
94
+
95
+ /// Final instrumentation pass: sets the meter's budget, adds the uncharged safepoint, and gives
96
+ /// every function that has a loop but no parameters or locals one unused `i32` local.
97
+ ///
98
+ /// JavaScriptCore (Safari 26) never enters optimized code inside a loop of a function without
99
+ /// parameters or locals: its baseline tier asks to tier up on almost every iteration and keeps
100
+ /// running the slow path, so a metered empty loop runs about 20 times slower than in Chromium and
101
+ /// hits the wall deadline before its instruction budget. The local changes neither behaviour nor
102
+ /// cost.
103
+ ///
104
+ /// JavaScriptCore also never stops a terminated Worker while it runs Wasm, only when it next
105
+ /// reaches a JavaScript checkpoint such as `Atomics.wait`. So the charge that opens every function
106
+ /// and loop body is inlined, and once the counter drops below a threshold it reaches a cold
107
+ /// function that traps on exhaustion, or calls the imported `wasm_oj_metering.safepoint` (which
108
+ /// the browser host turns into such a checkpoint) and lowers the threshold by
109
+ /// `SAFEPOINT_INTERVAL`. A call inside a hot loop makes the whole loop slower in JavaScriptCore and
110
+ /// V8 even when it never runs, so a loop body leaves the loop to reach it: each loop becomes
111
+ /// `block (loop (block (loop ...) br 2) safepoint, refund, br 0)`, and every branch out of it
112
+ /// skips the three new labels. Functions that use other kinds of labels keep the call inside the
113
+ /// loop. Every other block keeps radix's gas function. Exhaustion is exactly where it was, and
114
+ /// none of these instructions is charged.
91
115
  struct MeterInitializer {
92
116
  budget: i64,
117
+ parameterized_types: Vec<bool>,
118
+ function_types: Vec<u32>,
119
+ defined_functions: usize,
120
+ original_types: u32,
121
+ imported_functions: u32,
122
+ imported_globals: u32,
123
+ defined_globals: u32,
124
+ import_section_written: bool,
125
+ }
126
+
127
+ impl MeterInitializer {
128
+ fn new(budget: i64) -> Self {
129
+ Self {
130
+ budget,
131
+ parameterized_types: Vec::new(),
132
+ function_types: Vec::new(),
133
+ defined_functions: 0,
134
+ original_types: 0,
135
+ imported_functions: 0,
136
+ imported_globals: 0,
137
+ defined_globals: 0,
138
+ import_section_written: false,
139
+ }
140
+ }
141
+
142
+ fn safepoint_type(&self) -> u32 {
143
+ self.original_types
144
+ }
145
+
146
+ fn safepoint_function(&self) -> u32 {
147
+ self.imported_functions
148
+ }
149
+
150
+ fn gas_function(&self) -> u32 {
151
+ self.imported_functions + self.function_types.len() as u32
152
+ }
153
+
154
+ fn slow_function(&self) -> u32 {
155
+ self.gas_function() + 1
156
+ }
157
+
158
+ fn gas_global(&self) -> u32 {
159
+ self.imported_globals + self.defined_globals - 1
160
+ }
161
+
162
+ fn threshold_global(&self) -> u32 {
163
+ self.imported_globals + self.defined_globals
164
+ }
165
+
166
+ fn safepoint_import(&self, imports: &mut wasm_encoder::ImportSection) {
167
+ imports.import(
168
+ METERING_MODULE,
169
+ SAFEPOINT_NAME,
170
+ wasm_encoder::EntityType::Function(self.safepoint_type()),
171
+ );
172
+ }
173
+
174
+ fn slow_body(&self) -> wasm_encoder::Function {
175
+ use wasm_encoder::Instruction::*;
176
+ let gas = self.gas_global();
177
+ let mut function = wasm_encoder::Function::new([(1, wasm_encoder::ValType::I64)]);
178
+ for instruction in [
179
+ GlobalGet(gas),
180
+ I64Const(0),
181
+ I64LtS,
182
+ If(wasm_encoder::BlockType::Empty),
183
+ I64Const(-1),
184
+ GlobalSet(gas),
185
+ Unreachable,
186
+ End,
187
+ GlobalGet(gas),
188
+ I64Const(SAFEPOINT_INTERVAL),
189
+ I64Sub,
190
+ LocalTee(0),
191
+ I64Const(0),
192
+ LocalGet(0),
193
+ I64Const(0),
194
+ I64GtS,
195
+ Select,
196
+ GlobalSet(self.threshold_global()),
197
+ Call(self.safepoint_function()),
198
+ End,
199
+ ] {
200
+ function.instruction(&instruction);
201
+ }
202
+ function
203
+ }
204
+
205
+ /// Re-encodes a body, replacing the gas call that opens the function and each loop body with
206
+ /// an inline charge and safepoint check.
207
+ fn encode_body(
208
+ &mut self,
209
+ body: &wasmparser::FunctionBody<'_>,
210
+ extra_local: bool,
211
+ ) -> Result<wasm_encoder::Function, ReencodeError<MeterInitializationError>> {
212
+ use wasm_encoder::Instruction::*;
213
+ use wasmparser::Operator as Op;
214
+ let mut function = self.new_function_with_parsed_locals(body)?;
215
+ if extra_local {
216
+ function = wasm_encoder::Function::new([(1, wasm_encoder::ValType::I32)]);
217
+ }
218
+ let original_gas = self.imported_functions + self.function_types.len() as u32 - 1;
219
+ let gas = self.gas_global();
220
+ let rotate = !has_other_labels(body)?;
221
+ let mut frames: Vec<Option<i64>> = Vec::new();
222
+ let mut head = Some(false);
223
+ let mut pending = None;
224
+ let adjust = |frames: &[Option<i64>], depth: u32| {
225
+ let crossed = frames[frames.len() - depth as usize..]
226
+ .iter()
227
+ .filter(|frame| frame.is_some())
228
+ .count();
229
+ depth + 3 * crossed as u32
230
+ };
231
+ let mut operators = body.get_operators_reader()?;
232
+ while !operators.eof() {
233
+ let operator = operators.read()?;
234
+ if let Some((cost, wrapped)) = pending.take() {
235
+ if matches!(operator, Op::Call { function_index } if function_index == original_gas)
236
+ {
237
+ for instruction in [
238
+ GlobalGet(gas),
239
+ I64Const(cost),
240
+ I64Sub,
241
+ GlobalSet(gas),
242
+ GlobalGet(gas),
243
+ GlobalGet(self.threshold_global()),
244
+ I64LtS,
245
+ ] {
246
+ function.instruction(&instruction);
247
+ }
248
+ if wrapped {
249
+ function.instruction(&BrIf(1));
250
+ if let Some(frame) = frames.last_mut() {
251
+ *frame = Some(cost);
252
+ }
253
+ } else {
254
+ function.instruction(&If(wasm_encoder::BlockType::Empty));
255
+ function.instruction(&Call(self.slow_function()));
256
+ function.instruction(&End);
257
+ }
258
+ continue;
259
+ }
260
+ function.instruction(&I64Const(cost));
261
+ }
262
+ let at_head = head.take();
263
+ match operator {
264
+ Op::I64Const { value } if at_head.is_some() => {
265
+ pending = Some((value, at_head == Some(true)));
266
+ continue;
267
+ }
268
+ Op::Loop { blockty } => {
269
+ let parameterized = match blockty {
270
+ wasmparser::BlockType::FuncType(ty) => self
271
+ .parameterized_types
272
+ .get(ty as usize)
273
+ .copied()
274
+ .unwrap_or(true),
275
+ _ => false,
276
+ };
277
+ let wrapped = rotate && !parameterized;
278
+ let blockty = self.block_type(blockty)?;
279
+ if wrapped {
280
+ function.instruction(&Block(blockty));
281
+ function.instruction(&Loop(wasm_encoder::BlockType::Empty));
282
+ function.instruction(&Block(wasm_encoder::BlockType::Empty));
283
+ frames.push(Some(0));
284
+ } else {
285
+ frames.push(None);
286
+ }
287
+ function.instruction(&Loop(blockty));
288
+ head = Some(wrapped);
289
+ continue;
290
+ }
291
+ Op::Block { .. } | Op::If { .. } | Op::Try { .. } | Op::TryTable { .. } => {
292
+ frames.push(None)
293
+ }
294
+ Op::End => {
295
+ if let Some(Some(cost)) = frames.pop() {
296
+ for instruction in [
297
+ End,
298
+ Br(2),
299
+ End,
300
+ Call(self.slow_function()),
301
+ GlobalGet(gas),
302
+ I64Const(cost),
303
+ I64Add,
304
+ GlobalSet(gas),
305
+ Br(0),
306
+ End,
307
+ Unreachable,
308
+ End,
309
+ ] {
310
+ function.instruction(&instruction);
311
+ }
312
+ continue;
313
+ }
314
+ }
315
+ Op::Br { relative_depth } if rotate => {
316
+ function.instruction(&Br(adjust(&frames, relative_depth)));
317
+ continue;
318
+ }
319
+ Op::BrIf { relative_depth } if rotate => {
320
+ function.instruction(&BrIf(adjust(&frames, relative_depth)));
321
+ continue;
322
+ }
323
+ Op::BrTable { targets } if rotate => {
324
+ let mut depths = Vec::new();
325
+ for target in targets.targets() {
326
+ depths.push(adjust(&frames, target?));
327
+ }
328
+ function
329
+ .instruction(&BrTable(depths.into(), adjust(&frames, targets.default())));
330
+ continue;
331
+ }
332
+ _ => {}
333
+ }
334
+ function.instruction(&self.instruction(operator)?);
335
+ }
336
+ Ok(function)
337
+ }
338
+ }
339
+
340
+ /// Whether a body uses labels other than through `br`, `br_if` and `br_table`.
341
+ fn has_other_labels(
342
+ body: &wasmparser::FunctionBody<'_>,
343
+ ) -> Result<bool, wasmparser::BinaryReaderError> {
344
+ use wasmparser::Operator as Op;
345
+ let mut operators = body.get_operators_reader()?;
346
+ while !operators.eof() {
347
+ if matches!(
348
+ operators.read()?,
349
+ Op::Try { .. }
350
+ | Op::TryTable { .. }
351
+ | Op::Delegate { .. }
352
+ | Op::Rethrow { .. }
353
+ | Op::BrOnNull { .. }
354
+ | Op::BrOnNonNull { .. }
355
+ | Op::BrOnCast { .. }
356
+ | Op::BrOnCastFail { .. }
357
+ | Op::BrOnCastDescEq { .. }
358
+ | Op::BrOnCastDescEqFail { .. }
359
+ ) {
360
+ return Ok(true);
361
+ }
362
+ }
363
+ Ok(false)
364
+ }
365
+
366
+ fn meter_error(message: &str) -> ReencodeError<MeterInitializationError> {
367
+ ReencodeError::UserError(MeterInitializationError(message.to_string()))
93
368
  }
94
369
 
95
370
  impl Reencode for MeterInitializer {
96
371
  type Error = MeterInitializationError;
97
372
 
373
+ fn function_index(&mut self, func: u32) -> Result<u32, ReencodeError<Self::Error>> {
374
+ Ok(if func >= self.imported_functions {
375
+ func + 1
376
+ } else {
377
+ func
378
+ })
379
+ }
380
+
381
+ fn intersperse_section_hook(
382
+ &mut self,
383
+ module: &mut wasm_encoder::Module,
384
+ _after: Option<wasm_encoder::SectionId>,
385
+ before: Option<wasm_encoder::SectionId>,
386
+ ) -> Result<(), ReencodeError<Self::Error>> {
387
+ use wasm_encoder::SectionId;
388
+ if self.import_section_written
389
+ || matches!(
390
+ before,
391
+ Some(SectionId::Custom | SectionId::Type | SectionId::Import)
392
+ )
393
+ {
394
+ return Ok(());
395
+ }
396
+ let mut imports = wasm_encoder::ImportSection::new();
397
+ self.safepoint_import(&mut imports);
398
+ module.section(&imports);
399
+ self.import_section_written = true;
400
+ Ok(())
401
+ }
402
+
403
+ fn parse_type_section(
404
+ &mut self,
405
+ types: &mut wasm_encoder::TypeSection,
406
+ section: wasmparser::TypeSectionReader<'_>,
407
+ ) -> Result<(), ReencodeError<Self::Error>> {
408
+ for group in section.clone() {
409
+ for ty in group?.types() {
410
+ self.parameterized_types
411
+ .push(match &ty.composite_type.inner {
412
+ wasmparser::CompositeInnerType::Func(function) => {
413
+ !function.params().is_empty()
414
+ }
415
+ _ => true,
416
+ });
417
+ }
418
+ }
419
+ self.original_types = self.parameterized_types.len() as u32;
420
+ wasm_encoder::reencode::utils::parse_type_section(self, types, section)?;
421
+ types.ty().function([], []);
422
+ Ok(())
423
+ }
424
+
425
+ fn parse_import_section(
426
+ &mut self,
427
+ imports: &mut wasm_encoder::ImportSection,
428
+ section: wasmparser::ImportSectionReader<'_>,
429
+ ) -> Result<(), ReencodeError<Self::Error>> {
430
+ for import in section.clone().into_imports() {
431
+ match import?.ty {
432
+ wasmparser::TypeRef::Func(_) => self.imported_functions += 1,
433
+ wasmparser::TypeRef::Global(_) => self.imported_globals += 1,
434
+ _ => {}
435
+ }
436
+ }
437
+ wasm_encoder::reencode::utils::parse_import_section(self, imports, section)?;
438
+ self.safepoint_import(imports);
439
+ self.import_section_written = true;
440
+ Ok(())
441
+ }
442
+
443
+ fn parse_function_section(
444
+ &mut self,
445
+ functions: &mut wasm_encoder::FunctionSection,
446
+ section: wasmparser::FunctionSectionReader<'_>,
447
+ ) -> Result<(), ReencodeError<Self::Error>> {
448
+ for function in section.clone() {
449
+ self.function_types.push(function?);
450
+ }
451
+ if self.function_types.is_empty() {
452
+ return Err(meter_error("instrumented module has no gas function"));
453
+ }
454
+ wasm_encoder::reencode::utils::parse_function_section(self, functions, section)?;
455
+ functions.function(self.safepoint_type());
456
+ Ok(())
457
+ }
458
+
459
+ fn parse_code_section(
460
+ &mut self,
461
+ code: &mut wasm_encoder::CodeSection,
462
+ section: wasmparser::CodeSectionReader<'_>,
463
+ ) -> Result<(), ReencodeError<Self::Error>> {
464
+ wasm_encoder::reencode::utils::parse_code_section(self, code, section)?;
465
+ code.function(&self.slow_body());
466
+ Ok(())
467
+ }
468
+
469
+ fn parse_function_body(
470
+ &mut self,
471
+ code: &mut wasm_encoder::CodeSection,
472
+ body: wasmparser::FunctionBody<'_>,
473
+ ) -> Result<(), ReencodeError<Self::Error>> {
474
+ let ordinal = self.defined_functions;
475
+ self.defined_functions += 1;
476
+ if ordinal + 1 == self.function_types.len() {
477
+ return wasm_encoder::reencode::utils::parse_function_body(self, code, body);
478
+ }
479
+ let parameterized = self
480
+ .function_types
481
+ .get(ordinal)
482
+ .and_then(|ty| self.parameterized_types.get(*ty as usize))
483
+ .copied()
484
+ .unwrap_or(true);
485
+ let loops = has_loop(&body)?;
486
+ let extra_local = !parameterized && body.get_locals_reader()?.get_count() == 0 && loops;
487
+ let function = self.encode_body(&body, extra_local)?;
488
+ code.function(&function);
489
+ Ok(())
490
+ }
491
+
98
492
  fn parse_global_section(
99
493
  &mut self,
100
494
  globals: &mut wasm_encoder::GlobalSection,
101
495
  section: wasmparser::GlobalSectionReader<'_>,
102
496
  ) -> Result<(), ReencodeError<Self::Error>> {
103
- let meter_ordinal = section.count().checked_sub(1).ok_or_else(|| {
104
- ReencodeError::UserError(MeterInitializationError(
105
- "instrumented module has no meter global".to_string(),
106
- ))
107
- })?;
497
+ self.defined_globals = section.count();
498
+ let meter_ordinal = section
499
+ .count()
500
+ .checked_sub(1)
501
+ .ok_or_else(|| meter_error("instrumented module has no meter global"))?;
108
502
  for (ordinal, global) in section.into_iter().enumerate() {
109
503
  let global = global?;
110
504
  if u32::try_from(ordinal).ok() == Some(meter_ordinal) {
111
505
  if global.ty.content_type != wasmparser::ValType::I64 || !global.ty.mutable {
112
- return Err(ReencodeError::UserError(MeterInitializationError(
113
- "instrumented meter global has an unexpected type".to_string(),
114
- )));
506
+ return Err(meter_error(
507
+ "instrumented meter global has an unexpected type",
508
+ ));
115
509
  }
116
510
  globals.global(
117
511
  self.global_type(global.ty)?,
@@ -121,6 +515,14 @@ impl Reencode for MeterInitializer {
121
515
  wasm_encoder::reencode::utils::parse_global(self, globals, global)?;
122
516
  }
123
517
  }
518
+ globals.global(
519
+ wasm_encoder::GlobalType {
520
+ val_type: wasm_encoder::ValType::I64,
521
+ mutable: true,
522
+ shared: false,
523
+ },
524
+ &wasm_encoder::ConstExpr::i64_const((self.budget - SAFEPOINT_INTERVAL).max(0)),
525
+ );
124
526
  Ok(())
125
527
  }
126
528
  }
@@ -128,12 +530,22 @@ impl Reencode for MeterInitializer {
128
530
  fn set_initial_meter_budget(wasm: &[u8], budget: i64) -> Result<Vec<u8>, String> {
129
531
  validate_meter_global_position(wasm)?;
130
532
  let mut module = wasm_encoder::Module::new();
131
- MeterInitializer { budget }
533
+ MeterInitializer::new(budget)
132
534
  .parse_core_module(&mut module, wasmparser::Parser::new(0), wasm)
133
535
  .map_err(|error| format!("failed to initialize weighted meter: {error}"))?;
134
536
  Ok(module.finish())
135
537
  }
136
538
 
539
+ fn has_loop(body: &wasmparser::FunctionBody<'_>) -> Result<bool, wasmparser::BinaryReaderError> {
540
+ let mut operators = body.get_operators_reader()?;
541
+ while !operators.eof() {
542
+ if matches!(operators.read()?, wasmparser::Operator::Loop { .. }) {
543
+ return Ok(true);
544
+ }
545
+ }
546
+ Ok(false)
547
+ }
548
+
137
549
  fn validate_meter_global_position(wasm: &[u8]) -> Result<(), String> {
138
550
  let mut imported_globals = 0_u32;
139
551
  let mut defined_globals = 0_u32;
@@ -271,6 +683,36 @@ pub fn remaining_points(
271
683
  }
272
684
  }
273
685
 
686
+ /// Provides `wasm_oj_metering.safepoint`, which the meter calls every `SAFEPOINT_INTERVAL` units.
687
+ /// Natively it does nothing. In a browser it calls `Atomics.wait` with a value that never matches,
688
+ /// which returns at once; JavaScriptCore checks for Worker termination there, which it never does
689
+ /// inside Wasm, so `terminate()` stops a running program within one interval in WebKit too.
690
+ pub fn attach_safepoint(store: &mut impl AsStoreMut, imports: &mut Imports) {
691
+ imports.define(
692
+ METERING_MODULE,
693
+ SAFEPOINT_NAME,
694
+ Function::new_typed(store, safepoint),
695
+ );
696
+ }
697
+
698
+ #[cfg(not(target_arch = "wasm32"))]
699
+ fn safepoint() {}
700
+
701
+ #[cfg(target_arch = "wasm32")]
702
+ fn safepoint() {
703
+ thread_local! {
704
+ static CHECKPOINT: Option<js_sys::Int32Array> = js_sys::Reflect::get(&js_sys::global(), &"SharedArrayBuffer".into())
705
+ .ok()
706
+ .filter(|constructor| constructor.is_function())
707
+ .map(|_| js_sys::Int32Array::new(&js_sys::SharedArrayBuffer::new(4)));
708
+ }
709
+ CHECKPOINT.with(|checkpoint| {
710
+ if let Some(checkpoint) = checkpoint {
711
+ let _ = js_sys::Atomics::wait_with_timeout(checkpoint, 0, 1, 0.0);
712
+ }
713
+ });
714
+ }
715
+
274
716
  fn weighted_instruction_cost(operator: &Operator) -> Option<u32> {
275
717
  let debug = format!("{operator:?}");
276
718
  weighted_opcode_cost(debug.split_whitespace().next().unwrap_or("UNKNOWN"))
@@ -406,10 +848,12 @@ fn wark_v03_opcode_cost(opcode: &str) -> u32 {
406
848
 
407
849
  #[cfg(test)]
408
850
  mod tests {
409
- use super::{METER_MODEL, instrument_wasm, weighted_opcode_cost};
851
+ use super::{METER_MODEL, SAFEPOINT_INTERVAL, instrument_wasm, weighted_opcode_cost};
852
+ use crate::{DeterminismConfig, ExecutionTermination, ResourcePolicy, RunRequest};
410
853
  use std::borrow::Cow;
854
+ use std::collections::BTreeMap;
411
855
  use wasm_encoder::{Encode, Section};
412
- use wasmparser::{ExternalKind, Parser, Payload};
856
+ use wasmparser::{ExternalKind, Parser, Payload, ValType};
413
857
 
414
858
  #[test]
415
859
  fn instrumentation_adds_the_metering_global() {
@@ -488,6 +932,48 @@ mod tests {
488
932
  assert_eq!(weighted_opcode_cost("AtomicFence"), Some(1000));
489
933
  }
490
934
 
935
+ #[test]
936
+ fn functions_with_a_loop_and_no_params_or_locals_get_one_unused_local() {
937
+ let wasm = wat::parse_str(
938
+ r#"(module
939
+ (memory (export "memory") 1)
940
+ (func (export "_start") (loop (br 0)))
941
+ (func (local i64) (loop (br 0)))
942
+ (func (param i32) (loop (br 0)))
943
+ (func nop)
944
+ (func (block (loop (br 1)))))"#,
945
+ )
946
+ .unwrap();
947
+ let metered = instrument_wasm(&wasm, 1_000_000).unwrap();
948
+ let locals = Parser::new(0)
949
+ .parse_all(&metered.wasm)
950
+ .filter_map(Result::ok)
951
+ .filter_map(|payload| match payload {
952
+ Payload::CodeSectionEntry(body) => Some(
953
+ body.get_locals_reader()
954
+ .unwrap()
955
+ .into_iter()
956
+ .map(|local| local.unwrap())
957
+ .collect::<Vec<_>>(),
958
+ ),
959
+ _ => None,
960
+ })
961
+ .collect::<Vec<_>>();
962
+ let i32_local = vec![(1, ValType::I32)];
963
+ assert_eq!(
964
+ locals,
965
+ [
966
+ i32_local.clone(),
967
+ vec![(1, ValType::I64)],
968
+ vec![],
969
+ vec![],
970
+ i32_local,
971
+ vec![],
972
+ vec![(1, ValType::I64)],
973
+ ]
974
+ );
975
+ }
976
+
491
977
  #[test]
492
978
  fn reports_wark_compatible_static_operation_counts() {
493
979
  let wasm = wat::parse_str(
@@ -499,4 +985,235 @@ mod tests {
499
985
  assert_eq!(metered.operations.get("I32Add"), Some(&1));
500
986
  assert_eq!(metered.operations.get("Drop"), Some(&1));
501
987
  }
988
+
989
+ fn looping(iterations: u32) -> Vec<u8> {
990
+ wat::parse_str(format!(
991
+ r#"(module
992
+ (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32)))
993
+ (memory (export "memory") 1)
994
+ (func (export "_start")
995
+ (local $remaining i32)
996
+ i32.const {iterations} local.set $remaining
997
+ (loop $again
998
+ local.get $remaining i32.const 1 i32.sub local.tee $remaining
999
+ br_if $again)))"#
1000
+ ))
1001
+ .unwrap()
1002
+ }
1003
+
1004
+ fn run(wasm: &[u8], instruction_budget: u64) -> crate::RunResult {
1005
+ crate::run(RunRequest {
1006
+ wasm: wasm.to_vec(),
1007
+ args: Vec::new(),
1008
+ env: BTreeMap::new(),
1009
+ stdin: Vec::new(),
1010
+ files: BTreeMap::new(),
1011
+ output_paths: Vec::new(),
1012
+ cwd: Some("/".to_string()),
1013
+ startup_entropy_bytes: 0,
1014
+ determinism: DeterminismConfig {
1015
+ random_seed: 7,
1016
+ realtime_epoch_ms: 946_684_800_000,
1017
+ clock_step_ns: 1_000_000,
1018
+ },
1019
+ resources: ResourcePolicy {
1020
+ instruction_budget,
1021
+ logical_time_limit_ms: 60_000,
1022
+ memory_limit_bytes: 64 * 1024 * 1024,
1023
+ output_limit_bytes: 1024,
1024
+ filesystem_write_limit_bytes: 64 * 1024 * 1024,
1025
+ filesystem_entry_limit: 4_096,
1026
+ },
1027
+ })
1028
+ .unwrap()
1029
+ }
1030
+
1031
+ #[test]
1032
+ fn costs_and_exhaustion_do_not_depend_on_safepoint_intervals() {
1033
+ let wasm = looping(3_000_000);
1034
+ let generous = run(&wasm, 10_000_000_000);
1035
+ assert_eq!(generous.termination, ExecutionTermination::Exited);
1036
+ let cost = generous.metrics.cost;
1037
+ assert!(cost > 5 * SAFEPOINT_INTERVAL as u64);
1038
+ assert_eq!(run(&wasm, cost * 7).metrics.cost, cost);
1039
+
1040
+ let exact = run(&wasm, cost);
1041
+ assert_eq!(exact.termination, ExecutionTermination::Exited);
1042
+ assert_eq!(exact.metrics.cost, cost);
1043
+
1044
+ let short = run(&wasm, cost - 1);
1045
+ assert_eq!(short.termination, ExecutionTermination::InstructionLimit);
1046
+ assert_eq!(short.metrics.cost, cost - 1);
1047
+
1048
+ let within_one_interval = looping(1_000);
1049
+ let small = run(&within_one_interval, 1_000_000).metrics.cost;
1050
+ assert_eq!(run(&within_one_interval, small).metrics.cost, small);
1051
+ assert_eq!(
1052
+ run(&within_one_interval, small - 1).termination,
1053
+ ExecutionTermination::InstructionLimit
1054
+ );
1055
+ }
1056
+
1057
+ #[test]
1058
+ fn the_meter_calls_the_safepoint_once_per_interval() {
1059
+ let recursing = wat::parse_str(
1060
+ r#"(module
1061
+ (import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32)))
1062
+ (memory (export "memory") 1)
1063
+ (func $fib (param $n i32) (result i32)
1064
+ local.get $n i32.const 2 i32.lt_u
1065
+ if (result i32) local.get $n
1066
+ else
1067
+ local.get $n i32.const 1 i32.sub call $fib
1068
+ local.get $n i32.const 2 i32.sub call $fib
1069
+ i32.add
1070
+ end)
1071
+ (func (export "_start") i32.const 25 call $fib drop))"#,
1072
+ )
1073
+ .unwrap();
1074
+ for wasm in [looping(3_000_000), recursing] {
1075
+ let (calls, cost) = count_safepoints(&wasm);
1076
+ assert!(cost > 5 * SAFEPOINT_INTERVAL as u64);
1077
+ assert!(calls.abs_diff(cost / SAFEPOINT_INTERVAL as u64) <= 1);
1078
+ }
1079
+ }
1080
+
1081
+ #[test]
1082
+ fn safepoint_loops_keep_their_branches_and_results() {
1083
+ use wasmer::{Function, Imports, Instance, Module, Store, Value};
1084
+
1085
+ let wasm = wat::parse_str(
1086
+ r#"(module
1087
+ (memory (export "memory") 1)
1088
+ (func (export "compute") (result i32)
1089
+ (local $i i32) (local $j i32) (local $acc i32)
1090
+ (block $done
1091
+ (loop $outer
1092
+ (local.set $j (i32.const 0))
1093
+ (block $inner_done
1094
+ (loop $inner
1095
+ (local.set $acc
1096
+ (i32.add (local.get $acc) (i32.mul (local.get $i) (local.get $j))))
1097
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))
1098
+ (br_if $done (i32.eq (local.get $acc) (i32.const -1)))
1099
+ (br_table $inner $inner_done $done
1100
+ (i32.gt_u (local.get $j) (i32.const 500)))))
1101
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
1102
+ (br_if $outer (i32.lt_u (local.get $i) (i32.const 3000)))))
1103
+ (local.get $acc)
1104
+ (block $value (result i32)
1105
+ (loop $early (result i32) (br $value (i32.const 3))))
1106
+ (i32.add)
1107
+ (loop $fallthrough (result i32) (i32.const 7))
1108
+ (i32.add)
1109
+ (i32.const 5)
1110
+ (loop $param (param i32) (result i32)
1111
+ i32.const 1
1112
+ i32.sub
1113
+ local.tee $j
1114
+ local.get $j
1115
+ br_if $param)
1116
+ (i32.add)))"#,
1117
+ )
1118
+ .unwrap();
1119
+ let mut results = Vec::new();
1120
+ for module in [
1121
+ wasm.clone(),
1122
+ instrument_wasm(&wasm, 10_000_000_000).unwrap().wasm,
1123
+ ] {
1124
+ let mut store = Store::default();
1125
+ let module = Module::new(&store, &module).unwrap();
1126
+ let mut imports = Imports::new();
1127
+ imports.define(
1128
+ "wasm_oj_metering",
1129
+ "safepoint",
1130
+ Function::new_typed(&mut store, || {}),
1131
+ );
1132
+ let instance = Instance::new(&mut store, &module, &imports).unwrap();
1133
+ let compute = instance.exports.get_function("compute").unwrap();
1134
+ results.push(compute.call(&mut store, &[]).unwrap().to_vec());
1135
+ if let Ok(state) = super::meter_state(&instance) {
1136
+ let super::CostPoints::Remaining(remaining) =
1137
+ super::remaining_points(&mut store, &state).unwrap()
1138
+ else {
1139
+ panic!("meter exhausted");
1140
+ };
1141
+ assert!(10_000_000_000 - remaining > 5 * SAFEPOINT_INTERVAL as u64);
1142
+ }
1143
+ }
1144
+ assert_eq!(results[0], results[1]);
1145
+ assert!(matches!(results[0][..], [Value::I32(_)]));
1146
+ }
1147
+
1148
+ fn count_safepoints(wasm: &[u8]) -> (u64, u64) {
1149
+ use std::sync::Arc;
1150
+ use std::sync::atomic::{AtomicU64, Ordering};
1151
+ use wasmer::{Function, FunctionEnv, FunctionEnvMut, Imports, Instance, Module, Store};
1152
+
1153
+ let budget = 10_000_000_000;
1154
+ let metered = instrument_wasm(wasm, budget).unwrap();
1155
+ let mut store = Store::default();
1156
+ let module = Module::new(&store, &metered.wasm).unwrap();
1157
+ let calls = Arc::new(AtomicU64::new(0));
1158
+ let env = FunctionEnv::new(&mut store, calls.clone());
1159
+ let mut imports = Imports::new();
1160
+ imports.define(
1161
+ "wasm_oj_metering",
1162
+ "safepoint",
1163
+ Function::new_typed_with_env(
1164
+ &mut store,
1165
+ &env,
1166
+ |env: FunctionEnvMut<Arc<AtomicU64>>| {
1167
+ env.data().fetch_add(1, Ordering::Relaxed);
1168
+ },
1169
+ ),
1170
+ );
1171
+ imports.define(
1172
+ "wasi_snapshot_preview1",
1173
+ "proc_exit",
1174
+ Function::new_typed(&mut store, |_: i32| {}),
1175
+ );
1176
+ let instance = Instance::new(&mut store, &module, &imports).unwrap();
1177
+ instance
1178
+ .exports
1179
+ .get_function("_start")
1180
+ .unwrap()
1181
+ .call(&mut store, &[])
1182
+ .unwrap();
1183
+ let state = super::meter_state(&instance).unwrap();
1184
+ let super::CostPoints::Remaining(remaining) =
1185
+ super::remaining_points(&mut store, &state).unwrap()
1186
+ else {
1187
+ panic!("meter exhausted");
1188
+ };
1189
+ (calls.load(Ordering::Relaxed), budget - remaining)
1190
+ }
1191
+
1192
+ #[test]
1193
+ fn modules_without_imports_receive_the_safepoint_import() {
1194
+ let wasm =
1195
+ wat::parse_str("(module (memory (export \"memory\") 1) (func (export \"_start\")))")
1196
+ .unwrap();
1197
+ let metered = instrument_wasm(&wasm, 1_000_000).unwrap();
1198
+ let imports = Parser::new(0)
1199
+ .parse_all(&metered.wasm)
1200
+ .filter_map(Result::ok)
1201
+ .filter_map(|payload| match payload {
1202
+ Payload::ImportSection(section) => Some(
1203
+ section
1204
+ .into_imports()
1205
+ .map(|import| {
1206
+ let import = import.unwrap();
1207
+ format!("{}.{}", import.module, import.name)
1208
+ })
1209
+ .collect::<Vec<_>>(),
1210
+ ),
1211
+ _ => None,
1212
+ })
1213
+ .collect::<Vec<_>>();
1214
+ assert_eq!(imports, [vec!["wasm_oj_metering.safepoint".to_string()]]);
1215
+ wasmparser::Validator::new()
1216
+ .validate_all(&metered.wasm)
1217
+ .unwrap();
1218
+ }
502
1219
  }
@@ -2,7 +2,9 @@ use crate::capabilities::attach_capability_denials;
2
2
  use crate::deterministic::{VirtualClock, attach_deterministic_imports};
3
3
  use crate::filesystem::{read_files_bounded, runtime_project_files};
4
4
  use crate::memory::LimitingTunables;
5
- use crate::meter::{CostPoints, METER_MODEL, instrument_wasm, meter_state, remaining_points};
5
+ use crate::meter::{
6
+ CostPoints, METER_MODEL, attach_safepoint, instrument_wasm, meter_state, remaining_points,
7
+ };
6
8
  use crate::module_imports::attach_imported_memory;
7
9
  use crate::module_policy::{DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit};
8
10
  use crate::output::{OutputBudget, OutputCapture};
@@ -105,6 +107,7 @@ fn run_in_runtime(request: RunRequest) -> Result<RunResult, RunError> {
105
107
  clock.clone(),
106
108
  request.startup_entropy_bytes,
107
109
  );
110
+ attach_safepoint(&mut store, &mut imports);
108
111
  attach_capability_denials(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
109
112
  let imported_memory =
110
113
  attach_imported_memory(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
@@ -2,7 +2,9 @@ use super::web_runtime::runtime_with_engine;
2
2
  use crate::capabilities::attach_capability_denials;
3
3
  use crate::deterministic::{VirtualClock, attach_deterministic_imports};
4
4
  use crate::filesystem::{read_files_bounded, runtime_project_files};
5
- use crate::meter::{CostPoints, METER_MODEL, instrument_wasm, meter_state, remaining_points};
5
+ use crate::meter::{
6
+ CostPoints, METER_MODEL, attach_safepoint, instrument_wasm, meter_state, remaining_points,
7
+ };
6
8
  use crate::module_imports::attach_imported_memory;
7
9
  use crate::module_policy::{DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit};
8
10
  use crate::output::{CappedOutput, OutputBudget, OutputCapture};
@@ -102,6 +104,7 @@ pub(super) fn execute(
102
104
  clock.clone(),
103
105
  request.startup_entropy_bytes,
104
106
  );
107
+ attach_safepoint(&mut store, &mut imports);
105
108
  attach_capability_denials(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
106
109
  let imported_memory =
107
110
  attach_imported_memory(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
@@ -291,13 +291,18 @@ impl std::fmt::Debug for StreamOutput {
291
291
  }
292
292
 
293
293
  impl AsyncWrite for StreamOutput {
294
+ /// Drops bytes written after the peer closed its stdin, as native does; they are already in
295
+ /// the transcript.
294
296
  fn poll_write(
295
297
  mut self: Pin<&mut Self>,
296
298
  context: &mut Context<'_>,
297
299
  buffer: &[u8],
298
300
  ) -> Poll<io::Result<usize>> {
299
301
  match Pin::new(&mut self.capture).poll_write(context, buffer) {
300
- Poll::Ready(Ok(written)) => Poll::Ready(self.streams.write(&buffer[..written])),
302
+ Poll::Ready(Ok(written)) => Poll::Ready(match self.streams.write(&buffer[..written]) {
303
+ Err(error) if error.kind() == io::ErrorKind::BrokenPipe => Ok(written),
304
+ result => result,
305
+ }),
301
306
  result => result,
302
307
  }
303
308
  }
package/dist/index.js CHANGED
@@ -775,8 +775,8 @@ Object.freeze({
775
775
  });
776
776
  new TextEncoder();
777
777
  Object.freeze({
778
- runtimeCoreWasmSha256: "0ae00fc646f529aafb5d1a68758220e6428f6c565e8e621eeb6c8f14ac5d4a10",
779
- runtimeSourceRootSha256: "a7e9071511d127e3b14920558c7c38c27a1fa19d98906c8476c1df8041a9c3bb",
778
+ runtimeCoreWasmSha256: "077aa936a3382a242eee7222fc17369769cb5ad265303d730ffc45ee192367e9",
779
+ runtimeSourceRootSha256: "c65b3bbcad41359f86b8f1d1315af8f079b44f0e9084ace0d6d50c0ec418415b",
780
780
  wasmerNativeVersion: "7.2.1",
781
781
  wasmerSdkVersion: "0.10.0",
782
782
  wasmerSdkWasmSha256: "49a6646209f5ab5e7c737eac33407d87d9a9959ac83e5ecaaab9261b2323589e",
@@ -787,7 +787,7 @@ Object.freeze({
787
787
  * Release verification independently checks the component bytes before this
788
788
  * identity is admitted into a calibrated release.
789
789
  */
790
- var WASM_OJ_RUNTIME_IDENTITY_SHA256 = "e5680beae9bd832ed2b679267250ce1efc7d104dd005bb155135664b40f961fb";
790
+ var WASM_OJ_RUNTIME_IDENTITY_SHA256 = "47684bee57aafd163b18a787fa16eed33cab3ec3c0005c3c46bb4f1803ffc835";
791
791
  //#endregion
792
792
  //#region src/core/cost-profile.ts
793
793
  function coordinates(language, target, optimization) {
@@ -482,8 +482,8 @@ Object.freeze({
482
482
  });
483
483
  new TextEncoder();
484
484
  Object.freeze({
485
- runtimeCoreWasmSha256: "0ae00fc646f529aafb5d1a68758220e6428f6c565e8e621eeb6c8f14ac5d4a10",
486
- runtimeSourceRootSha256: "a7e9071511d127e3b14920558c7c38c27a1fa19d98906c8476c1df8041a9c3bb",
485
+ runtimeCoreWasmSha256: "077aa936a3382a242eee7222fc17369769cb5ad265303d730ffc45ee192367e9",
486
+ runtimeSourceRootSha256: "c65b3bbcad41359f86b8f1d1315af8f079b44f0e9084ace0d6d50c0ec418415b",
487
487
  wasmerNativeVersion: "7.2.1",
488
488
  wasmerSdkVersion: "0.10.0",
489
489
  wasmerSdkWasmSha256: "49a6646209f5ab5e7c737eac33407d87d9a9959ac83e5ecaaab9261b2323589e",
@@ -494,7 +494,7 @@ Object.freeze({
494
494
  * Release verification independently checks the component bytes before this
495
495
  * identity is admitted into a calibrated release.
496
496
  */
497
- var WASM_OJ_RUNTIME_IDENTITY_SHA256 = "e5680beae9bd832ed2b679267250ce1efc7d104dd005bb155135664b40f961fb";
497
+ var WASM_OJ_RUNTIME_IDENTITY_SHA256 = "47684bee57aafd163b18a787fa16eed33cab3ec3c0005c3c46bb4f1803ffc835";
498
498
  //#endregion
499
499
  //#region src/core/cost-profile.ts
500
500
  function coordinates(language, target, optimization) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wasm-oj/server",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Node.js compiler and runner host for the WASM-OJ SDK.",
5
5
  "license": "MIT",
6
6
  "author": "JacobLinCool",
@@ -58,8 +58,8 @@
58
58
  "node": ">=24.18.0 <25"
59
59
  },
60
60
  "dependencies": {
61
- "@wasm-oj/contracts": "0.2.4",
62
- "@wasm-oj/core": "0.2.4",
61
+ "@wasm-oj/contracts": "0.2.5",
62
+ "@wasm-oj/core": "0.2.5",
63
63
  "@wasmer/sdk": "0.10.0",
64
64
  "fflate": "0.8.3"
65
65
  },