wasmtime_environ/module.rs
1//! Data structures for representing decoded wasm modules.
2
3use crate::{ModuleTranslation, PrimaryMap, Tunables, WASM_PAGE_SIZE};
4use cranelift_entity::{packed_option::ReservedValue, EntityRef};
5use indexmap::IndexMap;
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use std::convert::TryFrom;
9use std::mem;
10use std::ops::Range;
11use wasmtime_types::*;
12
13/// Implementation styles for WebAssembly linear memory.
14#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
15pub enum MemoryStyle {
16 /// The actual memory can be resized and moved.
17 Dynamic {
18 /// Extra space to reserve when a memory must be moved due to growth.
19 reserve: u64,
20 },
21 /// Address space is allocated up front.
22 Static {
23 /// The number of mapped and unmapped pages.
24 bound: u64,
25 },
26}
27
28impl MemoryStyle {
29 /// Decide on an implementation style for the given `Memory`.
30 pub fn for_memory(memory: Memory, tunables: &Tunables) -> (Self, u64) {
31 // A heap with a maximum that doesn't exceed the static memory bound specified by the
32 // tunables make it static.
33 //
34 // If the module doesn't declare an explicit maximum treat it as 4GiB when not
35 // requested to use the static memory bound itself as the maximum.
36 let absolute_max_pages = if memory.memory64 {
37 crate::WASM64_MAX_PAGES
38 } else {
39 crate::WASM32_MAX_PAGES
40 };
41 let maximum = std::cmp::min(
42 memory.maximum.unwrap_or(absolute_max_pages),
43 if tunables.static_memory_bound_is_maximum {
44 std::cmp::min(tunables.static_memory_bound, absolute_max_pages)
45 } else {
46 absolute_max_pages
47 },
48 );
49
50 // Ensure the minimum is less than the maximum; the minimum might exceed the maximum
51 // when the memory is artificially bounded via `static_memory_bound_is_maximum` above
52 if memory.minimum <= maximum && maximum <= tunables.static_memory_bound {
53 return (
54 Self::Static {
55 bound: tunables.static_memory_bound,
56 },
57 tunables.static_memory_offset_guard_size,
58 );
59 }
60
61 // Otherwise, make it dynamic.
62 (
63 Self::Dynamic {
64 reserve: tunables.dynamic_memory_growth_reserve,
65 },
66 tunables.dynamic_memory_offset_guard_size,
67 )
68 }
69}
70
71/// A WebAssembly linear memory description along with our chosen style for
72/// implementing it.
73#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
74pub struct MemoryPlan {
75 /// The WebAssembly linear memory description.
76 pub memory: Memory,
77 /// Our chosen implementation style.
78 pub style: MemoryStyle,
79 /// Chosen size of a guard page before the linear memory allocation.
80 pub pre_guard_size: u64,
81 /// Our chosen offset-guard size.
82 pub offset_guard_size: u64,
83}
84
85impl MemoryPlan {
86 /// Draw up a plan for implementing a `Memory`.
87 pub fn for_memory(memory: Memory, tunables: &Tunables) -> Self {
88 let (style, offset_guard_size) = MemoryStyle::for_memory(memory, tunables);
89 Self {
90 memory,
91 style,
92 offset_guard_size,
93 pre_guard_size: if tunables.guard_before_linear_memory {
94 offset_guard_size
95 } else {
96 0
97 },
98 }
99 }
100}
101
102/// A WebAssembly linear memory initializer.
103#[derive(Clone, Debug, Serialize, Deserialize)]
104pub struct MemoryInitializer {
105 /// The index of a linear memory to initialize.
106 pub memory_index: MemoryIndex,
107 /// Optionally, a global variable giving a base index.
108 pub base: Option<GlobalIndex>,
109 /// The offset to add to the base.
110 pub offset: u64,
111 /// The range of the data to write within the linear memory.
112 ///
113 /// This range indexes into a separately stored data section which will be
114 /// provided with the compiled module's code as well.
115 pub data: Range<u32>,
116}
117
118/// Similar to the above `MemoryInitializer` but only used when memory
119/// initializers are statically known to be valid.
120#[derive(Clone, Debug, Serialize, Deserialize)]
121pub struct StaticMemoryInitializer {
122 /// The 64-bit offset, in bytes, of where this initializer starts.
123 pub offset: u64,
124
125 /// The range of data to write at `offset`, where these indices are indexes
126 /// into the compiled wasm module's data section.
127 pub data: Range<u32>,
128}
129
130/// The type of WebAssembly linear memory initialization to use for a module.
131#[derive(Debug, Serialize, Deserialize)]
132pub enum MemoryInitialization {
133 /// Memory initialization is segmented.
134 ///
135 /// Segmented initialization can be used for any module, but it is required
136 /// if:
137 ///
138 /// * A data segment referenced an imported memory.
139 /// * A data segment uses a global base.
140 ///
141 /// Segmented initialization is performed by processing the complete set of
142 /// data segments when the module is instantiated.
143 ///
144 /// This is the default memory initialization type.
145 Segmented(Vec<MemoryInitializer>),
146
147 /// Memory initialization is statically known and involves a single `memcpy`
148 /// or otherwise simply making the defined data visible.
149 ///
150 /// To be statically initialized everything must reference a defined memory
151 /// and all data segments have a statically known in-bounds base (no
152 /// globals).
153 ///
154 /// This form of memory initialization is a more optimized version of
155 /// `Segmented` where memory can be initialized with one of a few methods:
156 ///
157 /// * First it could be initialized with a single `memcpy` of data from the
158 /// module to the linear memory.
159 /// * Otherwise techniques like `mmap` are also possible to make this data,
160 /// which might reside in a compiled module on disk, available immediately
161 /// in a linear memory's address space.
162 ///
163 /// To facilitate the latter of these techniques the `try_static_init`
164 /// function below, which creates this variant, takes a host page size
165 /// argument which can page-align everything to make mmap-ing possible.
166 Static {
167 /// The initialization contents for each linear memory.
168 ///
169 /// This array has, for each module's own linear memory, the contents
170 /// necessary to initialize it. If the memory has a `None` value then no
171 /// initialization is necessary (it's zero-filled). Otherwise with
172 /// `Some` the first element of the tuple is the offset in memory to
173 /// start the initialization and the `Range` is the range within the
174 /// final data section of the compiled module of bytes to copy into the
175 /// memory.
176 ///
177 /// The offset, range base, and range end are all guaranteed to be page
178 /// aligned to the page size passed in to `try_static_init`.
179 map: PrimaryMap<MemoryIndex, Option<StaticMemoryInitializer>>,
180 },
181}
182
183impl ModuleTranslation<'_> {
184 /// Attempts to convert segmented memory initialization into static
185 /// initialization for the module that this translation represents.
186 ///
187 /// If this module's memory initialization is not compatible with paged
188 /// initialization then this won't change anything. Otherwise if it is
189 /// compatible then the `memory_initialization` field will be updated.
190 ///
191 /// Takes a `page_size` argument in order to ensure that all
192 /// initialization is page-aligned for mmap-ability, and
193 /// `max_image_size_always_allowed` to control how we decide
194 /// whether to use static init.
195 ///
196 /// We will try to avoid generating very sparse images, which are
197 /// possible if e.g. a module has an initializer at offset 0 and a
198 /// very high offset (say, 1 GiB). To avoid this, we use a dual
199 /// condition: we always allow images less than
200 /// `max_image_size_always_allowed`, and the embedder of Wasmtime
201 /// can set this if desired to ensure that static init should
202 /// always be done if the size of the module or its heaps is
203 /// otherwise bounded by the system. We also allow images with
204 /// static init data bigger than that, but only if it is "dense",
205 /// defined as having at least half (50%) of its pages with some
206 /// data.
207 ///
208 /// We could do something slightly better by building a dense part
209 /// and keeping a sparse list of outlier/leftover segments (see
210 /// issue #3820). This would also allow mostly-static init of
211 /// modules that have some dynamically-placed data segments. But,
212 /// for now, this is sufficient to allow a system that "knows what
213 /// it's doing" to always get static init.
214 pub fn try_static_init(&mut self, page_size: u64, max_image_size_always_allowed: u64) {
215 // This method only attempts to transform a `Segmented` memory init
216 // into a `Static` one, no other state.
217 if !self.module.memory_initialization.is_segmented() {
218 return;
219 }
220
221 // First a dry run of memory initialization is performed. This
222 // collects information about the extent of memory initialized for each
223 // memory as well as the size of all data segments being copied in.
224 struct Memory {
225 data_size: u64,
226 min_addr: u64,
227 max_addr: u64,
228 // The `usize` here is a pointer into `self.data` which is the list
229 // of data segments corresponding to what was found in the original
230 // wasm module.
231 segments: Vec<(usize, StaticMemoryInitializer)>,
232 }
233 let mut info = PrimaryMap::with_capacity(self.module.memory_plans.len());
234 for _ in 0..self.module.memory_plans.len() {
235 info.push(Memory {
236 data_size: 0,
237 min_addr: u64::MAX,
238 max_addr: 0,
239 segments: Vec::new(),
240 });
241 }
242 let mut idx = 0;
243 let ok = self.module.memory_initialization.init_memory(
244 &mut (),
245 InitMemory::CompileTime(&self.module),
246 |(), memory, init| {
247 // Currently `Static` only applies to locally-defined memories,
248 // so if a data segment references an imported memory then
249 // transitioning to a `Static` memory initializer is not
250 // possible.
251 if self.module.defined_memory_index(memory).is_none() {
252 return false;
253 };
254 let info = &mut info[memory];
255 let data_len = u64::from(init.data.end - init.data.start);
256 if data_len > 0 {
257 info.data_size += data_len;
258 info.min_addr = info.min_addr.min(init.offset);
259 info.max_addr = info.max_addr.max(init.offset + data_len);
260 info.segments.push((idx, init.clone()));
261 }
262 idx += 1;
263 true
264 },
265 );
266 if !ok {
267 return;
268 }
269
270 // Validate that the memory information collected is indeed valid for
271 // static memory initialization.
272 for info in info.values().filter(|i| i.data_size > 0) {
273 let image_size = info.max_addr - info.min_addr;
274
275 // If the range of memory being initialized is less than twice the
276 // total size of the data itself then it's assumed that static
277 // initialization is ok. This means we'll at most double memory
278 // consumption during the memory image creation process, which is
279 // currently assumed to "probably be ok" but this will likely need
280 // tweaks over time.
281 if image_size < info.data_size.saturating_mul(2) {
282 continue;
283 }
284
285 // If the memory initialization image is larger than the size of all
286 // data, then we still allow memory initialization if the image will
287 // be of a relatively modest size, such as 1MB here.
288 if image_size < max_image_size_always_allowed {
289 continue;
290 }
291
292 // At this point memory initialization is concluded to be too
293 // expensive to do at compile time so it's entirely deferred to
294 // happen at runtime.
295 return;
296 }
297
298 // Here's where we've now committed to changing to static memory. The
299 // memory initialization image is built here from the page data and then
300 // it's converted to a single initializer.
301 let data = mem::replace(&mut self.data, Vec::new());
302 let mut map = PrimaryMap::with_capacity(info.len());
303 let mut module_data_size = 0u32;
304 for (memory, info) in info.iter() {
305 // Create the in-memory `image` which is the initialized contents of
306 // this linear memory.
307 let extent = if info.segments.len() > 0 {
308 (info.max_addr - info.min_addr) as usize
309 } else {
310 0
311 };
312 let mut image = Vec::with_capacity(extent);
313 for (idx, init) in info.segments.iter() {
314 let data = &data[*idx];
315 assert_eq!(data.len(), init.data.len());
316 let offset = usize::try_from(init.offset - info.min_addr).unwrap();
317 if image.len() < offset {
318 image.resize(offset, 0u8);
319 image.extend_from_slice(data);
320 } else {
321 image.splice(
322 offset..(offset + data.len()).min(image.len()),
323 data.iter().copied(),
324 );
325 }
326 }
327 assert_eq!(image.len(), extent);
328 assert_eq!(image.capacity(), extent);
329 let mut offset = if info.segments.len() > 0 {
330 info.min_addr
331 } else {
332 0
333 };
334
335 // Chop off trailing zeros from the image as memory is already
336 // zero-initialized. Note that `i` is the position of a nonzero
337 // entry here, so to not lose it we truncate to `i + 1`.
338 if let Some(i) = image.iter().rposition(|i| *i != 0) {
339 image.truncate(i + 1);
340 }
341
342 // Also chop off leading zeros, if any.
343 if let Some(i) = image.iter().position(|i| *i != 0) {
344 offset += i as u64;
345 image.drain(..i);
346 }
347 let mut len = u64::try_from(image.len()).unwrap();
348
349 // The goal is to enable mapping this image directly into memory, so
350 // the offset into linear memory must be a multiple of the page
351 // size. If that's not already the case then the image is padded at
352 // the front and back with extra zeros as necessary
353 if offset % page_size != 0 {
354 let zero_padding = offset % page_size;
355 self.data.push(vec![0; zero_padding as usize].into());
356 offset -= zero_padding;
357 len += zero_padding;
358 }
359 self.data.push(image.into());
360 if len % page_size != 0 {
361 let zero_padding = page_size - (len % page_size);
362 self.data.push(vec![0; zero_padding as usize].into());
363 len += zero_padding;
364 }
365
366 // Offset/length should now always be page-aligned.
367 assert!(offset % page_size == 0);
368 assert!(len % page_size == 0);
369
370 // Create the `StaticMemoryInitializer` which describes this image,
371 // only needed if the image is actually present and has a nonzero
372 // length. The `offset` has been calculates above, originally
373 // sourced from `info.min_addr`. The `data` field is the extent
374 // within the final data segment we'll emit to an ELF image, which
375 // is the concatenation of `self.data`, so here it's the size of
376 // the section-so-far plus the current segment we're appending.
377 let len = u32::try_from(len).unwrap();
378 let init = if len > 0 {
379 Some(StaticMemoryInitializer {
380 offset,
381 data: module_data_size..module_data_size + len,
382 })
383 } else {
384 None
385 };
386 let idx = map.push(init);
387 assert_eq!(idx, memory);
388 module_data_size += len;
389 }
390 self.data_align = Some(page_size);
391 self.module.memory_initialization = MemoryInitialization::Static { map };
392 }
393
394 /// Attempts to convert the module's table initializers to
395 /// FuncTable form where possible. This enables lazy table
396 /// initialization later by providing a one-to-one map of initial
397 /// table values, without having to parse all segments.
398 pub fn try_func_table_init(&mut self) {
399 // This should be large enough to support very large Wasm
400 // modules with huge funcref tables, but small enough to avoid
401 // OOMs or DoS on truly sparse tables.
402 const MAX_FUNC_TABLE_SIZE: u32 = 1024 * 1024;
403
404 let segments = match &self.module.table_initialization {
405 TableInitialization::Segments { segments } => segments,
406 TableInitialization::FuncTable { .. } => {
407 // Already done!
408 return;
409 }
410 };
411
412 // Build the table arrays per-table.
413 let mut tables = PrimaryMap::with_capacity(self.module.table_plans.len());
414 // Keep the "leftovers" for eager init.
415 let mut leftovers = vec![];
416
417 for segment in segments {
418 // Skip imported tables: we can't provide a preconstructed
419 // table for them, because their values depend on the
420 // imported table overlaid with whatever segments we have.
421 if self
422 .module
423 .defined_table_index(segment.table_index)
424 .is_none()
425 {
426 leftovers.push(segment.clone());
427 continue;
428 }
429
430 // If this is not a funcref table, then we can't support a
431 // pre-computed table of function indices.
432 if self.module.table_plans[segment.table_index].table.wasm_ty != WasmType::FuncRef {
433 leftovers.push(segment.clone());
434 continue;
435 }
436
437 // If the base of this segment is dynamic, then we can't
438 // include it in the statically-built array of initial
439 // contents.
440 if segment.base.is_some() {
441 leftovers.push(segment.clone());
442 continue;
443 }
444
445 // Get the end of this segment. If out-of-bounds, or too
446 // large for our dense table representation, then skip the
447 // segment.
448 let top = match segment.offset.checked_add(segment.elements.len() as u32) {
449 Some(top) => top,
450 None => {
451 leftovers.push(segment.clone());
452 continue;
453 }
454 };
455 let table_size = self.module.table_plans[segment.table_index].table.minimum;
456 if top > table_size || top > MAX_FUNC_TABLE_SIZE {
457 leftovers.push(segment.clone());
458 continue;
459 }
460
461 // We can now incorporate this segment into the initializers array.
462 while tables.len() <= segment.table_index.index() {
463 tables.push(vec![]);
464 }
465 let elements = &mut tables[segment.table_index];
466 if elements.is_empty() {
467 elements.resize(table_size as usize, FuncIndex::reserved_value());
468 }
469
470 let dst = &mut elements[(segment.offset as usize)..(top as usize)];
471 dst.copy_from_slice(&segment.elements[..]);
472 }
473
474 self.module.table_initialization = TableInitialization::FuncTable {
475 tables,
476 segments: leftovers,
477 };
478 }
479}
480
481impl Default for MemoryInitialization {
482 fn default() -> Self {
483 Self::Segmented(Vec::new())
484 }
485}
486
487impl MemoryInitialization {
488 /// Returns whether this initialization is of the form
489 /// `MemoryInitialization::Segmented`.
490 pub fn is_segmented(&self) -> bool {
491 match self {
492 MemoryInitialization::Segmented(_) => true,
493 _ => false,
494 }
495 }
496
497 /// Performs the memory initialization steps for this set of initializers.
498 ///
499 /// This will perform wasm initialization in compliance with the wasm spec
500 /// and how data segments are processed. This doesn't need to necessarily
501 /// only be called as part of initialization, however, as it's structured to
502 /// allow learning about memory ahead-of-time at compile time possibly.
503 ///
504 /// The various callbacks provided here are used to drive the smaller bits
505 /// of initialization, such as:
506 ///
507 /// * `get_cur_size_in_pages` - gets the current size, in wasm pages, of the
508 /// memory specified. For compile-time purposes this would be the memory
509 /// type's minimum size.
510 ///
511 /// * `get_global` - gets the value of the global specified. This is
512 /// statically, via validation, a pointer to the global of the correct
513 /// type (either u32 or u64 depending on the memory), but the value
514 /// returned here is `u64`. A `None` value can be returned to indicate
515 /// that the global's value isn't known yet.
516 ///
517 /// * `write` - a callback used to actually write data. This indicates that
518 /// the specified memory must receive the specified range of data at the
519 /// specified offset. This can internally return an false error if it
520 /// wants to fail.
521 ///
522 /// This function will return true if all memory initializers are processed
523 /// successfully. If any initializer hits an error or, for example, a
524 /// global value is needed but `None` is returned, then false will be
525 /// returned. At compile-time this typically means that the "error" in
526 /// question needs to be deferred to runtime, and at runtime this means
527 /// that an invalid initializer has been found and a trap should be
528 /// generated.
529 pub fn init_memory<T>(
530 &self,
531 state: &mut T,
532 init: InitMemory<'_, T>,
533 mut write: impl FnMut(&mut T, MemoryIndex, &StaticMemoryInitializer) -> bool,
534 ) -> bool {
535 let initializers = match self {
536 // Fall through below to the segmented memory one-by-one
537 // initialization.
538 MemoryInitialization::Segmented(list) => list,
539
540 // If previously switched to static initialization then pass through
541 // all those parameters here to the `write` callback.
542 //
543 // Note that existence of `Static` already guarantees that all
544 // indices are in-bounds.
545 MemoryInitialization::Static { map } => {
546 for (index, init) in map {
547 if let Some(init) = init {
548 let result = write(state, index, init);
549 if !result {
550 return result;
551 }
552 }
553 }
554 return true;
555 }
556 };
557
558 for initializer in initializers {
559 let MemoryInitializer {
560 memory_index,
561 base,
562 offset,
563 ref data,
564 } = *initializer;
565
566 // First up determine the start/end range and verify that they're
567 // in-bounds for the initial size of the memory at `memory_index`.
568 // Note that this can bail if we don't have access to globals yet
569 // (e.g. this is a task happening before instantiation at
570 // compile-time).
571 let base = match base {
572 Some(index) => match &init {
573 InitMemory::Runtime {
574 get_global_as_u64, ..
575 } => get_global_as_u64(state, index),
576 InitMemory::CompileTime(_) => return false,
577 },
578 None => 0,
579 };
580 let start = match base.checked_add(offset) {
581 Some(start) => start,
582 None => return false,
583 };
584 let len = u64::try_from(data.len()).unwrap();
585 let end = match start.checked_add(len) {
586 Some(end) => end,
587 None => return false,
588 };
589
590 let cur_size_in_pages = match &init {
591 InitMemory::CompileTime(module) => module.memory_plans[memory_index].memory.minimum,
592 InitMemory::Runtime {
593 memory_size_in_pages,
594 ..
595 } => memory_size_in_pages(state, memory_index),
596 };
597
598 // Note that this `minimum` can overflow if `minimum` is
599 // `1 << 48`, the maximum number of minimum pages for 64-bit
600 // memories. If this overflow happens, though, then there's no need
601 // to check the `end` value since `end` fits in a `u64` and it is
602 // naturally less than the overflowed value.
603 //
604 // This is a bit esoteric though because it's impossible to actually
605 // create a memory of `u64::MAX + 1` bytes, so this is largely just
606 // here to avoid having the multiplication here overflow in debug
607 // mode.
608 if let Some(max) = cur_size_in_pages.checked_mul(u64::from(WASM_PAGE_SIZE)) {
609 if end > max {
610 return false;
611 }
612 }
613
614 // The limits of the data segment have been validated at this point
615 // so the `write` callback is called with the range of data being
616 // written. Any erroneous result is propagated upwards.
617 let init = StaticMemoryInitializer {
618 offset: start,
619 data: data.clone(),
620 };
621 let result = write(state, memory_index, &init);
622 if !result {
623 return result;
624 }
625 }
626
627 return true;
628 }
629}
630
631/// Argument to [`MemoryInitialization::init_memory`] indicating the current
632/// status of the instance.
633pub enum InitMemory<'a, T> {
634 /// This evaluation of memory initializers is happening at compile time.
635 /// This means that the current state of memories is whatever their initial
636 /// state is, and additionally globals are not available if data segments
637 /// have global offsets.
638 CompileTime(&'a Module),
639
640 /// Evaluation of memory initializers is happening at runtime when the
641 /// instance is available, and callbacks are provided to learn about the
642 /// instance's state.
643 Runtime {
644 /// Returns the size, in wasm pages, of the the memory specified.
645 memory_size_in_pages: &'a dyn Fn(&mut T, MemoryIndex) -> u64,
646 /// Returns the value of the global, as a `u64`. Note that this may
647 /// involve zero-extending a 32-bit global to a 64-bit number.
648 get_global_as_u64: &'a dyn Fn(&mut T, GlobalIndex) -> u64,
649 },
650}
651
652/// Implementation styles for WebAssembly tables.
653#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
654pub enum TableStyle {
655 /// Signatures are stored in the table and checked in the caller.
656 CallerChecksSignature,
657}
658
659impl TableStyle {
660 /// Decide on an implementation style for the given `Table`.
661 pub fn for_table(_table: Table, _tunables: &Tunables) -> Self {
662 Self::CallerChecksSignature
663 }
664}
665
666/// A WebAssembly table description along with our chosen style for
667/// implementing it.
668#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
669pub struct TablePlan {
670 /// The WebAssembly table description.
671 pub table: Table,
672 /// Our chosen implementation style.
673 pub style: TableStyle,
674}
675
676impl TablePlan {
677 /// Draw up a plan for implementing a `Table`.
678 pub fn for_table(table: Table, tunables: &Tunables) -> Self {
679 let style = TableStyle::for_table(table, tunables);
680 Self { table, style }
681 }
682}
683
684/// A WebAssembly table initializer segment.
685#[derive(Clone, Debug, Serialize, Deserialize)]
686pub struct TableInitializer {
687 /// The index of a table to initialize.
688 pub table_index: TableIndex,
689 /// Optionally, a global variable giving a base index.
690 pub base: Option<GlobalIndex>,
691 /// The offset to add to the base.
692 pub offset: u32,
693 /// The values to write into the table elements.
694 pub elements: Box<[FuncIndex]>,
695}
696
697/// Table initialization data for all tables in the module.
698#[derive(Debug, Serialize, Deserialize)]
699pub enum TableInitialization {
700 /// "Segment" mode: table initializer segments, possibly with
701 /// dynamic bases, possibly applying to an imported memory.
702 ///
703 /// Every kind of table initialization is supported by the
704 /// Segments mode.
705 Segments {
706 /// The segment initializers. All apply to the table for which
707 /// this TableInitialization is specified.
708 segments: Vec<TableInitializer>,
709 },
710
711 /// "FuncTable" mode: a single array per table, with a function
712 /// index or null per slot. This is only possible to provide for a
713 /// given table when it is defined by the module itself, and can
714 /// only include data from initializer segments that have
715 /// statically-knowable bases (i.e., not dependent on global
716 /// values).
717 ///
718 /// Any segments that are not compatible with this mode are held
719 /// in the `segments` array of "leftover segments", which are
720 /// still processed eagerly.
721 ///
722 /// This mode facilitates lazy initialization of the tables. It is
723 /// thus "nice to have", but not necessary for correctness.
724 FuncTable {
725 /// For each table, an array of function indices (or
726 /// FuncIndex::reserved_value(), meaning no initialized value,
727 /// hence null by default). Array elements correspond
728 /// one-to-one to table elements; i.e., `elements[i]` is the
729 /// initial value for `table[i]`.
730 tables: PrimaryMap<TableIndex, Vec<FuncIndex>>,
731
732 /// Leftover segments that need to be processed eagerly on
733 /// instantiation. These either apply to an imported table (so
734 /// we can't pre-build a full image of the table from this
735 /// overlay) or have dynamically (at instantiation time)
736 /// determined bases.
737 segments: Vec<TableInitializer>,
738 },
739}
740
741impl Default for TableInitialization {
742 fn default() -> Self {
743 TableInitialization::Segments { segments: vec![] }
744 }
745}
746
747/// Different types that can appear in a module.
748///
749/// Note that each of these variants are intended to index further into a
750/// separate table.
751#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
752#[allow(missing_docs)]
753pub enum ModuleType {
754 Function(SignatureIndex),
755}
756
757impl ModuleType {
758 /// Asserts this is a `ModuleType::Function`, returning the underlying
759 /// `SignatureIndex`.
760 pub fn unwrap_function(&self) -> SignatureIndex {
761 match self {
762 ModuleType::Function(f) => *f,
763 }
764 }
765}
766
767/// A translated WebAssembly module, excluding the function bodies and
768/// memory initializers.
769#[derive(Default, Debug, Serialize, Deserialize)]
770pub struct Module {
771 /// The name of this wasm module, often found in the wasm file.
772 pub name: Option<String>,
773
774 /// All import records, in the order they are declared in the module.
775 pub initializers: Vec<Initializer>,
776
777 /// Exported entities.
778 pub exports: IndexMap<String, EntityIndex>,
779
780 /// The module "start" function, if present.
781 pub start_func: Option<FuncIndex>,
782
783 /// WebAssembly table initialization data, per table.
784 pub table_initialization: TableInitialization,
785
786 /// WebAssembly linear memory initializer.
787 pub memory_initialization: MemoryInitialization,
788
789 /// WebAssembly passive elements.
790 pub passive_elements: Vec<Box<[FuncIndex]>>,
791
792 /// The map from passive element index (element segment index space) to index in `passive_elements`.
793 pub passive_elements_map: BTreeMap<ElemIndex, usize>,
794
795 /// The map from passive data index (data segment index space) to index in `passive_data`.
796 pub passive_data_map: BTreeMap<DataIndex, Range<u32>>,
797
798 /// Types declared in the wasm module.
799 pub types: PrimaryMap<TypeIndex, ModuleType>,
800
801 /// Number of imported or aliased functions in the module.
802 pub num_imported_funcs: usize,
803
804 /// Number of imported or aliased tables in the module.
805 pub num_imported_tables: usize,
806
807 /// Number of imported or aliased memories in the module.
808 pub num_imported_memories: usize,
809
810 /// Number of imported or aliased globals in the module.
811 pub num_imported_globals: usize,
812
813 /// Number of functions that "escape" from this module may need to have a
814 /// `VMCallerCheckedFuncRef` constructed for them.
815 ///
816 /// This is also the number of functions in the `functions` array below with
817 /// an `anyfunc` index (and is the maximum anyfunc index).
818 pub num_escaped_funcs: usize,
819
820 /// Types of functions, imported and local.
821 pub functions: PrimaryMap<FuncIndex, FunctionType>,
822
823 /// WebAssembly tables.
824 pub table_plans: PrimaryMap<TableIndex, TablePlan>,
825
826 /// WebAssembly linear memory plans.
827 pub memory_plans: PrimaryMap<MemoryIndex, MemoryPlan>,
828
829 /// WebAssembly global variables.
830 pub globals: PrimaryMap<GlobalIndex, Global>,
831}
832
833/// Initialization routines for creating an instance, encompassing imports,
834/// modules, instances, aliases, etc.
835#[derive(Debug, Serialize, Deserialize)]
836pub enum Initializer {
837 /// An imported item is required to be provided.
838 Import {
839 /// Name of this import
840 name: String,
841 /// The field name projection of this import
842 field: String,
843 /// Where this import will be placed, which also has type information
844 /// about the import.
845 index: EntityIndex,
846 },
847}
848
849impl Module {
850 /// Allocates the module data structures.
851 pub fn new() -> Self {
852 Module::default()
853 }
854
855 /// Convert a `DefinedFuncIndex` into a `FuncIndex`.
856 #[inline]
857 pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex {
858 FuncIndex::new(self.num_imported_funcs + defined_func.index())
859 }
860
861 /// Convert a `FuncIndex` into a `DefinedFuncIndex`. Returns None if the
862 /// index is an imported function.
863 #[inline]
864 pub fn defined_func_index(&self, func: FuncIndex) -> Option<DefinedFuncIndex> {
865 if func.index() < self.num_imported_funcs {
866 None
867 } else {
868 Some(DefinedFuncIndex::new(
869 func.index() - self.num_imported_funcs,
870 ))
871 }
872 }
873
874 /// Test whether the given function index is for an imported function.
875 #[inline]
876 pub fn is_imported_function(&self, index: FuncIndex) -> bool {
877 index.index() < self.num_imported_funcs
878 }
879
880 /// Convert a `DefinedTableIndex` into a `TableIndex`.
881 #[inline]
882 pub fn table_index(&self, defined_table: DefinedTableIndex) -> TableIndex {
883 TableIndex::new(self.num_imported_tables + defined_table.index())
884 }
885
886 /// Convert a `TableIndex` into a `DefinedTableIndex`. Returns None if the
887 /// index is an imported table.
888 #[inline]
889 pub fn defined_table_index(&self, table: TableIndex) -> Option<DefinedTableIndex> {
890 if table.index() < self.num_imported_tables {
891 None
892 } else {
893 Some(DefinedTableIndex::new(
894 table.index() - self.num_imported_tables,
895 ))
896 }
897 }
898
899 /// Test whether the given table index is for an imported table.
900 #[inline]
901 pub fn is_imported_table(&self, index: TableIndex) -> bool {
902 index.index() < self.num_imported_tables
903 }
904
905 /// Convert a `DefinedMemoryIndex` into a `MemoryIndex`.
906 #[inline]
907 pub fn memory_index(&self, defined_memory: DefinedMemoryIndex) -> MemoryIndex {
908 MemoryIndex::new(self.num_imported_memories + defined_memory.index())
909 }
910
911 /// Convert a `MemoryIndex` into a `DefinedMemoryIndex`. Returns None if the
912 /// index is an imported memory.
913 #[inline]
914 pub fn defined_memory_index(&self, memory: MemoryIndex) -> Option<DefinedMemoryIndex> {
915 if memory.index() < self.num_imported_memories {
916 None
917 } else {
918 Some(DefinedMemoryIndex::new(
919 memory.index() - self.num_imported_memories,
920 ))
921 }
922 }
923
924 /// Convert a `DefinedMemoryIndex` into an `OwnedMemoryIndex`. Returns None
925 /// if the index is an imported memory.
926 #[inline]
927 pub fn owned_memory_index(&self, memory: DefinedMemoryIndex) -> OwnedMemoryIndex {
928 assert!(
929 memory.index() < self.memory_plans.len(),
930 "non-shared memory must have an owned index"
931 );
932
933 // Once we know that the memory index is not greater than the number of
934 // plans, we can iterate through the plans up to the memory index and
935 // count how many are not shared (i.e., owned).
936 let owned_memory_index = self
937 .memory_plans
938 .iter()
939 .skip(self.num_imported_memories)
940 .take(memory.index())
941 .filter(|(_, mp)| !mp.memory.shared)
942 .count();
943 OwnedMemoryIndex::new(owned_memory_index)
944 }
945
946 /// Test whether the given memory index is for an imported memory.
947 #[inline]
948 pub fn is_imported_memory(&self, index: MemoryIndex) -> bool {
949 index.index() < self.num_imported_memories
950 }
951
952 /// Convert a `DefinedGlobalIndex` into a `GlobalIndex`.
953 #[inline]
954 pub fn global_index(&self, defined_global: DefinedGlobalIndex) -> GlobalIndex {
955 GlobalIndex::new(self.num_imported_globals + defined_global.index())
956 }
957
958 /// Convert a `GlobalIndex` into a `DefinedGlobalIndex`. Returns None if the
959 /// index is an imported global.
960 #[inline]
961 pub fn defined_global_index(&self, global: GlobalIndex) -> Option<DefinedGlobalIndex> {
962 if global.index() < self.num_imported_globals {
963 None
964 } else {
965 Some(DefinedGlobalIndex::new(
966 global.index() - self.num_imported_globals,
967 ))
968 }
969 }
970
971 /// Test whether the given global index is for an imported global.
972 #[inline]
973 pub fn is_imported_global(&self, index: GlobalIndex) -> bool {
974 index.index() < self.num_imported_globals
975 }
976
977 /// Returns an iterator of all the imports in this module, along with their
978 /// module name, field name, and type that's being imported.
979 pub fn imports(&self) -> impl ExactSizeIterator<Item = (&str, &str, EntityType)> {
980 self.initializers.iter().map(move |i| match i {
981 Initializer::Import { name, field, index } => {
982 (name.as_str(), field.as_str(), self.type_of(*index))
983 }
984 })
985 }
986
987 /// Returns the type of an item based on its index
988 pub fn type_of(&self, index: EntityIndex) -> EntityType {
989 match index {
990 EntityIndex::Global(i) => EntityType::Global(self.globals[i]),
991 EntityIndex::Table(i) => EntityType::Table(self.table_plans[i].table),
992 EntityIndex::Memory(i) => EntityType::Memory(self.memory_plans[i].memory),
993 EntityIndex::Function(i) => EntityType::Function(self.functions[i].signature),
994 }
995 }
996
997 /// Appends a new function to this module with the given type information,
998 /// used for functions that either don't escape or aren't certain whether
999 /// they escape yet.
1000 pub fn push_function(&mut self, signature: SignatureIndex) -> FuncIndex {
1001 self.functions.push(FunctionType {
1002 signature,
1003 anyfunc: AnyfuncIndex::reserved_value(),
1004 })
1005 }
1006
1007 /// Appends a new function to this module with the given type information.
1008 pub fn push_escaped_function(
1009 &mut self,
1010 signature: SignatureIndex,
1011 anyfunc: AnyfuncIndex,
1012 ) -> FuncIndex {
1013 self.functions.push(FunctionType { signature, anyfunc })
1014 }
1015}
1016
1017/// Type information about functions in a wasm module.
1018#[derive(Debug, Serialize, Deserialize)]
1019pub struct FunctionType {
1020 /// The type of this function, indexed into the module-wide type tables for
1021 /// a module compilation.
1022 pub signature: SignatureIndex,
1023 /// The index into the anyfunc table, if present. Note that this is
1024 /// `reserved_value()` if the function does not escape from a module.
1025 pub anyfunc: AnyfuncIndex,
1026}
1027
1028impl FunctionType {
1029 /// Returns whether this function's type is one that "escapes" the current
1030 /// module, meaning that the function is exported, used in `ref.func`, used
1031 /// in a table, etc.
1032 pub fn is_escaping(&self) -> bool {
1033 !self.anyfunc.is_reserved_value()
1034 }
1035}
1036
1037/// Index into the anyfunc table within a VMContext for a function.
1038#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]
1039pub struct AnyfuncIndex(u32);
1040cranelift_entity::entity_impl!(AnyfuncIndex);