1use crate::info::ExportedMethodInfo;
2use crate::Error;
3use libflate::gzip;
4use std::borrow::Cow;
5use std::collections::HashMap;
6use std::io::{self, Read};
7use walrus::*;
8#[cfg(feature = "wasm-opt")]
9use wasm_opt::Feature;
10use wasmparser::{Validator, WasmFeaturesInflated};
11
12pub const WASM_MAGIC_BYTES: &[u8] = &[0, 97, 115, 109];
13
14pub const GZIPPED_WASM_MAGIC_BYTES: &[u8] = &[31, 139, 8];
15
16#[cfg(feature = "wasm-opt")]
20pub const IC_ENABLED_WASM_FEATURES: [Feature; 7] = [
21 Feature::MutableGlobals,
22 Feature::TruncSat,
23 Feature::Simd,
24 Feature::BulkMemory,
25 Feature::SignExt,
26 Feature::ReferenceTypes,
27 Feature::Memory64,
28];
29
30pub const IC_ENABLED_WASM_FEATURES_INFLATED: WasmFeaturesInflated = WasmFeaturesInflated {
31 mutable_global: true,
32 saturating_float_to_int: true,
33 sign_extension: true,
34 reference_types: true,
35 multi_value: true,
36 bulk_memory: true,
37 simd: true,
38 relaxed_simd: false,
39 threads: true,
40 shared_everything_threads: true,
41 tail_call: false,
42 floats: true,
43 multi_memory: true,
44 exceptions: true,
45 memory64: true,
46 extended_const: true,
47 component_model: true,
48 function_references: false,
49 memory_control: true,
50 gc: false,
51 custom_page_sizes: true,
52 component_model_values: true,
53 component_model_nested_names: true,
54 component_model_more_flags: true,
55 component_model_multiple_returns: true,
56 legacy_exceptions: true,
57 gc_types: true,
58 stack_switching: true,
59 wide_arithmetic: false,
60 component_model_async: true,
61};
62
63pub fn make_validator_with_features() -> Validator {
64 Validator::new_with_features(IC_ENABLED_WASM_FEATURES_INFLATED.into())
65}
66
67fn wasm_parser_config(keep_name_section: bool) -> ModuleConfig {
68 let mut config = walrus::ModuleConfig::new();
69 config.generate_name_section(keep_name_section);
70 config.generate_producers_section(false);
71 config
72}
73
74fn decompress(bytes: &[u8]) -> Result<Vec<u8>, std::io::Error> {
75 let mut decoder = gzip::Decoder::new(bytes)?;
76 let mut decoded_data = Vec::new();
77 decoder.read_to_end(&mut decoded_data)?;
78 Ok(decoded_data)
79}
80
81pub fn parse_wasm(bytes: &[u8], keep_name_section: bool) -> Result<Module, Error> {
82 let wasm = if bytes.starts_with(WASM_MAGIC_BYTES) {
83 Ok(Cow::Borrowed(bytes))
84 } else if bytes.starts_with(GZIPPED_WASM_MAGIC_BYTES) {
85 decompress(bytes).map(Cow::Owned)
86 } else {
87 Err(io::Error::new(
88 io::ErrorKind::InvalidInput,
89 "Input must be either gzipped or uncompressed WASM.",
90 ))
91 }
92 .map_err(Error::IO)?;
93 let config = wasm_parser_config(keep_name_section);
94 config
95 .parse(&wasm)
96 .map_err(|e| Error::WasmParse(e.to_string()))
97}
98
99pub fn parse_wasm_file(file: std::path::PathBuf, keep_name_section: bool) -> Result<Module, Error> {
100 let bytes = std::fs::read(file).map_err(Error::IO)?;
101 parse_wasm(&bytes[..], keep_name_section)
102}
103
104#[derive(Clone, Copy, PartialEq, Eq)]
105pub(crate) enum InjectionKind {
106 Static,
107 Dynamic,
108 Dynamic64,
109}
110
111pub(crate) struct FunctionCost(HashMap<FunctionId, (i64, InjectionKind)>);
112impl FunctionCost {
113 pub fn new(m: &Module) -> Self {
114 let mut res = HashMap::new();
115 for (method, func) in m.imports.iter().filter_map(|i| {
116 if let ImportKind::Function(func) = i.kind {
117 if i.module == "ic0" {
118 Some((i.name.as_str(), func))
119 } else {
120 None
121 }
122 } else {
123 None
124 }
125 }) {
126 use InjectionKind::*;
127 let cost = match method {
129 "accept_message" => (500, Static),
130 "call_cycles_add" | "call_cycles_add128" => (500, Static),
131 "call_data_append" => (500, Dynamic),
132 "call_new" => (1500, Static),
133 "call_on_cleanup" => (500, Static),
134 "call_perform" => (5000, Static),
135 "canister_cycle_balance" | "canister_cycle_balance128" => (500, Static),
136 "canister_self_copy" => (500, Dynamic),
137 "canister_self_size" => (500, Static),
138 "canister_status" | "canister_version" => (500, Static),
139 "certified_data_set" => (500, Dynamic),
140 "data_certificate_copy" => (500, Dynamic),
141 "data_certificate_present" | "data_certificate_size" => (500, Static),
142 "debug_print" => (100, Dynamic),
143 "global_timer_set" => (500, Static),
144 "is_controller" => (1000, Dynamic),
145 "msg_arg_data_copy" => (500, Dynamic),
146 "msg_arg_data_size" => (500, Static),
147 "msg_caller_copy" => (500, Dynamic),
148 "msg_caller_size" => (500, Static),
149 "msg_cycles_accept" | "msg_cycles_accept128" => (500, Static),
150 "msg_cycles_available" | "msg_cycles_available128" => (500, Static),
151 "msg_cycles_refunded" | "msg_cycles_refunded128" => (500, Static),
152 "cycles_burn128" => (100, Static),
153 "msg_method_name_copy" => (500, Dynamic),
154 "msg_method_name_size" => (500, Static),
155 "msg_reject_code" | "msg_reject_msg_size" => (500, Static),
156 "msg_reject_msg_copy" => (500, Dynamic),
157 "msg_reject" => (500, Dynamic),
158 "msg_reply_data_append" => (500, Dynamic),
159 "msg_reply" => (500, Static),
160 "performance_counter" => (200, Static),
161 "stable_grow" | "stable64_grow" => (100, Static),
162 "stable_size" | "stable64_size" => (20, Static),
163 "stable_read" => (20, Dynamic),
164 "stable_write" => (20, Dynamic),
165 "stable64_read" => (20, Dynamic64),
166 "stable64_write" => (20, Dynamic64),
167 "trap" => (500, Dynamic),
168 "time" => (500, Static),
169 _ => (20, Static),
170 };
171 res.insert(func, cost);
172 }
173 Self(res)
174 }
175 pub fn get_cost(&self, id: FunctionId) -> Option<(i64, InjectionKind)> {
176 self.0.get(&id).copied()
177 }
178}
179pub(crate) fn instr_cost(i: &ir::Instr) -> i64 {
180 use ir::*;
181 use BinaryOp::*;
182 use UnaryOp::*;
183 match i {
185 Instr::Block(..) | Instr::Loop(..) => 0,
186 Instr::Const(..) | Instr::Load(..) | Instr::Store(..) => 1,
187 Instr::GlobalGet(..) | Instr::GlobalSet(..) => 2,
188 Instr::TableGet(..) | Instr::TableSet(..) => 5,
189 Instr::TableGrow(..) | Instr::MemoryGrow(..) => 300,
190 Instr::MemorySize(..) => 20,
191 Instr::TableSize(..) => 100,
192 Instr::MemoryFill(..) | Instr::MemoryCopy(..) | Instr::MemoryInit(..) => 100,
193 Instr::TableFill(..) | Instr::TableCopy(..) | Instr::TableInit(..) => 100,
194 Instr::DataDrop(..) | Instr::ElemDrop(..) => 300,
195 Instr::Call(..) => 5,
196 Instr::CallIndirect(..) => 10, Instr::IfElse(..) | Instr::Br(..) | Instr::BrIf(..) | Instr::BrTable(..) => 2,
198 Instr::RefIsNull(..) => 5,
199 Instr::RefFunc(..) => 130,
200 Instr::Unop(Unop { op }) => match op {
201 F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt => 20,
202 F64Ceil | F64Floor | F64Trunc | F64Nearest | F64Sqrt => 20,
203 F32Abs | F32Neg | F64Abs | F64Neg => 2,
204 F32ConvertSI32 | F64ConvertSI64 | F32ConvertSI64 | F64ConvertSI32 => 3,
205 F64ConvertUI32 | F32ConvertUI64 | F32ConvertUI32 | F64ConvertUI64 => 16,
206 I64TruncSF32 | I64TruncUF32 | I64TruncSF64 | I64TruncUF64 => 20,
207 I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 => 20, _ => 1,
209 },
210 Instr::Binop(Binop { op }) => match op {
211 I32DivS | I32DivU | I32RemS | I32RemU => 10,
212 I64DivS | I64DivU | I64RemS | I64RemU => 10,
213 F32Add | F32Sub | F32Mul | F32Div | F32Min | F32Max => 20,
214 F64Add | F64Sub | F64Mul | F64Div | F64Min | F64Max => 20,
215 F32Copysign | F64Copysign => 2,
216 F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge => 3,
217 F64Eq | F64Ne | F64Lt | F64Gt | F64Le | F64Ge => 3,
218 _ => 1,
219 },
220 _ => 1,
221 }
222}
223
224pub(crate) fn get_ic_func_id(m: &mut Module, method: &str) -> FunctionId {
225 match m.imports.find("ic0", method) {
226 Some(id) => match m.imports.get(id).kind {
227 ImportKind::Function(func_id) => func_id,
228 _ => unreachable!(),
229 },
230 None => {
231 let ty = match method {
232 "stable_write" => m
233 .types
234 .add(&[ValType::I32, ValType::I32, ValType::I32], &[]),
235 "stable64_write" => m
236 .types
237 .add(&[ValType::I64, ValType::I64, ValType::I64], &[]),
238 "stable_read" => m
239 .types
240 .add(&[ValType::I32, ValType::I32, ValType::I32], &[]),
241 "stable64_read" => m
242 .types
243 .add(&[ValType::I64, ValType::I64, ValType::I64], &[]),
244 "stable_grow" => m.types.add(&[ValType::I32], &[ValType::I32]),
245 "stable64_grow" => m.types.add(&[ValType::I64], &[ValType::I64]),
246 "stable_size" => m.types.add(&[], &[ValType::I32]),
247 "stable64_size" => m.types.add(&[], &[ValType::I64]),
248 "call_cycles_add" => m.types.add(&[ValType::I64], &[]),
249 "call_cycles_add128" => m.types.add(&[ValType::I64, ValType::I64], &[]),
250 "cycles_burn128" => m
251 .types
252 .add(&[ValType::I64, ValType::I64, ValType::I32], &[]),
253 "call_new" => m.types.add(
254 &[
255 ValType::I32,
256 ValType::I32,
257 ValType::I32,
258 ValType::I32,
259 ValType::I32,
260 ValType::I32,
261 ValType::I32,
262 ValType::I32,
263 ],
264 &[],
265 ),
266 "debug_print" => m.types.add(&[ValType::I32, ValType::I32], &[]),
267 "trap" => m.types.add(&[ValType::I32, ValType::I32], &[]),
268 "msg_arg_data_size" => m.types.add(&[], &[ValType::I32]),
269 "msg_arg_data_copy" => m
270 .types
271 .add(&[ValType::I32, ValType::I32, ValType::I32], &[]),
272 "msg_reply_data_append" => m.types.add(&[ValType::I32, ValType::I32], &[]),
273 "msg_reply" => m.types.add(&[], &[]),
274 _ => unreachable!(),
275 };
276 m.add_import_func("ic0", method, ty).0
277 }
278 }
279}
280
281pub(crate) fn get_memory_id(m: &Module) -> MemoryId {
282 m.memories
283 .iter()
284 .next()
285 .expect("only single memory is supported")
286 .id()
287}
288
289pub(crate) fn get_export_func_id(m: &Module, method: &str) -> Option<FunctionId> {
290 let e = m.exports.iter().find(|e| e.name == method)?;
291 if let ExportItem::Function(id) = e.item {
292 Some(id)
293 } else {
294 None
295 }
296}
297pub(crate) fn get_or_create_export_func<'a>(
298 m: &'a mut Module,
299 method: &'a str,
300) -> InstrSeqBuilder<'a> {
301 let id = match get_export_func_id(m, method) {
302 Some(id) => id,
303 None => {
304 let builder = FunctionBuilder::new(&mut m.types, &[], &[]);
305 let id = builder.finish(vec![], &mut m.funcs);
306 m.exports.add(method, id);
307 id
308 }
309 };
310 get_builder(m, id)
311}
312
313pub(crate) fn get_builder(m: &mut Module, id: FunctionId) -> InstrSeqBuilder<'_> {
314 if let FunctionKind::Local(func) = &mut m.funcs.get_mut(id).kind {
315 let id = func.entry_block();
316 func.builder_mut().instr_seq(id)
317 } else {
318 unreachable!()
319 }
320}
321
322pub(crate) fn inject_top(builder: &mut InstrSeqBuilder<'_>, instrs: Vec<ir::Instr>) {
323 for instr in instrs.into_iter().rev() {
324 builder.instr_at(0, instr);
325 }
326}
327
328pub(crate) fn get_exported_methods(m: &Module) -> Vec<ExportedMethodInfo> {
329 m.exports
330 .iter()
331 .filter_map(|e| match e.item {
332 ExportItem::Function(id) => Some(ExportedMethodInfo {
333 name: e.name.clone(),
334 internal_name: get_func_name(m, id),
335 }),
336 _ => None,
337 })
338 .collect()
339}
340
341pub(crate) fn get_func_name(m: &Module, id: FunctionId) -> String {
342 m.funcs
343 .get(id)
344 .name
345 .as_ref()
346 .unwrap_or(&format!("func_{}", id.index()))
347 .to_string()
348}
349
350pub(crate) fn is_motoko_canister(m: &Module) -> bool {
351 m.customs.iter().any(|(_, s)| {
352 s.name() == "icp:private motoko:compiler" || s.name() == "icp:public motoko:compiler"
353 }) || m
354 .exports
355 .iter()
356 .any(|e| e.name == "canister_update __motoko_async_helper")
357}
358
359pub(crate) fn is_motoko_wasm_data_section(blob: &[u8]) -> Option<&[u8]> {
360 let len = blob.len() as u32;
361 if len > 100
362 && blob[0..4] == [0x11, 0x00, 0x00, 0x00] && blob[8..12] == [0x00, 0x61, 0x73, 0x6d]
364 {
366 let decoded_len = u32::from_le_bytes(blob[4..8].try_into().unwrap());
367 if decoded_len + 8 == len {
368 return Some(&blob[8..]);
369 }
370 }
371 None
372}
373
374pub(crate) fn get_motoko_wasm_data_sections(m: &Module) -> Vec<(DataId, Module)> {
375 m.data
376 .iter()
377 .filter_map(|d| {
378 let blob = is_motoko_wasm_data_section(&d.value)?;
379 let mut config = ModuleConfig::new();
380 config.generate_name_section(false);
381 config.generate_producers_section(false);
382 let m = config.parse(blob).ok()?;
383 Some((d.id(), m))
384 })
385 .collect()
386}
387
388pub(crate) fn encode_module_as_data_section(mut m: Module) -> Vec<u8> {
389 let blob = m.emit_wasm();
390 let blob_len = blob.len();
391 let mut res = Vec::with_capacity(blob_len + 8);
392 res.extend_from_slice(&[0x11, 0x00, 0x00, 0x00]);
393 let encoded_len = (blob_len as u32).to_le_bytes();
394 res.extend_from_slice(&encoded_len);
395 res.extend_from_slice(&blob);
396 res
397}