@wasm-oj/server 0.2.3 → 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.
- package/crates/runtime-core/src/deterministic.rs +27 -1
- package/crates/runtime-core/src/interactive.rs +365 -110
- package/crates/runtime-core/src/meter.rs +736 -66
- package/crates/runtime-core/src/run/mod.rs +2 -0
- package/crates/runtime-core/src/run/native.rs +5 -2
- package/crates/runtime-core/src/run/web.rs +39 -13
- package/crates/runtime-core/src/run/web_interactive.rs +375 -0
- package/crates/runtime-core/src/web.rs +52 -6
- package/dist/chunks/process-keepalive-BaXpMTap.js +191 -0
- package/dist/index.js +107 -18
- package/dist/server-build-stage.mjs +11 -94
- package/dist/server-runner-stage.mjs +73 -4
- package/package.json +3 -3
- package/dist/chunks/process-keepalive-BWLUz55N.js +0 -16
|
@@ -7,18 +7,13 @@ use std::borrow::Cow;
|
|
|
7
7
|
use std::collections::BTreeMap;
|
|
8
8
|
use wasm_encoder::reencode::{Error as ReencodeError, Reencode};
|
|
9
9
|
use wasm_encoder::{Encode, Section};
|
|
10
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
11
|
-
use wasmer::Global;
|
|
12
10
|
#[cfg(target_arch = "wasm32")]
|
|
13
11
|
use wasmer::js::AsJs;
|
|
14
|
-
use wasmer::{AsStoreMut, Instance};
|
|
12
|
+
use wasmer::{AsStoreMut, Function, Global, Imports, Instance};
|
|
15
13
|
|
|
16
14
|
pub const METER_MODEL: &str = "weighted";
|
|
17
15
|
const METERING_MODULE: &str = "wasm_oj_metering";
|
|
18
16
|
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
17
|
|
|
23
18
|
#[derive(Debug)]
|
|
24
19
|
pub struct InstrumentedModule {
|
|
@@ -53,10 +48,7 @@ pub enum CostPoints {
|
|
|
53
48
|
|
|
54
49
|
#[derive(Clone, Debug)]
|
|
55
50
|
pub struct MeterState {
|
|
56
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
57
51
|
gas_counter: Global,
|
|
58
|
-
#[cfg(target_arch = "wasm32")]
|
|
59
|
-
gas_counter: WebAssembly::Global,
|
|
60
52
|
}
|
|
61
53
|
|
|
62
54
|
pub fn instrument_wasm(wasm: &[u8], budget: u64) -> Result<InstrumentedModule, String> {
|
|
@@ -85,32 +77,6 @@ pub fn instrument_wasm(wasm: &[u8], budget: u64) -> Result<InstrumentedModule, S
|
|
|
85
77
|
})
|
|
86
78
|
}
|
|
87
79
|
|
|
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
80
|
#[derive(Debug)]
|
|
115
81
|
struct MeterInitializationError(String);
|
|
116
82
|
|
|
@@ -122,30 +88,424 @@ impl std::fmt::Display for MeterInitializationError {
|
|
|
122
88
|
|
|
123
89
|
impl std::error::Error for MeterInitializationError {}
|
|
124
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.
|
|
125
115
|
struct MeterInitializer {
|
|
126
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()))
|
|
127
368
|
}
|
|
128
369
|
|
|
129
370
|
impl Reencode for MeterInitializer {
|
|
130
371
|
type Error = MeterInitializationError;
|
|
131
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
|
+
|
|
132
492
|
fn parse_global_section(
|
|
133
493
|
&mut self,
|
|
134
494
|
globals: &mut wasm_encoder::GlobalSection,
|
|
135
495
|
section: wasmparser::GlobalSectionReader<'_>,
|
|
136
496
|
) -> Result<(), ReencodeError<Self::Error>> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
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"))?;
|
|
142
502
|
for (ordinal, global) in section.into_iter().enumerate() {
|
|
143
503
|
let global = global?;
|
|
144
504
|
if u32::try_from(ordinal).ok() == Some(meter_ordinal) {
|
|
145
505
|
if global.ty.content_type != wasmparser::ValType::I64 || !global.ty.mutable {
|
|
146
|
-
return Err(
|
|
147
|
-
"instrumented meter global has an unexpected type"
|
|
148
|
-
))
|
|
506
|
+
return Err(meter_error(
|
|
507
|
+
"instrumented meter global has an unexpected type",
|
|
508
|
+
));
|
|
149
509
|
}
|
|
150
510
|
globals.global(
|
|
151
511
|
self.global_type(global.ty)?,
|
|
@@ -155,6 +515,14 @@ impl Reencode for MeterInitializer {
|
|
|
155
515
|
wasm_encoder::reencode::utils::parse_global(self, globals, global)?;
|
|
156
516
|
}
|
|
157
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
|
+
);
|
|
158
526
|
Ok(())
|
|
159
527
|
}
|
|
160
528
|
}
|
|
@@ -162,12 +530,22 @@ impl Reencode for MeterInitializer {
|
|
|
162
530
|
fn set_initial_meter_budget(wasm: &[u8], budget: i64) -> Result<Vec<u8>, String> {
|
|
163
531
|
validate_meter_global_position(wasm)?;
|
|
164
532
|
let mut module = wasm_encoder::Module::new();
|
|
165
|
-
MeterInitializer
|
|
533
|
+
MeterInitializer::new(budget)
|
|
166
534
|
.parse_core_module(&mut module, wasmparser::Parser::new(0), wasm)
|
|
167
535
|
.map_err(|error| format!("failed to initialize weighted meter: {error}"))?;
|
|
168
536
|
Ok(module.finish())
|
|
169
537
|
}
|
|
170
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
|
+
|
|
171
549
|
fn validate_meter_global_position(wasm: &[u8]) -> Result<(), String> {
|
|
172
550
|
let mut imported_globals = 0_u32;
|
|
173
551
|
let mut defined_globals = 0_u32;
|
|
@@ -271,26 +649,13 @@ fn inspect_weighted_opcodes(wasm: &[u8]) -> Result<BTreeMap<String, u64>, String
|
|
|
271
649
|
Ok(operations)
|
|
272
650
|
}
|
|
273
651
|
|
|
274
|
-
pub fn meter_state(
|
|
275
|
-
|
|
652
|
+
pub fn meter_state(instance: &Instance) -> Result<MeterState, String> {
|
|
653
|
+
instance
|
|
276
654
|
.exports
|
|
277
655
|
.get_global(GAS_COUNTER_NAME)
|
|
278
|
-
.
|
|
279
|
-
.
|
|
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
|
-
}
|
|
656
|
+
.cloned()
|
|
657
|
+
.map(|gas_counter| MeterState { gas_counter })
|
|
658
|
+
.map_err(|error| format!("instrumented module does not export its meter: {error}"))
|
|
294
659
|
}
|
|
295
660
|
|
|
296
661
|
pub fn remaining_points(
|
|
@@ -306,8 +671,8 @@ pub fn remaining_points(
|
|
|
306
671
|
|
|
307
672
|
#[cfg(target_arch = "wasm32")]
|
|
308
673
|
let value = {
|
|
309
|
-
let
|
|
310
|
-
i64::try_from(BigInt::from(
|
|
674
|
+
let gas_counter: WebAssembly::Global = meter.gas_counter.as_jsvalue(store).into();
|
|
675
|
+
i64::try_from(BigInt::from(gas_counter.value()))
|
|
311
676
|
.map_err(|_| "metering global is outside the signed 64-bit range".to_string())?
|
|
312
677
|
};
|
|
313
678
|
|
|
@@ -318,6 +683,36 @@ pub fn remaining_points(
|
|
|
318
683
|
}
|
|
319
684
|
}
|
|
320
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
|
+
|
|
321
716
|
fn weighted_instruction_cost(operator: &Operator) -> Option<u32> {
|
|
322
717
|
let debug = format!("{operator:?}");
|
|
323
718
|
weighted_opcode_cost(debug.split_whitespace().next().unwrap_or("UNKNOWN"))
|
|
@@ -453,10 +848,12 @@ fn wark_v03_opcode_cost(opcode: &str) -> u32 {
|
|
|
453
848
|
|
|
454
849
|
#[cfg(test)]
|
|
455
850
|
mod tests {
|
|
456
|
-
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};
|
|
457
853
|
use std::borrow::Cow;
|
|
854
|
+
use std::collections::BTreeMap;
|
|
458
855
|
use wasm_encoder::{Encode, Section};
|
|
459
|
-
use wasmparser::{ExternalKind, Parser, Payload};
|
|
856
|
+
use wasmparser::{ExternalKind, Parser, Payload, ValType};
|
|
460
857
|
|
|
461
858
|
#[test]
|
|
462
859
|
fn instrumentation_adds_the_metering_global() {
|
|
@@ -535,6 +932,48 @@ mod tests {
|
|
|
535
932
|
assert_eq!(weighted_opcode_cost("AtomicFence"), Some(1000));
|
|
536
933
|
}
|
|
537
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
|
+
|
|
538
977
|
#[test]
|
|
539
978
|
fn reports_wark_compatible_static_operation_counts() {
|
|
540
979
|
let wasm = wat::parse_str(
|
|
@@ -546,4 +985,235 @@ mod tests {
|
|
|
546
985
|
assert_eq!(metered.operations.get("I32Add"), Some(&1));
|
|
547
986
|
assert_eq!(metered.operations.get("Drop"), Some(&1));
|
|
548
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
|
+
}
|
|
549
1219
|
}
|