Skip to content

Commit 1f16b28

Browse files
authored
Refactor String usage in wasmtime_environ::Module (#12565)
* Refactor `String` usage in `wasmtime_environ::Module` Do not hold regular `String`s; instead use our own OOM-handling `wasmtime_core::alloc::String` or, even better, an interned `Atom` from the `StringPool`. Also avoid `IndexMap`, as it doesn't handle OOMs. * Use OOM-handling `IndexMap` in `wasmtime_environ::Module` * review feedback
1 parent 9ead02e commit 1f16b28

12 files changed

Lines changed: 121 additions & 43 deletions

File tree

crates/environ/src/compile/module_environ.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::error::{Result, bail};
1+
use crate::error::{OutOfMemory, Result, bail};
22
use crate::module::{
33
FuncRefIndex, Initializer, MemoryInitialization, MemoryInitializer, Module, TableSegment,
44
TableSegmentElements,
@@ -7,10 +7,10 @@ use crate::prelude::*;
77
use crate::{
88
ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex,
99
EntityIndex, EntityType, FuncIndex, FuncKey, GlobalIndex, IndexType, InitMemory, MemoryIndex,
10-
ModuleInternedTypeIndex, ModuleTypesBuilder, PrimaryMap, SizeOverflow, StaticMemoryInitializer,
11-
StaticModuleIndex, TableIndex, TableInitialValue, Tag, TagIndex, Tunables, TypeConvert,
12-
TypeIndex, WasmError, WasmHeapTopType, WasmHeapType, WasmResult, WasmValType,
13-
WasmparserTypeConverter,
10+
ModuleInternedTypeIndex, ModuleTypesBuilder, PanicOnOom as _, PrimaryMap, SizeOverflow,
11+
StaticMemoryInitializer, StaticModuleIndex, TableIndex, TableInitialValue, Tag, TagIndex,
12+
Tunables, TypeConvert, TypeIndex, WasmError, WasmHeapTopType, WasmHeapType, WasmResult,
13+
WasmValType, WasmparserTypeConverter,
1414
};
1515
use cranelift_entity::SecondaryMap;
1616
use cranelift_entity::packed_option::ReservedValue;
@@ -373,7 +373,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
373373
bail!("custom-descriptors proposal not implemented yet");
374374
}
375375
};
376-
self.declare_import(import.module, import.name, ty);
376+
self.declare_import(import.module, import.name, ty)?;
377377
}
378378
}
379379

@@ -469,7 +469,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
469469
self.validator.export_section(&exports)?;
470470

471471
let cnt = usize::try_from(exports.count()).unwrap();
472-
self.result.module.exports.reserve(cnt);
472+
self.result.module.exports.reserve(cnt)?;
473473

474474
for entry in exports {
475475
let wasmparser::Export { name, kind, index } = entry?;
@@ -484,10 +484,8 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
484484
ExternalKind::Global => EntityIndex::Global(GlobalIndex::from_u32(index)),
485485
ExternalKind::Tag => EntityIndex::Tag(TagIndex::from_u32(index)),
486486
};
487-
self.result
488-
.module
489-
.exports
490-
.insert(String::from(name), entity);
487+
let name = self.result.module.strings.insert(name)?;
488+
self.result.module.exports.insert(name, entity)?;
491489
}
492490
}
493491

@@ -812,13 +810,19 @@ and for re-adding support for interface types you can see this issue:
812810
/// When the module linking proposal is disabled, however, disregard this
813811
/// logic and instead work directly with two-level imports since no
814812
/// instances are defined.
815-
fn declare_import(&mut self, module: &'data str, field: &'data str, ty: EntityType) {
813+
fn declare_import(
814+
&mut self,
815+
module: &'data str,
816+
field: &'data str,
817+
ty: EntityType,
818+
) -> Result<(), OutOfMemory> {
816819
let index = self.push_type(ty);
817820
self.result.module.initializers.push(Initializer::Import {
818-
name: module.to_owned(),
819-
field: field.to_owned(),
821+
name: self.result.module.strings.insert(module)?,
822+
field: self.result.module.strings.insert(field)?,
820823
index,
821824
});
825+
Ok(())
822826
}
823827

824828
fn push_type(&mut self, ty: EntityType) -> EntityIndex {
@@ -877,7 +881,8 @@ and for re-adding support for interface types you can see this issue:
877881
}
878882
}
879883
wasmparser::Name::Module { name, .. } => {
880-
self.result.module.name = Some(name.to_string());
884+
self.result.module.name =
885+
Some(self.result.module.strings.insert(name).panic_on_oom());
881886
if self.tunables.debug_native {
882887
self.result.debuginfo.name_section.module_name = Some(name);
883888
}

crates/environ/src/component/translate/adapt.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,9 @@ impl<'data> Translator<'_, 'data> {
252252
// partitioned in-order so we're guaranteed to push the adapters
253253
// in-order here as well. (with an assert to double-check)
254254
for (adapter, name) in adapter_module.adapters.iter().zip(&names) {
255-
let index = translation.module.exports[name];
256-
let i = component.adapter_partitionings.push((module_id, index));
255+
let name = translation.module.strings.get_atom(name).unwrap();
256+
let export = translation.module.exports[&name];
257+
let i = component.adapter_partitionings.push((module_id, export));
257258
assert_eq!(i, *adapter);
258259
}
259260

crates/environ/src/component/translate/inline.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,12 @@ impl<'a> Inliner<'a> {
13101310
ModuleInstanceDef::Instantiated(instance, module) => {
13111311
let (ty, item) = match &frame.modules[*module] {
13121312
ModuleDef::Static(idx, _ty) => {
1313-
let entity = self.nested_modules[*idx].module.exports[*name];
1313+
let name = self.nested_modules[*idx]
1314+
.module
1315+
.strings
1316+
.get_atom(name)
1317+
.unwrap();
1318+
let entity = self.nested_modules[*idx].module.exports[&name];
13141319
let ty = match entity {
13151320
EntityIndex::Function(f) => {
13161321
self.nested_modules[*idx].module.functions[f]
@@ -1481,7 +1486,12 @@ impl<'a> Inliner<'a> {
14811486
ModuleInstanceDef::Instantiated(instance, module) => {
14821487
let item = match frame.modules[*module] {
14831488
ModuleDef::Static(idx, _ty) => {
1484-
let entity = self.nested_modules[idx].module.exports[name];
1489+
let name = self.nested_modules[idx]
1490+
.module
1491+
.strings
1492+
.get_atom(name)
1493+
.unwrap();
1494+
let entity = self.nested_modules[idx].module.exports[&name];
14851495
ExportItem::Index(entity)
14861496
}
14871497
ModuleDef::Import(..) => ExportItem::Name(name.to_string()),

crates/environ/src/module.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,14 +294,17 @@ pub struct Module {
294294
/// This module's index.
295295
pub module_index: StaticModuleIndex,
296296

297+
/// A pool of strings used in this module.
298+
pub strings: StringPool,
299+
297300
/// The name of this wasm module, often found in the wasm file.
298-
pub name: Option<String>,
301+
pub name: Option<Atom>,
299302

300303
/// All import records, in the order they are declared in the module.
301304
pub initializers: Vec<Initializer>,
302305

303306
/// Exported entities.
304-
pub exports: IndexMap<String, EntityIndex>,
307+
pub exports: collections::IndexMap<Atom, EntityIndex>,
305308

306309
/// The module "start" function, if present.
307310
pub start_func: Option<FuncIndex>,
@@ -375,9 +378,9 @@ pub enum Initializer {
375378
/// An imported item is required to be provided.
376379
Import {
377380
/// Name of this import
378-
name: String,
381+
name: Atom,
379382
/// The field name projection of this import
380-
field: String,
383+
field: Atom,
381384
/// Where this import will be placed, which also has type information
382385
/// about the import.
383386
index: EntityIndex,
@@ -389,6 +392,7 @@ impl Module {
389392
pub fn new(module_index: StaticModuleIndex) -> Self {
390393
Self {
391394
module_index,
395+
strings: Default::default(),
392396
name: Default::default(),
393397
initializers: Default::default(),
394398
exports: Default::default(),
@@ -563,17 +567,22 @@ impl Module {
563567
/// Returns an iterator of all the imports in this module, along with their
564568
/// module name, field name, and type that's being imported.
565569
pub fn imports(&self) -> impl ExactSizeIterator<Item = (&str, &str, EntityType)> {
570+
let pool = &self.strings;
566571
self.initializers.iter().map(move |i| match i {
567572
Initializer::Import { name, field, index } => {
568-
(name.as_str(), field.as_str(), self.type_of(*index))
573+
(&pool[name], &pool[field], self.type_of(*index))
569574
}
570575
})
571576
}
572577

573578
/// Get this module's `i`th import.
574579
pub fn import(&self, i: usize) -> Option<(&str, &str, EntityType)> {
575580
match self.initializers.get(i)? {
576-
Initializer::Import { name, field, index } => Some((name, field, self.type_of(*index))),
581+
Initializer::Import { name, field, index } => Some((
582+
&self.strings[name],
583+
&self.strings[field],
584+
self.type_of(*index),
585+
)),
577586
}
578587
}
579588

@@ -670,6 +679,7 @@ impl TypeTrace for Module {
670679
// when adding new fields that might need re-canonicalization.
671680
let Self {
672681
module_index: _,
682+
strings: _,
673683
name: _,
674684
initializers: _,
675685
exports: _,
@@ -721,6 +731,7 @@ impl TypeTrace for Module {
721731
// when adding new fields that might need re-canonicalization.
722732
let Self {
723733
module_index: _,
734+
strings: _,
724735
name: _,
725736
initializers: _,
726737
exports: _,

crates/environ/src/string_pool.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ impl core::ops::Index<Atom> for StringPool {
101101
}
102102
}
103103

104+
// For convenience, to avoid `*atom` noise at call sites.
105+
impl core::ops::Index<&'_ Atom> for StringPool {
106+
type Output = str;
107+
108+
#[inline]
109+
#[track_caller]
110+
fn index(&self, atom: &Atom) -> &Self::Output {
111+
self.get(*atom).unwrap()
112+
}
113+
}
114+
104115
impl serde::ser::Serialize for StringPool {
105116
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106117
where
@@ -218,6 +229,20 @@ impl StringPool {
218229
None
219230
}
220231
}
232+
233+
/// Get the number of strings in this pool.
234+
pub fn len(&self) -> usize {
235+
self.strings.len()
236+
}
237+
}
238+
239+
impl Default for Atom {
240+
#[inline]
241+
fn default() -> Self {
242+
Self {
243+
index: NonZeroU32::MAX,
244+
}
245+
}
221246
}
222247

223248
impl fmt::Debug for Atom {
@@ -228,6 +253,17 @@ impl fmt::Debug for Atom {
228253
}
229254
}
230255

256+
// Allow using `Atom` in `SecondaryMap`s.
257+
impl crate::EntityRef for Atom {
258+
fn new(index: usize) -> Self {
259+
Atom::new(index)
260+
}
261+
262+
fn index(self) -> usize {
263+
Atom::index(&self)
264+
}
265+
}
266+
231267
impl serde::ser::Serialize for Atom {
232268
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233269
where

crates/wasmtime/src/runtime/component/instance.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,11 @@ where
632632
// during that phase so the actual instantiation of an `InstancePre`
633633
// skips all string lookups. This should probably only be
634634
// investigated if this becomes a performance issue though.
635-
ExportItem::Name(name) => instance.env_module().exports[name],
635+
ExportItem::Name(name) => {
636+
let module = instance.env_module();
637+
let name = module.strings.get_atom(name).unwrap();
638+
module.exports[&name]
639+
}
636640
};
637641
// SAFETY: the `store_id` owns this instance and all exports contained
638642
// within.

crates/wasmtime/src/runtime/component/matching.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ impl TypeChecker<'_> {
119119
// Every export that is expected should be in the actual module we have
120120
for (name, expected) in expected.exports.iter() {
121121
let idx = actual
122-
.exports
123-
.get(name)
122+
.strings
123+
.get_atom(name)
124+
.and_then(|atom| actual.exports.get(&atom))
124125
.ok_or_else(|| format_err!("module export `{name}` not defined"))?;
125126
let actual = actual.type_of(*idx);
126127
matching::entity_ty(self.engine, expected, &actual)

crates/wasmtime/src/runtime/instance.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -425,12 +425,12 @@ impl Instance {
425425
for (_name, entity) in module.exports.iter() {
426426
items.push(self._get_export(store, *entity));
427427
}
428-
store[self.id]
429-
.env_module()
428+
let module = store[self.id].env_module();
429+
module
430430
.exports
431431
.iter()
432432
.zip(items)
433-
.map(|((name, _), item)| Export::new(name, item))
433+
.map(|((name, _), item)| Export::new(&module.strings[name], item))
434434
}
435435

436436
/// Looks up an exported [`Extern`] value by name.
@@ -452,7 +452,9 @@ impl Instance {
452452
/// mutable context.
453453
pub fn get_export(&self, mut store: impl AsContextMut, name: &str) -> Option<Extern> {
454454
let store = store.as_context_mut().0;
455-
let entity = *store[self.id].env_module().exports.get(name)?;
455+
let module = store[self.id].env_module();
456+
let name = module.strings.get_atom(name)?;
457+
let entity = *module.exports.get(&name)?;
456458
Some(self._get_export(store, entity))
457459
}
458460

crates/wasmtime/src/runtime/module.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,9 @@ impl Module {
678678
/// # }
679679
/// ```
680680
pub fn name(&self) -> Option<&str> {
681-
self.compiled_module().module().name.as_deref()
681+
let module = self.compiled_module().module();
682+
let name = module.name?;
683+
Some(&module.strings[name])
682684
}
683685

684686
/// Returns the list of imports that this [`Module`] has and must be
@@ -807,7 +809,12 @@ impl Module {
807809
let types = self.types();
808810
let engine = self.engine();
809811
module.exports.iter().map(move |(name, entity_index)| {
810-
ExportType::new(name, module.type_of(*entity_index), types, engine)
812+
ExportType::new(
813+
&module.strings[name],
814+
module.type_of(*entity_index),
815+
types,
816+
engine,
817+
)
811818
})
812819
}
813820

@@ -856,7 +863,8 @@ impl Module {
856863
/// ```
857864
pub fn get_export(&self, name: &str) -> Option<ExternType> {
858865
let module = self.compiled_module().module();
859-
let entity_index = module.exports.get(name)?;
866+
let name = module.strings.get_atom(name)?;
867+
let entity_index = module.exports.get(&name)?;
860868
Some(ExternType::from_wasmtime(
861869
self.engine(),
862870
self.types(),
@@ -873,7 +881,8 @@ impl Module {
873881
pub fn get_export_index(&self, name: &str) -> Option<ModuleExport> {
874882
let compiled_module = self.compiled_module();
875883
let module = compiled_module.module();
876-
let entity = *module.exports.get(name)?;
884+
let name = module.strings.get_atom(name)?;
885+
let entity = *module.exports.get(&name)?;
877886
Some(ModuleExport {
878887
module: self.id(),
879888
entity,

crates/wasmtime/src/runtime/trampoline/memory.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ pub async fn create_memory(
3838
// Since we have only associated a single memory with the "frankenstein"
3939
// instance, it will be exported at index 0.
4040
debug_assert_eq!(memory_id.as_u32(), 0);
41+
let name = module.strings.insert("")?;
4142
module
4243
.exports
43-
.insert(String::new(), EntityIndex::Memory(memory_id));
44+
.insert(name, EntityIndex::Memory(memory_id))?;
4445
let info = ModuleRuntimeInfo::bare(Arc::new(module))?;
4546

4647
// We create an instance in the on-demand allocator when creating handles

0 commit comments

Comments
 (0)