@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.
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/THIRD_PARTY_NOTICES.md +302 -0
- package/crates/runtime-core/Cargo.lock +5099 -0
- package/crates/runtime-core/Cargo.toml +66 -0
- package/crates/runtime-core/README.md +47 -0
- package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
- package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
- package/crates/runtime-core/src/capabilities.rs +118 -0
- package/crates/runtime-core/src/compiler.rs +658 -0
- package/crates/runtime-core/src/contract.rs +5 -0
- package/crates/runtime-core/src/deterministic.rs +1051 -0
- package/crates/runtime-core/src/error.rs +58 -0
- package/crates/runtime-core/src/filesystem.rs +547 -0
- package/crates/runtime-core/src/filesystem_quota.rs +167 -0
- package/crates/runtime-core/src/go_compiler_session.rs +297 -0
- package/crates/runtime-core/src/interactive.rs +1019 -0
- package/crates/runtime-core/src/judge_package.rs +1539 -0
- package/crates/runtime-core/src/lib.rs +98 -0
- package/crates/runtime-core/src/memory.rs +84 -0
- package/crates/runtime-core/src/meter.rs +549 -0
- package/crates/runtime-core/src/module_imports.rs +149 -0
- package/crates/runtime-core/src/module_policy.rs +714 -0
- package/crates/runtime-core/src/output.rs +204 -0
- package/crates/runtime-core/src/run/mod.rs +208 -0
- package/crates/runtime-core/src/run/native.rs +260 -0
- package/crates/runtime-core/src/run/web.rs +229 -0
- package/crates/runtime-core/src/run/web_runtime.rs +109 -0
- package/crates/runtime-core/src/types.rs +268 -0
- package/crates/runtime-core/src/web.rs +83 -0
- package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
- package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
- package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
- package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
- package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
- package/dist/go-stage.mjs +193 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.js +4393 -0
- package/dist/java-stage.mjs +111 -0
- package/dist/python-stage.mjs +90 -0
- package/dist/rustc-stage.mjs +301 -0
- package/dist/server-build-stage.mjs +2564 -0
- package/dist/server-runner-stage.mjs +155 -0
- package/licenses/fflate-MIT.txt +21 -0
- package/licenses/runtime-core-dependencies.html +6253 -0
- package/licenses/runtime-core-dependencies.json +3041 -0
- package/licenses/wasmer-sdk-MIT.txt +21 -0
- package/licenses/wasmer-sdk-dependencies.html +6901 -0
- package/licenses/wasmer-sdk-dependencies.json +3013 -0
- package/package.json +70 -0
- package/rust-toolchain.toml +5 -0
- package/testdata/wojjdg02-v2-text.hex +1 -0
- package/vendor/shared-buffer/Cargo.toml +22 -0
- package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
- package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
- package/vendor/shared-buffer/README.md +34 -0
- package/vendor/shared-buffer/src/lib.rs +58 -0
- package/vendor/shared-buffer/src/mmap.rs +250 -0
- package/vendor/shared-buffer/src/owned.rs +389 -0
- package/vendor/virtual-fs/Cargo.toml +181 -0
- package/vendor/virtual-fs/LICENSE +25 -0
- package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
- package/vendor/virtual-fs/src/arc_file.rs +182 -0
- package/vendor/virtual-fs/src/arc_fs.rs +68 -0
- package/vendor/virtual-fs/src/buffer_file.rs +103 -0
- package/vendor/virtual-fs/src/builder.rs +232 -0
- package/vendor/virtual-fs/src/combine_file.rs +101 -0
- package/vendor/virtual-fs/src/cow_file.rs +345 -0
- package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
- package/vendor/virtual-fs/src/empty_fs.rs +81 -0
- package/vendor/virtual-fs/src/filesystems.rs +108 -0
- package/vendor/virtual-fs/src/host_fs.rs +1390 -0
- package/vendor/virtual-fs/src/lib.rs +782 -0
- package/vendor/virtual-fs/src/limiter.rs +252 -0
- package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
- package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
- package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
- package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
- package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
- package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
- package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
- package/vendor/virtual-fs/src/null_file.rs +87 -0
- package/vendor/virtual-fs/src/ops.rs +364 -0
- package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
- package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
- package/vendor/virtual-fs/src/pipe.rs +603 -0
- package/vendor/virtual-fs/src/random_file.rs +88 -0
- package/vendor/virtual-fs/src/special_file.rs +108 -0
- package/vendor/virtual-fs/src/static_file.rs +133 -0
- package/vendor/virtual-fs/src/static_fs.rs +460 -0
- package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
- package/vendor/virtual-fs/src/trace_fs.rs +258 -0
- package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
- 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
|
+
}
|