@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,549 @@
1
+ #[cfg(target_arch = "wasm32")]
2
+ use js_sys::{BigInt, WebAssembly};
3
+ use meter_wasmparser::Operator;
4
+ use radix_wasm_instrument::gas_metering::{self, MemoryGrowCost, Rules};
5
+ use radix_wasm_instrument::utils::module_info::ModuleInfo;
6
+ use std::borrow::Cow;
7
+ use std::collections::BTreeMap;
8
+ use wasm_encoder::reencode::{Error as ReencodeError, Reencode};
9
+ use wasm_encoder::{Encode, Section};
10
+ #[cfg(not(target_arch = "wasm32"))]
11
+ use wasmer::Global;
12
+ #[cfg(target_arch = "wasm32")]
13
+ use wasmer::js::AsJs;
14
+ use wasmer::{AsStoreMut, Instance};
15
+
16
+ pub const METER_MODEL: &str = "weighted";
17
+ const METERING_MODULE: &str = "wasm_oj_metering";
18
+ const GAS_COUNTER_NAME: &str = "gas_counter";
19
+ pub(crate) const CONTESTANT_METERING_MODULE: &str = "wasm_oj_contestant_metering";
20
+ pub(crate) const INTERACTOR_METERING_MODULE: &str = "wasm_oj_interactor_metering";
21
+ pub(crate) const HOST_GAS_FUNCTION: &str = "charge";
22
+
23
+ #[derive(Debug)]
24
+ pub struct InstrumentedModule {
25
+ pub wasm: Vec<u8>,
26
+ /// Static counts of the original module's operators, matching WARK's
27
+ /// `RunResult.operations` semantics. Meter-injected operators are excluded.
28
+ pub operations: BTreeMap<String, u64>,
29
+ }
30
+
31
+ #[derive(Debug, Default)]
32
+ struct WeightedRules;
33
+
34
+ impl Rules for WeightedRules {
35
+ fn instruction_cost(&self, instruction: &Operator) -> Option<u32> {
36
+ weighted_instruction_cost(instruction)
37
+ }
38
+
39
+ fn memory_grow_cost(&self) -> MemoryGrowCost {
40
+ MemoryGrowCost::Free
41
+ }
42
+
43
+ fn call_per_local_cost(&self) -> u32 {
44
+ 0
45
+ }
46
+ }
47
+
48
+ #[derive(Debug, Eq, PartialEq)]
49
+ pub enum CostPoints {
50
+ Remaining(u64),
51
+ Exhausted,
52
+ }
53
+
54
+ #[derive(Clone, Debug)]
55
+ pub struct MeterState {
56
+ #[cfg(not(target_arch = "wasm32"))]
57
+ gas_counter: Global,
58
+ #[cfg(target_arch = "wasm32")]
59
+ gas_counter: WebAssembly::Global,
60
+ }
61
+
62
+ pub fn instrument_wasm(wasm: &[u8], budget: u64) -> Result<InstrumentedModule, String> {
63
+ let initial_budget = i64::try_from(budget)
64
+ .map_err(|_| format!("budget {budget} exceeds the signed 64-bit metering range"))?;
65
+ let runtime_sections = runtime_custom_sections(wasm)?;
66
+ let executable = canonicalize_custom_sections(wasm)?;
67
+ let operations = inspect_weighted_opcodes(&executable)?;
68
+ let mut module = ModuleInfo::new(&executable)
69
+ .map_err(|error| format!("failed to parse module for weighted metering: {error}"))?;
70
+ let backend = gas_metering::mutable_global::Injector::new(METERING_MODULE, GAS_COUNTER_NAME);
71
+ let metered = gas_metering::inject(&mut module, backend, &WeightedRules)
72
+ .map_err(|error| format!("failed to inject weighted metering: {error}"))?;
73
+ let mut metered = set_initial_meter_budget(&metered, initial_budget)?;
74
+ for (name, data) in runtime_sections {
75
+ let section = wasm_encoder::CustomSection {
76
+ name: Cow::Owned(name),
77
+ data: Cow::Owned(data),
78
+ };
79
+ metered.push(section.id());
80
+ section.encode(&mut metered);
81
+ }
82
+ Ok(InstrumentedModule {
83
+ wasm: metered,
84
+ operations,
85
+ })
86
+ }
87
+
88
+ pub(crate) fn instrument_wasm_with_host_meter(
89
+ wasm: &[u8],
90
+ metering_module: &'static str,
91
+ ) -> Result<InstrumentedModule, String> {
92
+ let runtime_sections = runtime_custom_sections(wasm)?;
93
+ let executable = canonicalize_custom_sections(wasm)?;
94
+ let operations = inspect_weighted_opcodes(&executable)?;
95
+ let mut module = ModuleInfo::new(&executable)
96
+ .map_err(|error| format!("failed to parse module for weighted metering: {error}"))?;
97
+ let backend = gas_metering::host_function::Injector::new(metering_module, HOST_GAS_FUNCTION);
98
+ let mut metered = gas_metering::inject(&mut module, backend, &WeightedRules)
99
+ .map_err(|error| format!("failed to inject weighted host metering: {error}"))?;
100
+ for (name, data) in runtime_sections {
101
+ let section = wasm_encoder::CustomSection {
102
+ name: Cow::Owned(name),
103
+ data: Cow::Owned(data),
104
+ };
105
+ metered.push(section.id());
106
+ section.encode(&mut metered);
107
+ }
108
+ Ok(InstrumentedModule {
109
+ wasm: metered,
110
+ operations,
111
+ })
112
+ }
113
+
114
+ #[derive(Debug)]
115
+ struct MeterInitializationError(String);
116
+
117
+ impl std::fmt::Display for MeterInitializationError {
118
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119
+ formatter.write_str(&self.0)
120
+ }
121
+ }
122
+
123
+ impl std::error::Error for MeterInitializationError {}
124
+
125
+ struct MeterInitializer {
126
+ budget: i64,
127
+ }
128
+
129
+ impl Reencode for MeterInitializer {
130
+ type Error = MeterInitializationError;
131
+
132
+ fn parse_global_section(
133
+ &mut self,
134
+ globals: &mut wasm_encoder::GlobalSection,
135
+ section: wasmparser::GlobalSectionReader<'_>,
136
+ ) -> Result<(), ReencodeError<Self::Error>> {
137
+ let meter_ordinal = section.count().checked_sub(1).ok_or_else(|| {
138
+ ReencodeError::UserError(MeterInitializationError(
139
+ "instrumented module has no meter global".to_string(),
140
+ ))
141
+ })?;
142
+ for (ordinal, global) in section.into_iter().enumerate() {
143
+ let global = global?;
144
+ if u32::try_from(ordinal).ok() == Some(meter_ordinal) {
145
+ if global.ty.content_type != wasmparser::ValType::I64 || !global.ty.mutable {
146
+ return Err(ReencodeError::UserError(MeterInitializationError(
147
+ "instrumented meter global has an unexpected type".to_string(),
148
+ )));
149
+ }
150
+ globals.global(
151
+ self.global_type(global.ty)?,
152
+ &wasm_encoder::ConstExpr::i64_const(self.budget),
153
+ );
154
+ } else {
155
+ wasm_encoder::reencode::utils::parse_global(self, globals, global)?;
156
+ }
157
+ }
158
+ Ok(())
159
+ }
160
+ }
161
+
162
+ fn set_initial_meter_budget(wasm: &[u8], budget: i64) -> Result<Vec<u8>, String> {
163
+ validate_meter_global_position(wasm)?;
164
+ let mut module = wasm_encoder::Module::new();
165
+ MeterInitializer { budget }
166
+ .parse_core_module(&mut module, wasmparser::Parser::new(0), wasm)
167
+ .map_err(|error| format!("failed to initialize weighted meter: {error}"))?;
168
+ Ok(module.finish())
169
+ }
170
+
171
+ fn validate_meter_global_position(wasm: &[u8]) -> Result<(), String> {
172
+ let mut imported_globals = 0_u32;
173
+ let mut defined_globals = 0_u32;
174
+ let mut exported_meter = None;
175
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
176
+ match payload.map_err(|error| format!("failed to inspect weighted meter: {error}"))? {
177
+ wasmparser::Payload::ImportSection(section) => {
178
+ for import in section.into_imports() {
179
+ let import = import.map_err(|error| error.to_string())?;
180
+ if matches!(import.ty, wasmparser::TypeRef::Global(_)) {
181
+ imported_globals = imported_globals.saturating_add(1);
182
+ }
183
+ }
184
+ }
185
+ wasmparser::Payload::GlobalSection(section) => defined_globals = section.count(),
186
+ wasmparser::Payload::ExportSection(section) => {
187
+ for export in section {
188
+ let export = export.map_err(|error| error.to_string())?;
189
+ if export.name == GAS_COUNTER_NAME
190
+ && export.kind == wasmparser::ExternalKind::Global
191
+ {
192
+ exported_meter = Some(export.index);
193
+ }
194
+ }
195
+ }
196
+ _ => {}
197
+ }
198
+ }
199
+ let expected = imported_globals
200
+ .checked_add(defined_globals)
201
+ .and_then(|count| count.checked_sub(1))
202
+ .ok_or_else(|| "instrumented module has no defined meter global".to_string())?;
203
+ if exported_meter != Some(expected) {
204
+ return Err("instrumented meter is not the final defined global".to_string());
205
+ }
206
+ Ok(())
207
+ }
208
+
209
+ /// Index-bearing metadata becomes stale when the metering pass inserts
210
+ /// functions. The WASIX `dylink.0` section is runtime semantics, however, and
211
+ /// must be restored after instrumentation so dynamically linked modules remain
212
+ /// valid. Radix's encoder intentionally omits all custom sections.
213
+ fn canonicalize_custom_sections(wasm: &[u8]) -> Result<Vec<u8>, String> {
214
+ #[derive(Debug)]
215
+ struct ExecutableOnly;
216
+
217
+ impl Reencode for ExecutableOnly {
218
+ type Error = std::convert::Infallible;
219
+
220
+ fn parse_custom_section(
221
+ &mut self,
222
+ _module: &mut wasm_encoder::Module,
223
+ _section: wasmparser::CustomSectionReader<'_>,
224
+ ) -> Result<(), ReencodeError<Self::Error>> {
225
+ Ok(())
226
+ }
227
+ }
228
+
229
+ let mut module = wasm_encoder::Module::new();
230
+ ExecutableOnly
231
+ .parse_core_module(&mut module, wasmparser::Parser::new(0), wasm)
232
+ .map_err(|error| format!("failed to canonicalize executable sections: {error}"))?;
233
+ Ok(module.finish())
234
+ }
235
+
236
+ fn runtime_custom_sections(wasm: &[u8]) -> Result<Vec<(String, Vec<u8>)>, String> {
237
+ let mut sections = Vec::new();
238
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
239
+ let payload =
240
+ payload.map_err(|error| format!("failed to inspect custom sections: {error}"))?;
241
+ if let wasmparser::Payload::CustomSection(section) = payload
242
+ && section.name() == "dylink.0"
243
+ {
244
+ sections.push((section.name().to_string(), section.data().to_vec()));
245
+ }
246
+ }
247
+ Ok(sections)
248
+ }
249
+
250
+ fn inspect_weighted_opcodes(wasm: &[u8]) -> Result<BTreeMap<String, u64>, String> {
251
+ let mut operations = BTreeMap::new();
252
+ for payload in wasmparser::Parser::new(0).parse_all(wasm) {
253
+ let payload =
254
+ payload.map_err(|error| format!("failed to inspect meter opcodes: {error}"))?;
255
+ if let wasmparser::Payload::CodeSectionEntry(body) = payload {
256
+ let reader = body
257
+ .get_operators_reader()
258
+ .map_err(|error| format!("failed to inspect function opcodes: {error}"))?;
259
+ for operator in reader {
260
+ let operator =
261
+ operator.map_err(|error| format!("failed to read function opcode: {error}"))?;
262
+ let debug = format!("{operator:?}");
263
+ let opcode = debug.split_whitespace().next().unwrap_or("UNKNOWN");
264
+ operations
265
+ .entry(opcode.to_string())
266
+ .and_modify(|count| *count += 1)
267
+ .or_insert(1);
268
+ }
269
+ }
270
+ }
271
+ Ok(operations)
272
+ }
273
+
274
+ pub fn meter_state(store: &mut impl AsStoreMut, instance: &Instance) -> Result<MeterState, String> {
275
+ let gas_counter = instance
276
+ .exports
277
+ .get_global(GAS_COUNTER_NAME)
278
+ .map_err(|error| format!("instrumented module does not export its meter: {error}"))?
279
+ .clone();
280
+
281
+ #[cfg(not(target_arch = "wasm32"))]
282
+ {
283
+ let _ = store;
284
+ Ok(MeterState { gas_counter })
285
+ }
286
+
287
+ #[cfg(target_arch = "wasm32")]
288
+ {
289
+ let js_global: WebAssembly::Global = gas_counter.as_jsvalue(store).into();
290
+ Ok(MeterState {
291
+ gas_counter: js_global,
292
+ })
293
+ }
294
+ }
295
+
296
+ pub fn remaining_points(
297
+ store: &mut impl AsStoreMut,
298
+ meter: &MeterState,
299
+ ) -> Result<CostPoints, String> {
300
+ #[cfg(not(target_arch = "wasm32"))]
301
+ let value = meter
302
+ .gas_counter
303
+ .get(store)
304
+ .i64()
305
+ .ok_or_else(|| "metering global has the wrong type".to_string())?;
306
+
307
+ #[cfg(target_arch = "wasm32")]
308
+ let value = {
309
+ let _ = store;
310
+ i64::try_from(BigInt::from(meter.gas_counter.value()))
311
+ .map_err(|_| "metering global is outside the signed 64-bit range".to_string())?
312
+ };
313
+
314
+ if value < 0 {
315
+ Ok(CostPoints::Exhausted)
316
+ } else {
317
+ Ok(CostPoints::Remaining(value as u64))
318
+ }
319
+ }
320
+
321
+ fn weighted_instruction_cost(operator: &Operator) -> Option<u32> {
322
+ let debug = format!("{operator:?}");
323
+ weighted_opcode_cost(debug.split_whitespace().next().unwrap_or("UNKNOWN"))
324
+ }
325
+
326
+ fn weighted_opcode_cost(opcode: &str) -> Option<u32> {
327
+ Some(wark_v03_opcode_cost(opcode))
328
+ }
329
+
330
+ /// Opcode cost model adapted from Binaryen's optimizer cost analysis and
331
+ /// preserved through WARK 0.3. WARK's 1000-point penalty for every operator
332
+ /// absent from the table, including future instructions, remains an explicit
333
+ /// compatibility rule.
334
+ fn wark_v03_opcode_cost(opcode: &str) -> u32 {
335
+ match opcode {
336
+ "LocalGet" | "Return" | "Unreachable" | "Nop" | "Drop" | "Try" => 0,
337
+ "LocalSet" | "LocalTee" | "GlobalGet" => 1,
338
+ "GlobalSet" => 2,
339
+ "F32Load" | "F64Load" | "I32Load" | "I64Load" | "I32Load8S" | "I32Load8U"
340
+ | "I32Load16S" | "I32Load16U" | "I64Load8S" | "I64Load8U" | "I64Load16S" | "I64Load16U"
341
+ | "I64Load32S" | "I64Load32U" => 1,
342
+ "I32AtomicLoad" | "I32AtomicLoad8U" | "I32AtomicLoad16U" | "I64AtomicLoad"
343
+ | "I64AtomicLoad8U" | "I64AtomicLoad16U" | "I64AtomicLoad32U" => 11,
344
+ "F32Store" | "F64Store" | "I32Store" | "I64Store" | "I32Store8" | "I32Store16"
345
+ | "I64Store8" | "I64Store16" | "I64Store32" => 2,
346
+ "I32AtomicStore" | "I32AtomicStore8" | "I32AtomicStore16" | "I64AtomicStore"
347
+ | "I64AtomicStore8" | "I64AtomicStore16" | "I64AtomicStore32" => 12,
348
+ "F32Const" | "F64Const" | "I32Const" | "I64Const" => 1,
349
+ "F32ConvertI32S" | "F32ConvertI32U" | "F32ConvertI64S" | "F32ConvertI64U"
350
+ | "F64ConvertI32S" | "F64ConvertI32U" | "F64ConvertI64S" | "F64ConvertI64U"
351
+ | "I32ReinterpretF32" | "I64ReinterpretF64" | "F32ReinterpretI32" | "F64ReinterpretI64"
352
+ | "I32WrapI64" | "I32Extend8S" | "I32Extend16S" | "I64Extend8S" | "I64Extend16S"
353
+ | "I64Extend32S" | "I64ExtendI32U" | "I64ExtendI32S" | "F32Trunc" | "F64Trunc"
354
+ | "I32TruncF32S" | "I32TruncF32U" | "I32TruncF64S" | "I32TruncF64U" | "I32TruncSatF32S"
355
+ | "I32TruncSatF32U" | "I32TruncSatF64S" | "I32TruncSatF64U" | "I64TruncF32S"
356
+ | "I64TruncF32U" | "I64TruncF64S" | "I64TruncF64U" | "I64TruncSatF32S"
357
+ | "I64TruncSatF32U" | "I64TruncSatF64S" | "I64TruncSatF64U" | "F32DemoteF64"
358
+ | "F64PromoteF32" | "I32Popcnt" | "I64Popcnt" | "I32Clz" | "I32Ctz" | "I64Clz"
359
+ | "I64Ctz" | "F32Neg" | "F64Neg" | "F32Abs" | "F64Abs" | "F32Ceil" | "F64Ceil"
360
+ | "F32Floor" | "F64Floor" | "F32Nearest" | "F64Nearest" | "I32Eqz" | "I64Eqz" => 1,
361
+ "F32Sqrt" | "F64Sqrt" => 2,
362
+ "F32x4Splat"
363
+ | "F64x2Splat"
364
+ | "I16x8Splat"
365
+ | "I32x4Splat"
366
+ | "I64x2Splat"
367
+ | "I8x16Splat"
368
+ | "V128Not"
369
+ | "V128AnyTrue"
370
+ | "F32x4Abs"
371
+ | "F32x4Neg"
372
+ | "F32x4Sqrt"
373
+ | "F32x4Ceil"
374
+ | "F32x4Floor"
375
+ | "F32x4Trunc"
376
+ | "F32x4Nearest"
377
+ | "F64x2Abs"
378
+ | "F64x2Neg"
379
+ | "F64x2Sqrt"
380
+ | "F64x2Ceil"
381
+ | "F64x2Floor"
382
+ | "F64x2Trunc"
383
+ | "F64x2Nearest"
384
+ | "I8x16Abs"
385
+ | "I8x16Neg"
386
+ | "I8x16AllTrue"
387
+ | "I8x16Bitmask"
388
+ | "I8x16Popcnt"
389
+ | "I16x8Abs"
390
+ | "I16x8Neg"
391
+ | "I16x8AllTrue"
392
+ | "I16x8Bitmask"
393
+ | "I32x4Abs"
394
+ | "I32x4Neg"
395
+ | "I32x4AllTrue"
396
+ | "I32x4Bitmask"
397
+ | "I64x2Abs"
398
+ | "I64x2Neg"
399
+ | "I64x2AllTrue"
400
+ | "I64x2Bitmask"
401
+ | "F32x4ConvertI32x4S"
402
+ | "F32x4ConvertI32x4U"
403
+ | "I32x4TruncSatF32x4S"
404
+ | "I32x4TruncSatF32x4U"
405
+ | "F64x2ConvertLowI32x4S"
406
+ | "F64x2ConvertLowI32x4U"
407
+ | "I32x4TruncSatF64x2SZero"
408
+ | "I32x4TruncSatF64x2UZero"
409
+ | "I16x8ExtAddPairwiseI8x16S"
410
+ | "I16x8ExtAddPairwiseI8x16U"
411
+ | "I32x4ExtAddPairwiseI16x8S"
412
+ | "I32x4ExtAddPairwiseI16x8U"
413
+ | "I16x8ExtendHighI8x16S"
414
+ | "I16x8ExtendLowI8x16S"
415
+ | "I16x8ExtendHighI8x16U"
416
+ | "I16x8ExtendLowI8x16U"
417
+ | "I32x4ExtendHighI16x8S"
418
+ | "I32x4ExtendLowI16x8S"
419
+ | "I32x4ExtendHighI16x8U"
420
+ | "I32x4ExtendLowI16x8U"
421
+ | "I64x2ExtendHighI32x4S"
422
+ | "I64x2ExtendLowI32x4S"
423
+ | "I64x2ExtendHighI32x4U"
424
+ | "I64x2ExtendLowI32x4U"
425
+ | "F32x4DemoteF64x2Zero"
426
+ | "F64x2PromoteLowF32x4"
427
+ | "I32x4RelaxedTruncF32x4S"
428
+ | "I32x4RelaxedTruncF32x4U"
429
+ | "I32x4RelaxedTruncF64x2SZero"
430
+ | "I32x4RelaxedTruncF64x2UZero" => 1,
431
+ "I32Add" | "I32Sub" | "I64Add" | "I64Sub" | "F32Add" | "F32Sub" | "F64Add" | "F64Sub" => 1,
432
+ "I32Mul" | "I64Mul" | "F32Mul" | "F64Mul" => 2,
433
+ "I32DivS" | "I32DivU" | "I32RemS" | "I32RemU" | "I64DivS" | "I64DivU" | "I64RemS"
434
+ | "I64RemU" | "F32Div" | "F64Div" => 3,
435
+ "I32And" | "I32Or" | "I32Xor" | "I32Shl" | "I32ShrS" | "I32ShrU" | "I32Rotl"
436
+ | "I32Rotr" | "I64And" | "I64Or" | "I64Xor" | "I64Shl" | "I64ShrS" | "I64ShrU"
437
+ | "I64Rotl" | "I64Rotr" | "F32Copysign" | "F64Copysign" | "F32Min" | "F32Max"
438
+ | "F64Min" | "F64Max" | "I32Eq" | "I32Ne" | "I32LtS" | "I32LtU" | "I32LeS" | "I32LeU"
439
+ | "I32GtS" | "I32GtU" | "I32GeS" | "I32GeU" | "I64Eq" | "I64Ne" | "I64LtS" | "I64LtU"
440
+ | "I64LeS" | "I64LeU" | "I64GtS" | "I64GtU" | "I64GeS" | "I64GeU" | "F32Eq" | "F32Ne"
441
+ | "F32Lt" | "F32Le" | "F32Gt" | "F32Ge" | "F64Eq" | "F64Ne" | "F64Lt" | "F64Le"
442
+ | "F64Gt" | "F64Ge" => 1,
443
+ "Block" | "Loop" | "If" | "Else" | "End" | "Br" | "BrIf" | "BrTable" | "Select" => 1,
444
+ "MemoryGrow" | "MemorySize" => 1,
445
+ "MemoryInit" | "MemoryCopy" | "MemoryFill" => 6,
446
+ "Call" => 4,
447
+ "CallIndirect" => 6,
448
+ "DataDrop" => 5,
449
+ "Throw" => 100,
450
+ _ => 1000,
451
+ }
452
+ }
453
+
454
+ #[cfg(test)]
455
+ mod tests {
456
+ use super::{METER_MODEL, instrument_wasm, weighted_opcode_cost};
457
+ use std::borrow::Cow;
458
+ use wasm_encoder::{Encode, Section};
459
+ use wasmparser::{ExternalKind, Parser, Payload};
460
+
461
+ #[test]
462
+ fn instrumentation_adds_the_metering_global() {
463
+ let wasm =
464
+ wat::parse_str("(module (memory (export \"memory\") 1) (func (export \"_start\")))")
465
+ .unwrap();
466
+ let metered = instrument_wasm(&wasm, 1_000_000).unwrap();
467
+ let found = Parser::new(0)
468
+ .parse_all(&metered.wasm)
469
+ .filter_map(Result::ok)
470
+ .any(|payload| {
471
+ let Payload::ExportSection(section) = payload else {
472
+ return false;
473
+ };
474
+ section.into_iter().filter_map(Result::ok).any(|export| {
475
+ export.name == "gas_counter" && export.kind == ExternalKind::Global
476
+ })
477
+ });
478
+ assert!(found);
479
+ assert_eq!(METER_MODEL, "weighted");
480
+ }
481
+
482
+ #[test]
483
+ fn current_wasi_atomic_fences_are_supported() {
484
+ let wasm = wat::parse_str(
485
+ "(module (memory (export \"memory\") 1) (func (export \"_start\") atomic.fence))",
486
+ )
487
+ .unwrap();
488
+ instrument_wasm(&wasm, 1_000_000).unwrap();
489
+ }
490
+
491
+ #[test]
492
+ fn module_name_sections_are_removed_before_instrumentation() {
493
+ let wasm = wat::parse_str(
494
+ "(module $quickjs (memory (export \"memory\") 1) (func (export \"_start\")))",
495
+ )
496
+ .unwrap();
497
+ instrument_wasm(&wasm, 1_000_000).unwrap();
498
+ }
499
+
500
+ #[test]
501
+ fn wasix_dynamic_linking_metadata_survives_instrumentation() {
502
+ let mut wasm =
503
+ wat::parse_str("(module (memory (export \"memory\") 1) (func (export \"_start\")))")
504
+ .unwrap();
505
+ let dylink = wasm_encoder::CustomSection {
506
+ name: Cow::Borrowed("dylink.0"),
507
+ data: Cow::Borrowed(&[1, 0]),
508
+ };
509
+ wasm.push(dylink.id());
510
+ dylink.encode(&mut wasm);
511
+
512
+ let metered = instrument_wasm(&wasm, 1_000_000).unwrap();
513
+ let found = Parser::new(0)
514
+ .parse_all(&metered.wasm)
515
+ .filter_map(Result::ok)
516
+ .any(|payload| {
517
+ matches!(payload, Payload::CustomSection(section) if section.name() == "dylink.0")
518
+ });
519
+ assert!(found);
520
+ }
521
+
522
+ #[test]
523
+ fn weights_match_wark_v03_cost_classes_and_penalty() {
524
+ assert_eq!(weighted_opcode_cost("LocalGet"), Some(0));
525
+ assert_eq!(weighted_opcode_cost("I32Add"), Some(1));
526
+ assert_eq!(weighted_opcode_cost("I32Mul"), Some(2));
527
+ assert_eq!(weighted_opcode_cost("I32DivS"), Some(3));
528
+ assert_eq!(weighted_opcode_cost("MemoryCopy"), Some(6));
529
+ assert_eq!(weighted_opcode_cost("I32AtomicLoad"), Some(11));
530
+ assert_eq!(weighted_opcode_cost("I32AtomicStore"), Some(12));
531
+ assert_eq!(weighted_opcode_cost("Throw"), Some(100));
532
+ assert_eq!(weighted_opcode_cost("I32AtomicRmwCmpxchg"), Some(1000));
533
+ assert_eq!(weighted_opcode_cost("MemoryAtomicWait32"), Some(1000));
534
+ assert_eq!(weighted_opcode_cost("FutureInstruction"), Some(1000));
535
+ assert_eq!(weighted_opcode_cost("AtomicFence"), Some(1000));
536
+ }
537
+
538
+ #[test]
539
+ fn reports_wark_compatible_static_operation_counts() {
540
+ let wasm = wat::parse_str(
541
+ "(module (memory (export \"memory\") 1) (func (export \"_start\") i32.const 1 i32.const 2 i32.add drop))",
542
+ )
543
+ .unwrap();
544
+ let metered = instrument_wasm(&wasm, 1_000_000).unwrap();
545
+ assert_eq!(metered.operations.get("I32Const"), Some(&2));
546
+ assert_eq!(metered.operations.get("I32Add"), Some(&1));
547
+ assert_eq!(metered.operations.get("Drop"), Some(&1));
548
+ }
549
+ }
@@ -0,0 +1,149 @@
1
+ use wasmer::{AsStoreMut, Extern, ExternType, Imports, Memory, Module};
2
+
3
+ /// Resolves the single guest linear memory when a WASI/WASIX module imports it.
4
+ ///
5
+ /// Wasmer's low-level WASI import builder provides syscall functions, but it
6
+ /// does not allocate an `env.memory` import. Packaged WASIX runtimes such as
7
+ /// CPython use that ABI, so the portable runner must provide the memory before
8
+ /// instantiation. Other unresolved imports remain errors: silently fabricating
9
+ /// functions, tables, or globals would change program semantics.
10
+ pub(crate) fn attach_imported_memory(
11
+ store: &mut impl AsStoreMut,
12
+ module: &Module,
13
+ imports: &mut Imports,
14
+ ) -> Result<Option<Memory>, String> {
15
+ let mut guest_memory = None;
16
+ let mut memory_import = None;
17
+
18
+ for import in module.imports() {
19
+ if let Some(existing) = imports.get_export(import.module(), import.name()) {
20
+ if let ExternType::Memory(_) = import.ty() {
21
+ let Extern::Memory(memory) = existing else {
22
+ return Err(format!(
23
+ "import {}.{} must be a memory",
24
+ import.module(),
25
+ import.name()
26
+ ));
27
+ };
28
+ record_memory_import(
29
+ &mut guest_memory,
30
+ &mut memory_import,
31
+ import.module(),
32
+ import.name(),
33
+ memory,
34
+ )?;
35
+ }
36
+ continue;
37
+ }
38
+
39
+ match import.ty() {
40
+ ExternType::Memory(memory_type) => {
41
+ if import.module() != "env" || import.name() != "memory" {
42
+ return Err(format!(
43
+ "unresolved memory import {}.{} is unsupported; only env.memory is admitted",
44
+ import.module(),
45
+ import.name()
46
+ ));
47
+ }
48
+ let memory = Memory::new(store, *memory_type).map_err(|error| {
49
+ format!(
50
+ "failed to create imported memory {}.{}: {error}",
51
+ import.module(),
52
+ import.name()
53
+ )
54
+ })?;
55
+ imports.define(import.module(), import.name(), memory.clone());
56
+ record_memory_import(
57
+ &mut guest_memory,
58
+ &mut memory_import,
59
+ import.module(),
60
+ import.name(),
61
+ memory,
62
+ )?;
63
+ }
64
+ unresolved_type => {
65
+ return Err(format!(
66
+ "unresolved import {}.{} ({unresolved_type:?})",
67
+ import.module(),
68
+ import.name()
69
+ ));
70
+ }
71
+ }
72
+ }
73
+
74
+ Ok(guest_memory)
75
+ }
76
+
77
+ /// Supplies admitted memory imports from an additional-imports hook where
78
+ /// Wasmer has not merged its built-in WASI functions yet. Non-memory imports
79
+ /// are intentionally left for the runtime's normal resolver.
80
+ pub(crate) fn attach_declared_memory_imports(
81
+ store: &mut impl AsStoreMut,
82
+ module: &Module,
83
+ imports: &mut Imports,
84
+ ) -> Result<Option<Memory>, String> {
85
+ let mut guest_memory = None;
86
+ let mut memory_import = None;
87
+ for import in module.imports() {
88
+ let ExternType::Memory(memory_type) = import.ty() else {
89
+ continue;
90
+ };
91
+ if let Some(existing) = imports.get_export(import.module(), import.name()) {
92
+ let Extern::Memory(memory) = existing else {
93
+ return Err(format!(
94
+ "import {}.{} must be a memory",
95
+ import.module(),
96
+ import.name()
97
+ ));
98
+ };
99
+ record_memory_import(
100
+ &mut guest_memory,
101
+ &mut memory_import,
102
+ import.module(),
103
+ import.name(),
104
+ memory,
105
+ )?;
106
+ continue;
107
+ }
108
+ if import.module() != "env" || import.name() != "memory" {
109
+ return Err(format!(
110
+ "unresolved memory import {}.{} is unsupported; only env.memory is admitted",
111
+ import.module(),
112
+ import.name()
113
+ ));
114
+ }
115
+ let memory = Memory::new(&mut *store, *memory_type).map_err(|error| {
116
+ format!(
117
+ "failed to create imported memory {}.{}: {error}",
118
+ import.module(),
119
+ import.name()
120
+ )
121
+ })?;
122
+ imports.define(import.module(), import.name(), memory.clone());
123
+ record_memory_import(
124
+ &mut guest_memory,
125
+ &mut memory_import,
126
+ import.module(),
127
+ import.name(),
128
+ memory,
129
+ )?;
130
+ }
131
+ Ok(guest_memory)
132
+ }
133
+
134
+ fn record_memory_import(
135
+ guest_memory: &mut Option<Memory>,
136
+ first_import: &mut Option<(String, String)>,
137
+ module: &str,
138
+ name: &str,
139
+ memory: Memory,
140
+ ) -> Result<(), String> {
141
+ if let Some((first_module, first_name)) = first_import {
142
+ return Err(format!(
143
+ "multiple imported memories are unsupported: {first_module}.{first_name} and {module}.{name}"
144
+ ));
145
+ }
146
+ *first_import = Some((module.to_string(), name.to_string()));
147
+ *guest_memory = Some(memory);
148
+ Ok(())
149
+ }