Skip to content

Commit 155f827

Browse files
authored
wizer: Partially support nested components (#11898)
* wizer: Partially support nested components Support nested components insofar as they don't have access to modules at this time. This helps `wasmtime wizer` support wasm-tools-generated components which use nested components for type-ascribing exports. Otherwise though nested components are not allowed to defined or instantiate core modules as that would need more implementation in Wizer to support. * Two-level test too * Add test.wasm to wizer's gitignore * Fix test
1 parent 0f43b49 commit 155f827

5 files changed

Lines changed: 153 additions & 31 deletions

File tree

crates/wizer/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ npm/wizer-darwin-x64/
55
npm/wizer-linux-x64/
66
npm/wizer-win32-x64/
77
npm/wizer-linux-s390x/
8-
npm/wizer-linux-arm64/
8+
npm/wizer-linux-arm64/
9+
test.wasm

crates/wizer/src/component/parse.rs

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,45 @@ use wasmparser::{
1313
/// parsing phase which in theory could be lifted in the future but serve as
1414
/// simplifying assumptions for now:
1515
///
16-
/// * Nested components are not supported.
16+
/// * Nested components with modules are not supported.
1717
/// * Imported modules or components are not supported.
1818
/// * Instantiating a module twice is not supported.
1919
/// * Component-level start functions are not supported.
2020
///
2121
/// Some of these restrictions are likely to be loosened over time, however.
2222
pub(crate) fn parse<'a>(full_wasm: &'a [u8]) -> anyhow::Result<ComponentContext<'a>> {
2323
let mut component = ComponentContext::default();
24-
let mut parser = Parser::new(0).parse_all(full_wasm);
24+
let parser = Parser::new(0).parse_all(full_wasm);
25+
parse_into(Some(&mut component), full_wasm, parser)?;
26+
Ok(component)
27+
}
2528

26-
while let Some(payload) = parser.next() {
29+
fn parse_into<'a>(
30+
mut cx: Option<&mut ComponentContext<'a>>,
31+
full_wasm: &'a [u8],
32+
mut iter: impl Iterator<Item = wasmparser::Result<Payload<'a>>>,
33+
) -> anyhow::Result<()> {
34+
let mut stack = Vec::new();
35+
while let Some(payload) = iter.next() {
2736
let payload = payload?;
2837

2938
match &payload {
3039
// Module sections get parsed with wizer's core wasm support.
31-
Payload::ModuleSection { .. } => {
32-
let info = crate::parse::parse_with(&full_wasm, &mut parser)?;
33-
component.push_module_section(info);
34-
}
40+
Payload::ModuleSection { .. } => match &mut cx {
41+
Some(component) => {
42+
let info = crate::parse::parse_with(&full_wasm, &mut iter)?;
43+
component.push_module_section(info);
44+
}
45+
None => {
46+
bail!("nested components with modules not currently supported");
47+
}
48+
},
3549

3650
// All other sections get pushed raw as-is into the component.
3751
_ => {
38-
if let Some((id, range)) = payload.as_section() {
52+
if let Some((id, range)) = payload.as_section()
53+
&& let Some(component) = &mut cx
54+
{
3955
component.push_raw_section(wasm_encoder::RawSection {
4056
id,
4157
data: &full_wasm[range],
@@ -56,43 +72,61 @@ pub(crate) fn parse<'a>(full_wasm: &'a [u8]) -> anyhow::Result<ComponentContext<
5672
}
5773

5874
Payload::ComponentSection { .. } => {
59-
bail!("wizer does not currently support nested components");
75+
stack.push(cx.take());
6076
}
6177

62-
Payload::InstanceSection(reader) => {
63-
for instance in reader {
64-
let instance_index = component.inc_core_instances();
78+
Payload::End(_) => {
79+
if stack.len() > 0 {
80+
cx = stack.pop().unwrap();
81+
}
82+
}
6583

66-
if let Instance::Instantiate { module_index, .. } = instance? {
67-
match component.core_instantiations.entry(module_index) {
68-
Entry::Vacant(entry) => {
69-
entry.insert(instance_index);
84+
Payload::InstanceSection(reader) => {
85+
if let Some(component) = &mut cx {
86+
for instance in reader {
87+
let instance_index = component.inc_core_instances();
88+
89+
if let Instance::Instantiate { module_index, .. } = instance? {
90+
match component.core_instantiations.entry(module_index) {
91+
Entry::Vacant(entry) => {
92+
entry.insert(instance_index);
93+
}
94+
Entry::Occupied(_) => {
95+
bail!("modules may be instantiated at most once")
96+
}
7097
}
71-
Entry::Occupied(_) => bail!("modules may be instantiated at most once"),
7298
}
7399
}
74100
}
75101
}
76102
Payload::ComponentInstanceSection(reader) => {
77-
for _ in reader {
78-
component.inc_instances();
103+
if let Some(component) = &mut cx {
104+
for _ in reader {
105+
component.inc_instances();
106+
}
79107
}
80108
}
81109

82110
Payload::ComponentAliasSection(reader) => {
83111
for alias in reader {
84112
match alias? {
85113
ComponentAlias::CoreInstanceExport { kind, .. } => {
86-
component.inc_core(kind);
114+
if let Some(component) = &mut cx {
115+
component.inc_core(kind);
116+
}
87117
}
88118
ComponentAlias::InstanceExport { kind, .. } => {
89119
validate_item_kind(kind, "aliases")?;
90-
component.inc(kind);
120+
if let Some(component) = &mut cx {
121+
component.inc(kind);
122+
}
91123
}
92124
ComponentAlias::Outer { kind, .. } => match kind {
93125
ComponentOuterAliasKind::CoreType => {}
94126
ComponentOuterAliasKind::Type => {
95-
component.inc_types();
127+
if let Some(component) = &mut cx {
128+
component.inc_types();
129+
}
96130
}
97131
ComponentOuterAliasKind::CoreModule => {
98132
bail!("wizer does not currently support module aliases");
@@ -109,10 +143,14 @@ pub(crate) fn parse<'a>(full_wasm: &'a [u8]) -> anyhow::Result<ComponentContext<
109143
for function in reader {
110144
match function? {
111145
CanonicalFunction::Lift { .. } => {
112-
component.inc_funcs();
146+
if let Some(component) = &mut cx {
147+
component.inc_funcs();
148+
}
113149
}
114150
_ => {
115-
component.inc_core_funcs();
151+
if let Some(component) = &mut cx {
152+
component.inc_core_funcs();
153+
}
116154
}
117155
}
118156
}
@@ -122,21 +160,27 @@ pub(crate) fn parse<'a>(full_wasm: &'a [u8]) -> anyhow::Result<ComponentContext<
122160
for import in reader {
123161
let kind = import?.ty.kind();
124162
validate_item_kind(kind, "imports")?;
125-
component.inc(kind);
163+
if let Some(component) = &mut cx {
164+
component.inc(kind);
165+
}
126166
}
127167
}
128168

129169
Payload::ComponentExportSection(reader) => {
130170
for export in reader {
131171
let kind = export?.kind;
132172
validate_item_kind(kind, "exports")?;
133-
component.inc(kind);
173+
if let Some(component) = &mut cx {
174+
component.inc(kind);
175+
}
134176
}
135177
}
136178

137179
Payload::ComponentTypeSection(reader) => {
138180
for _ in reader {
139-
component.inc_types();
181+
if let Some(component) = &mut cx {
182+
component.inc_types();
183+
}
140184
}
141185
}
142186

@@ -151,7 +195,7 @@ pub(crate) fn parse<'a>(full_wasm: &'a [u8]) -> anyhow::Result<ComponentContext<
151195
}
152196
}
153197

154-
Ok(component)
198+
Ok(())
155199
}
156200

157201
fn validate_item_kind(kind: ComponentExternalKind, msg: &str) -> Result<()> {

crates/wizer/src/component/rewrite.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ impl Reencoder<'_> {
107107
// indices in case the index is higher than the one that we're
108108
// removing.
109109
id if id == wasm_encoder::ComponentSectionId::Instance as u8 => {
110-
self.rewrite(encoder, section.data, Self::parse_instance_section);
110+
self.rewrite(
111+
encoder,
112+
section.data,
113+
Self::parse_component_instance_section,
114+
);
111115
}
112116
id if id == wasm_encoder::ComponentSectionId::Alias as u8 => {
113117
self.rewrite(encoder, section.data, Self::parse_component_alias_section);

crates/wizer/test.wasm

-156 Bytes
Binary file not shown.

crates/wizer/tests/all/component.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ fn fail_wizening(msg: &str, wasm: &[u8]) -> Result<()> {
77
let _ = env_logger::try_init();
88

99
let wasm = wat::parse_bytes(wasm)?;
10+
log::debug!(
11+
"testing wizening failure for wasm:\n{}",
12+
wasmprinter::print_bytes(&wasm)?
13+
);
1014
match Wizer::new().instrument_component(&wasm) {
1115
Ok(_) => bail!("expected wizening to fail"),
1216
Err(e) => {
@@ -29,9 +33,40 @@ fn unsupported_constructs() -> Result<()> {
2933
)?;
3034

3135
fail_wizening(
32-
"does not currently support nested components",
36+
"nested components with modules not currently supported",
37+
br#"(component
38+
(component (core module))
39+
)"#,
40+
)?;
41+
fail_wizening(
42+
"nested components with modules not currently supported",
3343
br#"(component
3444
(component)
45+
(component (core module))
46+
)"#,
47+
)?;
48+
fail_wizening(
49+
"wizer does not currently support module imports",
50+
br#"(component
51+
(component (import "x" (core module)))
52+
)"#,
53+
)?;
54+
fail_wizening(
55+
"wizer does not currently support module aliases",
56+
br#"(component
57+
(core module $a)
58+
(component
59+
(core instance (instantiate $a))
60+
)
61+
)"#,
62+
)?;
63+
fail_wizening(
64+
"wizer does not currently support component aliases",
65+
br#"(component
66+
(component $a)
67+
(component
68+
(instance (instantiate $a))
69+
)
3570
)"#,
3671
)?;
3772

@@ -308,6 +343,44 @@ async fn snapshot_memory() -> Result<()> {
308343
Ok(())
309344
}
310345

346+
#[tokio::test]
347+
async fn nested_components() -> Result<()> {
348+
wizen_and_run_wasm(
349+
42,
350+
r#"(component
351+
(component $a)
352+
(instance (instantiate $a))
353+
(instance (export "hi") (instantiate $a))
354+
355+
(component $b
356+
(type $t string)
357+
(import "x" (type (eq $t)))
358+
(component $a)
359+
(instance (instantiate $a))
360+
(instance (export "hi") (instantiate $a))
361+
)
362+
(type $x string)
363+
(instance (instantiate $b
364+
(with "x" (type $x))
365+
))
366+
(instance (export "hi2") (instantiate $b
367+
(with "x" (type $x))
368+
))
369+
370+
(core module $m
371+
(func (export "init"))
372+
(func (export "run") (result i32) i32.const 42)
373+
)
374+
(core instance $i (instantiate $m))
375+
(func (export "run") (result u32) (canon lift (core func $i "run")))
376+
(func (export "wizer-initialize") (canon lift (core func $i "init")))
377+
)"#,
378+
)
379+
.await?;
380+
381+
Ok(())
382+
}
383+
311384
#[tokio::test]
312385
async fn multiple_modules() -> Result<()> {
313386
wizen_and_run_wasm(

0 commit comments

Comments
 (0)