Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wizer/src/component/instrument.rs
2459 views
1
use crate::ModuleContext;
2
use crate::component::info::{Accessor, RawSection};
3
use crate::component::{ComponentContext, WIZER_INSTANCE};
4
use anyhow::{Result, bail};
5
use wasm_encoder::reencode::{Reencode, RoundtripReencoder};
6
7
/// Instrumentation phase of wizening a component.
8
///
9
/// This is similar to the core wasm wizening instrumentation but operates at
10
/// the component level. This notably handles multiple instances and multiple
11
/// globals/memories across these instances. The general idea of the
12
/// instrumented component is:
13
///
14
/// * All core modules are instrumented with typical wizer instrumentation (e.g.
15
/// `__wizer_*` exports of all internal state).
16
/// * A core module is then generated which accesses all of these exports in the
17
/// form of functions.
18
/// * This core module is instantiated and lifted in a component instance which
19
/// contains exported functions for accessing all of the various pieces of
20
/// state of the module.
21
/// * The lifted instance is exported under the `WIZER_INSTANCE` name.
22
///
23
/// The main goal is to reuse the core wasm instrumentation as much as possible
24
/// here, and then the only remaining question is how to plumb all the core wasm
25
/// state out of the component through WIT.
26
pub(crate) fn instrument(component: &mut ComponentContext<'_>) -> Result<Vec<u8>> {
27
let mut encoder = wasm_encoder::Component::new();
28
29
// First pass through all sections as-is, ensuring that module sections are
30
// the instrumented version of the module.
31
for section in component.sections.iter_mut() {
32
match section {
33
RawSection::Raw(raw) => {
34
encoder.section(raw);
35
}
36
RawSection::Module(module) => {
37
let wasm = crate::instrument::instrument(module);
38
encoder.section(&wasm_encoder::RawSection {
39
id: wasm_encoder::ComponentSectionId::CoreModule as u8,
40
data: &wasm,
41
});
42
}
43
}
44
}
45
46
// Build the accessor module and append it with this helper.
47
let mut builder = AccessorBuilder {
48
component,
49
accessor_types: Default::default(),
50
accessor_imports: Default::default(),
51
accessor_functions: Default::default(),
52
accessor_code: Default::default(),
53
accessor_exports: Default::default(),
54
accessor_nglobals: 0,
55
accessor_nmemories: 0,
56
instances_to_instantiate_with: Vec::new(),
57
accessors: Vec::new(),
58
extra_types: Default::default(),
59
extra_aliases: Default::default(),
60
extra_canonicals: Default::default(),
61
accessor_instance_export_items: Vec::new(),
62
extra_core_funcs: 0,
63
};
64
builder.build(&mut encoder)?;
65
component.accessors = Some(builder.accessors);
66
67
Ok(encoder.finish())
68
}
69
70
struct AccessorBuilder<'a> {
71
component: &'a ComponentContext<'a>,
72
73
// Sections that are used to create the "accessor" module which is a bunch
74
// of functions that reads the internal state of all other instances in this
75
// component.
76
accessor_types: wasm_encoder::TypeSection,
77
accessor_imports: wasm_encoder::ImportSection,
78
accessor_functions: wasm_encoder::FunctionSection,
79
accessor_code: wasm_encoder::CodeSection,
80
accessor_exports: wasm_encoder::ExportSection,
81
accessor_nglobals: u32,
82
accessor_nmemories: u32,
83
84
// Arguments to the instantiation of the accessor module.
85
instances_to_instantiate_with: Vec<(u32, String)>,
86
87
// All accessor functions generated for all instance internal state.
88
accessors: Vec<Accessor>,
89
90
// Sections that are appended to the component as part of the
91
// instrumentation. This is the implementation detail of lifting all the
92
// functions in the "accessor" module.
93
extra_types: wasm_encoder::ComponentTypeSection,
94
extra_aliases: wasm_encoder::ComponentAliasSection,
95
extra_core_funcs: u32,
96
extra_canonicals: wasm_encoder::CanonicalFunctionSection,
97
accessor_instance_export_items: Vec<(String, wasm_encoder::ComponentExportKind, u32)>,
98
}
99
100
impl AccessorBuilder<'_> {
101
fn build(&mut self, encoder: &mut wasm_encoder::Component) -> Result<()> {
102
for (module_index, module) in self.component.core_modules() {
103
let instance_index = match self.component.core_instantiations.get(&module_index) {
104
Some(i) => *i,
105
None => continue,
106
};
107
self.add_core_instance(module_index, module, instance_index)?;
108
}
109
110
self.finish(encoder);
111
Ok(())
112
}
113
114
fn add_core_instance(
115
&mut self,
116
module_index: u32,
117
module: &ModuleContext<'_>,
118
instance_index: u32,
119
) -> Result<()> {
120
let instance_import_name = instance_index.to_string();
121
122
for (_, ty, name) in module.defined_globals() {
123
let name = match name {
124
Some(n) => n,
125
None => continue,
126
};
127
128
let accessor_export_name =
129
self.add_core_instance_global(&instance_import_name, name, ty)?;
130
self.accessors.push(Accessor::Global {
131
module_index,
132
accessor_export_name,
133
ty: ty.content_type,
134
core_export_name: name.to_string(),
135
});
136
self.accessor_nglobals += 1;
137
}
138
139
let defined_memory_exports = module.defined_memory_exports.as_ref().unwrap();
140
for ((_, ty), name) in module.defined_memories().zip(defined_memory_exports) {
141
let accessor_export_name =
142
self.add_core_instance_memory(instance_index, &instance_import_name, name, ty);
143
self.accessors.push(Accessor::Memory {
144
module_index,
145
accessor_export_name,
146
core_export_name: name.to_string(),
147
});
148
self.accessor_nmemories += 1;
149
}
150
151
self.instances_to_instantiate_with
152
.push((instance_index, instance_import_name));
153
154
Ok(())
155
}
156
157
fn add_core_instance_global(
158
&mut self,
159
instance_import_name: &str,
160
global_export_name: &str,
161
global_ty: wasmparser::GlobalType,
162
) -> Result<String> {
163
// Import the global and then define a function which returns the
164
// current value of the global.
165
self.accessor_imports.import(
166
&instance_import_name,
167
global_export_name,
168
RoundtripReencoder.global_type(global_ty).unwrap(),
169
);
170
let type_index = self.accessor_types.len();
171
self.accessor_types.ty().function(
172
[],
173
[RoundtripReencoder.val_type(global_ty.content_type).unwrap()],
174
);
175
self.accessor_functions.function(type_index);
176
177
// Accessing a global is pretty easy, it's just `global.get`
178
let mut function = wasm_encoder::Function::new([]);
179
let mut ins = function.instructions();
180
ins.global_get(self.accessor_nglobals);
181
ins.end();
182
self.accessor_code.function(&function);
183
184
let prim = match global_ty.content_type {
185
wasmparser::ValType::I32 => wasm_encoder::PrimitiveValType::S32,
186
wasmparser::ValType::I64 => wasm_encoder::PrimitiveValType::S64,
187
wasmparser::ValType::F32 => wasm_encoder::PrimitiveValType::F32,
188
wasmparser::ValType::F64 => wasm_encoder::PrimitiveValType::F64,
189
wasmparser::ValType::V128 => bail!("component wizening does not support v128 globals"),
190
wasmparser::ValType::Ref(_) => unreachable!(),
191
};
192
let lift_type_index = self.component.types + self.extra_types.len();
193
self.extra_types
194
.function()
195
.params::<_, wasm_encoder::ComponentValType>([])
196
.result(Some(wasm_encoder::ComponentValType::Primitive(prim)));
197
Ok(self.lift_accessor("global", lift_type_index, &[]))
198
}
199
200
fn add_core_instance_memory(
201
&mut self,
202
instance_index: u32,
203
instance_import_name: &str,
204
memory_export_name: &str,
205
memory_ty: wasmparser::MemoryType,
206
) -> String {
207
self.accessor_imports.import(
208
&instance_import_name,
209
memory_export_name,
210
RoundtripReencoder.memory_type(memory_ty).unwrap(),
211
);
212
let type_index = self.accessor_types.len();
213
self.accessor_types
214
.ty()
215
.function([], [wasm_encoder::ValType::I32]);
216
self.accessor_functions.function(type_index);
217
218
// Accessing a linear memory is more subtle than a global. We're
219
// returning a `list<u8>` in WIT but to do so we have to store the
220
// ptr/length in memory itself. To work around this the memory is grown
221
// by a single page to ensure we don't tamper with the original image,
222
// and then in this new page the ptr/len are stored. The base pointer is
223
// always 0 and the length is the size of memory prior to the growth.
224
let mut function = wasm_encoder::Function::new([(1, wasm_encoder::ValType::I32)]);
225
let mut ins = function.instructions();
226
227
// Grow memory by 1 page, and trap if the growth failed.
228
let pages_to_grow_by = match memory_ty.page_size_log2 {
229
Some(0) => 8,
230
Some(16) | None => 1,
231
_ => unreachable!(),
232
};
233
ins.i32_const(pages_to_grow_by);
234
ins.memory_grow(self.accessor_nmemories);
235
ins.local_tee(0);
236
ins.i32_const(-1);
237
ins.i32_eq();
238
ins.if_(wasm_encoder::BlockType::Empty);
239
ins.unreachable();
240
ins.end();
241
242
// Update our one local as the full byte length of memory.
243
ins.local_get(0);
244
ins.i32_const(memory_ty.page_size_log2.unwrap_or(16).cast_signed());
245
ins.i32_shl();
246
ins.local_set(0);
247
248
let memarg = |offset| wasm_encoder::MemArg {
249
align: 2,
250
offset,
251
memory_index: self.accessor_nmemories,
252
};
253
// Store the ptr/len into the page that was just allocated
254
ins.local_get(0);
255
ins.i32_const(0);
256
ins.i32_store(memarg(0));
257
ins.local_get(0);
258
ins.local_get(0);
259
ins.i32_store(memarg(4));
260
261
// and return the local as it's the pointer to the ptr/len combo
262
ins.local_get(0);
263
264
ins.end();
265
self.accessor_code.function(&function);
266
267
let list_ty = self.component.types + self.extra_types.len();
268
self.extra_types
269
.defined_type()
270
.list(wasm_encoder::ComponentValType::Primitive(
271
wasm_encoder::PrimitiveValType::U8,
272
));
273
let lift_type_index = self.component.types + self.extra_types.len();
274
self.extra_types
275
.function()
276
.params::<_, wasm_encoder::ComponentValType>([])
277
.result(Some(wasm_encoder::ComponentValType::Type(list_ty)));
278
self.extra_aliases
279
.alias(wasm_encoder::Alias::CoreInstanceExport {
280
instance: instance_index,
281
kind: wasm_encoder::ExportKind::Memory,
282
name: memory_export_name,
283
});
284
self.lift_accessor(
285
"memory",
286
lift_type_index,
287
&[wasm_encoder::CanonicalOption::Memory(
288
self.component.core_memories + self.accessor_nmemories,
289
)],
290
)
291
}
292
293
fn lift_accessor(
294
&mut self,
295
item: &str,
296
lift_type_index: u32,
297
opts: &[wasm_encoder::CanonicalOption],
298
) -> String {
299
let accessor_core_export_name = self.accessors.len().to_string();
300
self.accessor_exports.export(
301
&accessor_core_export_name,
302
wasm_encoder::ExportKind::Func,
303
self.accessor_functions.len() - 1,
304
);
305
306
self.extra_aliases
307
.alias(wasm_encoder::Alias::CoreInstanceExport {
308
instance: self.accessor_instance_index(),
309
kind: wasm_encoder::ExportKind::Func,
310
name: &accessor_core_export_name,
311
});
312
self.extra_core_funcs += 1;
313
self.extra_canonicals.lift(
314
self.component.core_funcs + self.extra_core_funcs - 1,
315
lift_type_index,
316
opts.iter().copied(),
317
);
318
319
let accessor_export_name = format!("{item}{}", self.accessors.len());
320
self.accessor_instance_export_items.push((
321
accessor_export_name.clone(),
322
wasm_encoder::ComponentExportKind::Func,
323
self.component.funcs + self.extra_canonicals.len() - 1,
324
));
325
accessor_export_name
326
}
327
328
fn finish(&mut self, encoder: &mut wasm_encoder::Component) {
329
// Build the `accessor_module` and add it to the component.
330
let mut accessor_module = wasm_encoder::Module::new();
331
accessor_module.section(&self.accessor_types);
332
accessor_module.section(&self.accessor_imports);
333
accessor_module.section(&self.accessor_functions);
334
accessor_module.section(&self.accessor_exports);
335
accessor_module.section(&self.accessor_code);
336
encoder.section(&wasm_encoder::ModuleSection(&accessor_module));
337
338
// Instantiate the `accessor_module` with prior instantiations.
339
let mut extra_instances = wasm_encoder::InstanceSection::new();
340
extra_instances.instantiate(
341
self.component.num_core_modules(),
342
self.instances_to_instantiate_with
343
.iter()
344
.map(|(i, name)| (name.as_str(), wasm_encoder::ModuleArg::Instance(*i))),
345
);
346
encoder.section(&extra_instances);
347
348
// Add instrumentation to the component which extracts names from the
349
// accessor instance, lifts things into the component model, and then
350
// export them.
351
encoder.section(&self.extra_aliases);
352
encoder.section(&self.extra_types);
353
encoder.section(&self.extra_canonicals);
354
let mut extra_component_instances = wasm_encoder::ComponentInstanceSection::new();
355
extra_component_instances.export_items(
356
self.accessor_instance_export_items
357
.iter()
358
.map(|(a, b, c)| (a.as_str(), *b, *c)),
359
);
360
encoder.section(&extra_component_instances);
361
362
let mut extra_exports = wasm_encoder::ComponentExportSection::new();
363
extra_exports.export(
364
WIZER_INSTANCE,
365
wasm_encoder::ComponentExportKind::Instance,
366
self.component.instances,
367
None,
368
);
369
encoder.section(&extra_exports);
370
}
371
372
fn accessor_instance_index(&self) -> u32 {
373
self.component.core_instances
374
}
375
}
376
377