Skip to content

Commit 8697e5b

Browse files
authored
Implement the compact import section proposal (#2398)
This implements the Compact Import Section proposal for WebAssembly: https://github.com/WebAssembly/compact-import-section At the time of writing this proposal is stage 2. It adds two new binary / text encodings for the import section that allow for deduplication of module names and externtypes.
1 parent a7a81fc commit 8697e5b

File tree

37 files changed

+1041
-191
lines changed

37 files changed

+1041
-191
lines changed

crates/wasm-encoder/src/core/imports.rs

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{
33
CORE_TABLE_SORT, CORE_TAG_SORT, Encode, GlobalType, MemoryType, Section, SectionId, TableType,
44
TagType, encode_section,
55
};
6+
use alloc::borrow::Cow;
67
use alloc::vec::Vec;
78

89
/// The type of an entity.
@@ -83,6 +84,49 @@ impl From<TagType> for EntityType {
8384
}
8485
}
8586

87+
/// An import item to be used with [`Imports::Single`].
88+
#[derive(Clone, Debug)]
89+
pub struct Import<'a> {
90+
/// The import's module name.
91+
pub module: &'a str,
92+
/// The import's item name.
93+
pub name: &'a str,
94+
/// The import's time.
95+
pub ty: EntityType,
96+
}
97+
98+
/// An import item to be used with [`Imports::Compact1`].
99+
#[derive(Clone, Debug)]
100+
pub struct ImportCompact<'a> {
101+
/// The import's item name.
102+
pub name: &'a str,
103+
/// The import's type.
104+
pub ty: EntityType,
105+
}
106+
107+
/// A single entry in the import section of a WebAssembly module, possibly containing multiple imports.
108+
#[derive(Clone, Debug)]
109+
pub enum Imports<'a> {
110+
/// A single import item.
111+
Single(Import<'a>),
112+
/// A group of imports with a common module name.
113+
Compact1 {
114+
/// The common module name.
115+
module: &'a str,
116+
/// The individual import items (name/type).
117+
items: Cow<'a, [ImportCompact<'a>]>,
118+
},
119+
/// A group of imports with a common module name and type.
120+
Compact2 {
121+
/// The common module name.
122+
module: &'a str,
123+
/// The common import type.
124+
ty: EntityType,
125+
/// The individual import item names.
126+
names: Cow<'a, [&'a str]>,
127+
},
128+
}
129+
86130
/// An encoder for the import section of WebAssembly modules.
87131
///
88132
/// # Example
@@ -130,14 +174,47 @@ impl ImportSection {
130174
self.num_added == 0
131175
}
132176

133-
/// Define an import in the import section.
134-
pub fn import(&mut self, module: &str, field: &str, ty: impl Into<EntityType>) -> &mut Self {
135-
module.encode(&mut self.bytes);
136-
field.encode(&mut self.bytes);
137-
ty.into().encode(&mut self.bytes);
177+
/// Define imports in the import section.
178+
pub fn imports<'a>(&mut self, imports: Imports<'a>) -> &mut Self {
179+
match imports {
180+
Imports::Single(import) => {
181+
import.module.encode(&mut self.bytes);
182+
import.name.encode(&mut self.bytes);
183+
import.ty.encode(&mut self.bytes);
184+
}
185+
Imports::Compact1 { module, items } => {
186+
module.encode(&mut self.bytes);
187+
self.bytes.push(0x00); // empty name
188+
self.bytes.push(0x7F);
189+
items.len().encode(&mut self.bytes);
190+
for item in items.iter() {
191+
item.name.encode(&mut self.bytes);
192+
item.ty.encode(&mut self.bytes);
193+
}
194+
}
195+
Imports::Compact2 { module, ty, names } => {
196+
module.encode(&mut self.bytes);
197+
self.bytes.push(0x00); // empty name
198+
self.bytes.push(0x7E);
199+
ty.encode(&mut self.bytes);
200+
names.len().encode(&mut self.bytes);
201+
for item in names.iter() {
202+
item.encode(&mut self.bytes);
203+
}
204+
}
205+
}
138206
self.num_added += 1;
139207
self
140208
}
209+
210+
/// Define an import in the import section.
211+
pub fn import(&mut self, module: &str, name: &str, ty: impl Into<EntityType>) -> &mut Self {
212+
self.imports(Imports::Single(Import {
213+
module,
214+
name,
215+
ty: ty.into(),
216+
}))
217+
}
141218
}
142219

143220
impl Encode for ImportSection {

crates/wasm-encoder/src/reencode.rs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,16 @@ pub trait Reencode {
427427
utils::parse_import_section(self, imports, section)
428428
}
429429

430+
/// Parses a [`wasmparser::Imports`] and adds all of its contents to the
431+
/// `import` section.
432+
fn parse_imports(
433+
&mut self,
434+
import_section: &mut crate::ImportSection,
435+
imports: wasmparser::Imports<'_>,
436+
) -> Result<(), Error<Self::Error>> {
437+
utils::parse_imports(self, import_section, imports)
438+
}
439+
430440
/// Parses the single [`wasmparser::Import`] provided and adds it to the
431441
/// `import` section.
432442
fn parse_import(
@@ -648,7 +658,7 @@ impl Reencode for RoundtripReencoder {
648658
#[allow(missing_docs)] // FIXME
649659
pub mod utils {
650660
use super::{Error, Reencode};
651-
use crate::{CoreTypeEncoder, Encode};
661+
use crate::{CoreTypeEncoder, Encode, Imports};
652662
use alloc::vec::Vec;
653663
use core::ops::Range;
654664

@@ -1430,27 +1440,63 @@ pub mod utils {
14301440
/// all the imports to the `import` section.
14311441
pub fn parse_import_section<T: ?Sized + Reencode>(
14321442
reencoder: &mut T,
1433-
imports: &mut crate::ImportSection,
1443+
import_section: &mut crate::ImportSection,
14341444
section: wasmparser::ImportSectionReader<'_>,
14351445
) -> Result<(), Error<T::Error>> {
1436-
for import in section {
1437-
reencoder.parse_import(imports, import?)?;
1446+
for imports in section {
1447+
let imports = imports?;
1448+
reencoder.parse_imports(import_section, imports)?;
14381449
}
14391450
Ok(())
14401451
}
14411452

1453+
/// Parses a [`wasmparser::Imports`] and adds all of its contents to the
1454+
/// `import` section.
1455+
pub fn parse_imports<T: ?Sized + Reencode>(
1456+
reencoder: &mut T,
1457+
import_section: &mut crate::ImportSection,
1458+
imports: wasmparser::Imports<'_>,
1459+
) -> Result<(), Error<T::Error>> {
1460+
import_section.imports(match imports {
1461+
wasmparser::Imports::Single(_, import) => Imports::Single(crate::Import {
1462+
module: import.module,
1463+
name: import.name,
1464+
ty: reencoder.entity_type(import.ty)?,
1465+
}),
1466+
wasmparser::Imports::Compact1 { module, items } => {
1467+
let mut new_items: Vec<crate::ImportCompact> = Vec::new();
1468+
for item in items {
1469+
let item = item?;
1470+
new_items.push(crate::ImportCompact {
1471+
name: item.name,
1472+
ty: reencoder.entity_type(item.ty)?,
1473+
})
1474+
}
1475+
Imports::Compact1 {
1476+
module: module,
1477+
items: new_items.into(),
1478+
}
1479+
}
1480+
wasmparser::Imports::Compact2 { module, ty, names } => {
1481+
let names = names.into_iter().collect::<wasmparser::Result<Vec<_>>>()?;
1482+
Imports::Compact2 {
1483+
module: module,
1484+
ty: reencoder.entity_type(ty)?,
1485+
names: names.into(),
1486+
}
1487+
}
1488+
});
1489+
Ok(())
1490+
}
1491+
14421492
/// Parses the single [`wasmparser::Import`] provided and adds it to the
14431493
/// `import` section.
14441494
pub fn parse_import<T: ?Sized + Reencode>(
14451495
reencoder: &mut T,
14461496
imports: &mut crate::ImportSection,
14471497
import: wasmparser::Import<'_>,
14481498
) -> Result<(), Error<T::Error>> {
1449-
imports.import(
1450-
import.module,
1451-
import.name,
1452-
reencoder.entity_type(import.ty)?,
1453-
);
1499+
reencoder.parse_imports(imports, wasmparser::Imports::Single(0, import))?;
14541500
Ok(())
14551501
}
14561502

crates/wasm-mutate/src/info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ impl<'a> ModuleInfo<'a> {
9999
info.imports = Some(info.raw_sections.len());
100100
info.section(SectionId::Import.into(), reader.range(), input_wasm);
101101

102-
for ty in reader {
102+
for ty in reader.into_imports() {
103103
match ty?.ty {
104104
wasmparser::TypeRef::Func(ty) | wasmparser::TypeRef::FuncExact(ty) => {
105105
// Save imported functions

crates/wasm-mutate/src/mutators/remove_item.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ impl Reencode for RemoveItem<'_> {
274274
let mut table = 0;
275275
let mut memory = 0;
276276
let mut tag = 0;
277-
for item in section {
277+
for item in section.into_imports() {
278278
let item = item?;
279279
let retain;
280280
match &item.ty {

crates/wasm-smith/src/component.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ impl ComponentBuilder {
792792
0 => {
793793
let module = crate::limited_string(100, u)?;
794794
let existing_module_imports = imports.entry(module.clone()).or_default();
795-
let field = crate::unique_string(100, existing_module_imports, u)?;
795+
let name = crate::unique_string(100, existing_module_imports, u)?;
796796
let entity_type = match self.arbitrary_core_entity_type(
797797
u,
798798
&types,
@@ -804,7 +804,7 @@ impl ComponentBuilder {
804804
};
805805
defs.push(ModuleTypeDef::Import(crate::core::Import {
806806
module,
807-
field,
807+
name,
808808
entity_type,
809809
}));
810810
}

crates/wasm-smith/src/component/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl CoreType {
147147
ModuleTypeDef::Import(imp) => {
148148
enc_mod_ty.import(
149149
&imp.module,
150-
&imp.field,
150+
&imp.name,
151151
crate::core::encode::translate_entity_type(&imp.entity_type),
152152
);
153153
}

crates/wasm-smith/src/core.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ pub(crate) struct Import {
360360
/// The name of the module providing this entity.
361361
pub(crate) module: String,
362362
/// The name of the entity.
363-
pub(crate) field: String,
363+
pub(crate) name: String,
364364
/// The type of this entity.
365365
pub(crate) entity_type: EntityType,
366366
}
@@ -1280,7 +1280,7 @@ impl Module {
12801280
}
12811281
}
12821282
wasmparser::Payload::ImportSection(import_reader) => {
1283-
for im in import_reader {
1283+
for im in import_reader.into_imports() {
12841284
let im = im.expect("could not read import");
12851285
required_imports.push(im);
12861286
}
@@ -1418,7 +1418,7 @@ impl Module {
14181418
};
14191419
new_imports.push(Import {
14201420
module: import.module.to_string(),
1421-
field: import.name.to_string(),
1421+
name: import.name.to_string(),
14221422
entity_type,
14231423
});
14241424
self.num_imports += 1;
@@ -1578,7 +1578,7 @@ impl Module {
15781578
}
15791579
import_strings.insert(import_pair.clone());
15801580
}
1581-
let (module, field) = import_pair;
1581+
let (module, name) = import_pair;
15821582

15831583
// Once our name is determined, then we push the typed item into the
15841584
// appropriate namespace.
@@ -1593,7 +1593,7 @@ impl Module {
15931593
self.num_imports += 1;
15941594
self.imports.push(Import {
15951595
module,
1596-
field,
1596+
name,
15971597
entity_type,
15981598
});
15991599
Ok(true)
@@ -1661,7 +1661,7 @@ impl Module {
16611661
}
16621662
}
16631663
wasmparser::Payload::ImportSection(import_reader) => {
1664-
for im in import_reader {
1664+
for im in import_reader.into_imports() {
16651665
let im = im.expect("could not read import");
16661666
// We can immediately filter whether this is an import we want to
16671667
// use.
@@ -1773,7 +1773,7 @@ impl Module {
17731773
};
17741774
new_imports.push(Import {
17751775
module: import.module.to_string(),
1776-
field: import.name.to_string(),
1776+
name: import.name.to_string(),
17771777
entity_type,
17781778
});
17791779
self.num_imports += 1;

crates/wasm-smith/src/core/encode.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,7 @@ impl Module {
6262

6363
let mut section = wasm_encoder::ImportSection::new();
6464
for im in &self.imports {
65-
section.import(
66-
&im.module,
67-
&im.field,
68-
translate_entity_type(&im.entity_type),
69-
);
65+
section.import(&im.module, &im.name, translate_entity_type(&im.entity_type));
7066
}
7167
module.section(&section);
7268
}

crates/wasm-smith/tests/available_imports.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn smoke_test_imports_config() {
5454
// list of expected imports (i.e. we don't generate
5555
// arbitrary ones), and that we handle the logic correctly
5656
// (i.e. signature types are as expected)
57-
for import in rdr {
57+
for import in rdr.into_imports() {
5858
let import = import.unwrap();
5959
use AvailableImportKind as I;
6060
let entry = imports_seen.get_mut(&(import.module, import.name));

crates/wasm-smith/tests/module_shape.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn get_imports_exports(
7777
let payload = payload.unwrap();
7878
match payload {
7979
wasmparser::Payload::ImportSection(ir) => {
80-
for import in ir {
80+
for import in ir.into_imports() {
8181
let import = import.unwrap();
8282
imports.push(WasmImport(
8383
import.module.to_string(),

0 commit comments

Comments
 (0)