twasm_utils/
ext.rs

1use crate::std::string::String;
2use crate::std::vec::Vec;
3use crate::std::borrow::ToOwned;
4
5use tetsy_wasm::{elements, builder};
6use byteorder::{LittleEndian, ByteOrder};
7
8use crate::optimizer::{import_section, export_section};
9
10type Insertion = (usize, u32, u32, String);
11
12pub fn update_call_index(instructions: &mut elements::Instructions, original_imports: usize, inserts: &[Insertion]) {
13	use tetsy_wasm::elements::Instruction::*;
14	for instruction in instructions.elements_mut().iter_mut() {
15		if let Call(call_index) = instruction {
16			if let Some(pos) = inserts.iter().position(|x| x.1 == *call_index) {
17				*call_index = (original_imports + pos) as u32;
18			} else if *call_index as usize > original_imports {
19				*call_index += inserts.len() as u32;
20			}
21		}
22	}
23}
24
25pub fn memory_section(module: &mut elements::Module) -> Option<&mut elements::MemorySection> {
26	for section in module.sections_mut() {
27	   if let elements::Section::Memory(sect) = section {
28		   return Some(sect);
29		}
30	}
31	None
32}
33
34pub fn externalize_mem(mut module: elements::Module, adjust_pages: Option<u32>, max_pages: u32) -> elements::Module {
35	let mut entry = memory_section(&mut module)
36		.expect("Memory section to exist")
37		.entries_mut()
38		.pop()
39		.expect("Own memory entry to exist in memory section");
40
41	if let Some(adjust_pages) = adjust_pages {
42		assert!(adjust_pages <= max_pages);
43		entry = elements::MemoryType::new(adjust_pages, Some(max_pages));
44	}
45
46	if entry.limits().maximum().is_none() {
47		entry = elements::MemoryType::new(entry.limits().initial(), Some(max_pages));
48	}
49
50	let mut builder = builder::from_module(module);
51	builder.push_import(
52		elements::ImportEntry::new(
53			"env".to_owned(),
54			"memory".to_owned(),
55			elements::External::Memory(entry),
56		)
57	);
58
59	builder.build()
60}
61
62fn foreach_public_func_name<F>(mut module: elements::Module, f: F) -> elements::Module
63where F: Fn(&mut String)
64{
65	if let Some(section) = import_section(&mut module) {
66		for entry in section.entries_mut() {
67			if let elements::External::Function(_) = *entry.external() {
68				f(entry.field_mut())
69			}
70		}
71	}
72
73	if let Some(section) = export_section(&mut module) {
74		for entry in section.entries_mut() {
75			if let elements::Internal::Function(_) = *entry.internal() {
76				f(entry.field_mut())
77			}
78		}
79	}
80
81	module
82}
83
84pub fn underscore_funcs(module: elements::Module) -> elements::Module {
85	foreach_public_func_name(module, |n| n.insert(0, '_'))
86}
87
88pub fn ununderscore_funcs(module: elements::Module) -> elements::Module {
89	foreach_public_func_name(module, |n| { n.remove(0); })
90}
91
92pub fn shrink_unknown_stack(
93	mut module: elements::Module,
94	// for example, `shrink_amount = (1MB - 64KB)` will limit stack to 64KB
95	shrink_amount: u32,
96) -> (elements::Module, u32) {
97	let mut new_stack_top = 0;
98	for section in module.sections_mut() {
99		match section {
100			elements::Section::Data(data_section) => {
101				for data_segment in data_section.entries_mut() {
102					if *data_segment
103						.offset()
104						.as_ref()
105						.expect("tetsy-wasm is compiled without bulk-memory operations")
106						.code() == [elements::Instruction::I32Const(4), elements::Instruction::End]
107					{
108						assert_eq!(data_segment.value().len(), 4);
109						let current_val = LittleEndian::read_u32(data_segment.value());
110						let new_val = current_val - shrink_amount;
111						LittleEndian::write_u32(data_segment.value_mut(), new_val);
112						new_stack_top = new_val;
113					}
114				}
115			},
116			_ => continue
117		}
118	}
119	(module, new_stack_top)
120}
121
122pub fn externalize(
123	module: elements::Module,
124	replaced_funcs: Vec<&str>,
125) -> elements::Module {
126   // Save import functions number for later
127	let import_funcs_total = module
128		.import_section().expect("Import section to exist")
129		.entries()
130		.iter()
131		.filter(|e| matches!(e.external(), &elements::External::Function(_)))
132		.count();
133
134	// First, we find functions indices that are to be rewired to externals
135	//   Triple is (function_index (callable), type_index, function_name)
136	let mut replaces: Vec<Insertion> = replaced_funcs
137		.into_iter()
138		.filter_map(|f| {
139			let export = module
140				.export_section().expect("Export section to exist")
141				.entries().iter().enumerate()
142				.find(|&(_, entry)| entry.field() == f)
143				.expect("All functions of interest to exist");
144
145			if let elements::Internal::Function(func_idx) = *export.1.internal() {
146				let type_ref = module
147					.function_section().expect("Functions section to exist")
148					.entries()[func_idx as usize - import_funcs_total]
149					.type_ref();
150
151				Some((export.0, func_idx, type_ref, export.1.field().to_owned()))
152			} else {
153				None
154			}
155		})
156		.collect();
157
158	replaces.sort_by_key(|e| e.0);
159
160	// Second, we duplicate them as import definitions
161	let mut mbuilder = builder::from_module(module);
162	for (_, _, type_ref, field) in replaces.iter() {
163		mbuilder.push_import(
164			builder::import()
165				.module("env")
166				.field(field)
167				.external().func(*type_ref)
168				.build()
169		);
170	}
171
172	// Back to mutable access
173	let mut module = mbuilder.build();
174
175	// Third, rewire all calls to imported functions and update all other calls indices
176	for section in module.sections_mut() {
177		match section {
178			elements::Section::Code(code_section) => {
179				for func_body in code_section.bodies_mut() {
180					update_call_index(func_body.code_mut(), import_funcs_total, &replaces);
181				}
182			},
183			elements::Section::Export(export_section) => {
184				for export in export_section.entries_mut() {
185					if let elements::Internal::Function(func_index) = export.internal_mut() {
186						if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; }
187					}
188				}
189			},
190			elements::Section::Element(elements_section) => {
191				for segment in elements_section.entries_mut() {
192					// update all indirect call addresses initial values
193					for func_index in segment.members_mut() {
194						if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; }
195					}
196				}
197			},
198			_ => { }
199		}
200	}
201
202	module
203
204}