twasm_utils/
export_globals.rs

1use tetsy_wasm::elements;
2
3use crate::optimizer::{global_section, export_section};
4
5/// Export all declared mutable globals.
6///
7/// This will export all internal mutable globals under the name of
8/// concat(`prefix`, i) where i is the index inside the range of
9/// [0..<total number of internal mutable globals>].
10pub fn export_mutable_globals(
11	module: &mut elements::Module,
12	prefix: impl Into<String>,
13) {
14
15	let exports = global_section(module).map(
16		|section| section.entries().iter().enumerate().filter_map(
17			|(index, global)| if global.global_type().is_mutable() { Some(index) } else { None }
18		).collect::<Vec<_>>()
19	).unwrap_or_default();
20
21	if module.export_section().is_none() {
22		module.sections_mut().push(elements::Section::Export(elements::ExportSection::default()));
23	}
24
25	let prefix: String = prefix.into();
26	for (symbol_index, export) in exports.into_iter().enumerate() {
27		let new_entry = elements::ExportEntry::new(
28			format!("{}_{}", prefix, symbol_index),
29			elements::Internal::Global(
30				(module.import_count(elements::ImportCountType::Global) + export) as _
31			),
32		);
33		export_section(module)
34			.expect("added above if does not exists")
35			.entries_mut()
36			.push(new_entry);
37	}
38}
39
40#[cfg(test)]
41mod tests {
42
43	use super::export_mutable_globals;
44	use tetsy_wasm::elements;
45
46	fn parse_wat(source: &str) -> elements::Module {
47		let module_bytes = wabt::Wat2Wasm::new()
48			.validate(true)
49			.convert(source)
50			.expect("failed to parse module");
51		elements::deserialize_buffer(module_bytes.as_ref())
52			.expect("failed to parse module")
53	}
54
55	macro_rules! test_export_global {
56		(name = $name:ident; input = $input:expr; expected = $expected:expr) => {
57			#[test]
58			fn $name() {
59				let mut input_module = parse_wat($input);
60				let expected_module = parse_wat($expected);
61
62				export_mutable_globals(&mut input_module, "exported_internal_global");
63
64				let actual_bytes = elements::serialize(input_module)
65					.expect("injected module must have a function body");
66
67				let expected_bytes = elements::serialize(expected_module)
68					.expect("injected module must have a function body");
69
70				assert_eq!(actual_bytes, expected_bytes);
71			}
72		}
73	}
74
75	test_export_global! {
76		name = simple;
77		input = r#"
78		(module
79			(global (;0;) (mut i32) (i32.const 1))
80			(global (;1;) (mut i32) (i32.const 0)))
81		"#;
82		expected = r#"
83		(module
84			(global (;0;) (mut i32) (i32.const 1))
85			(global (;1;) (mut i32) (i32.const 0))
86			(export "exported_internal_global_0" (global 0))
87			(export "exported_internal_global_1" (global 1)))
88		"#
89	}
90
91	test_export_global! {
92		name = with_import;
93		input = r#"
94		(module
95			(import "env" "global" (global $global i64))
96			(global (;0;) (mut i32) (i32.const 1))
97			(global (;1;) (mut i32) (i32.const 0)))
98		"#;
99		expected = r#"
100		(module
101			(import "env" "global" (global $global i64))
102			(global (;0;) (mut i32) (i32.const 1))
103			(global (;1;) (mut i32) (i32.const 0))
104			(export "exported_internal_global_0" (global 1))
105			(export "exported_internal_global_1" (global 2)))
106		"#
107	}
108
109	test_export_global! {
110		name = with_import_and_some_are_immutable;
111		input = r#"
112		(module
113			(import "env" "global" (global $global i64))
114			(global (;0;) i32 (i32.const 1))
115			(global (;1;) (mut i32) (i32.const 0)))
116		"#;
117		expected = r#"
118		(module
119			(import "env" "global" (global $global i64))
120			(global (;0;) i32 (i32.const 1))
121			(global (;1;) (mut i32) (i32.const 0))
122			(export "exported_internal_global_0" (global 2)))
123		"#
124	}
125}