1use core::fmt::Display;
4use std::{collections::HashMap, path::PathBuf};
5
6#[cfg(not(target_arch = "wasm32"))]
7use std::process::{Command, Stdio};
8
9use itertools::Itertools;
10use target_lexicon::Architecture;
11use wasmer_types::{
12 CompileError, FunctionIndex, FunctionType, LocalFunctionIndex, SignatureIndex, Type,
13};
14
15#[cfg(not(target_arch = "wasm32"))]
16use tempfile::NamedTempFile;
17
18#[derive(Debug, Clone)]
21pub enum CompiledKind {
22 Local(LocalFunctionIndex, String),
24 FunctionCallTrampoline(SignatureIndex, FunctionType),
26 DynamicFunctionTrampoline(FunctionIndex, FunctionType),
28 ImportFunctionTrampoline(FunctionIndex, FunctionType),
30 Module,
32}
33
34pub fn types_to_signature(types: &[Type]) -> String {
37 let tokens = types
38 .iter()
39 .map(|ty| match ty {
40 Type::I32 => "i",
41 Type::I64 => "I",
42 Type::F32 => "f",
43 Type::F64 => "F",
44 Type::V128 => "v",
45 Type::ExternRef => "e",
46 Type::FuncRef => "r",
47 Type::ExceptionRef => "x",
48 })
49 .collect_vec();
50 tokens
52 .chunk_by(|a, b| a == b)
53 .map(|chunk| {
54 if chunk.len() >= 8 {
55 format!("{}x{}", chunk.len(), chunk[0])
56 } else {
57 chunk.to_owned().join("")
58 }
59 })
60 .join("")
61}
62
63fn sanitize_filename(name: &str) -> String {
65 name.chars()
66 .map(|c| {
67 if c.is_alphanumeric() || c == '_' || c == '-' {
68 c
69 } else {
70 '_'
71 }
72 })
73 .collect()
74}
75
76pub fn function_kind_to_filename(kind: &CompiledKind, suffix: &str) -> String {
79 match kind {
80 CompiledKind::Local(local_func_index, name) => {
81 let mut name = sanitize_filename(name);
82
83 const PATH_LIMIT: usize = 255;
85
86 if name.len() + suffix.len() > PATH_LIMIT {
87 let id_string = local_func_index.as_u32().to_string();
88 name.truncate(PATH_LIMIT - id_string.len() - suffix.len() - 1);
89 name.push('_');
90 name.push_str(&id_string);
91 name.push_str(suffix);
92 } else {
93 name.push_str(suffix);
94 }
95
96 debug_assert!(name.len() <= PATH_LIMIT);
97 name
98 }
99 CompiledKind::FunctionCallTrampoline(_, func_type) => format!(
100 "trampoline_call_{}_{}{suffix}",
101 types_to_signature(func_type.params()),
102 types_to_signature(func_type.results())
103 ),
104 CompiledKind::DynamicFunctionTrampoline(_, func_type) => format!(
105 "trampoline_dynamic_{}_{}{suffix}",
106 types_to_signature(func_type.params()),
107 types_to_signature(func_type.results())
108 ),
109 CompiledKind::ImportFunctionTrampoline(_, func_type) => format!(
110 "trampoline_import_{}_{}{suffix}",
111 types_to_signature(func_type.params()),
112 types_to_signature(func_type.results())
113 ),
114 CompiledKind::Module => "Module".to_string(),
115 }
116}
117
118pub trait CompiledFunctionExt {
120 fn linkage_name(&self) -> String;
122
123 fn traps_name(&self) -> String {
125 format!("{}.traps", self.linkage_name())
126 }
127}
128
129impl CompiledFunctionExt for CompiledKind {
130 fn linkage_name(&self) -> String {
131 match self {
132 Self::Local(index, _) => format!("f{}", index.as_u32()),
133 Self::FunctionCallTrampoline(index, _) => format!("t{}", index.as_u32()),
134 Self::DynamicFunctionTrampoline(index, _) => format!("dt{}", index.as_u32()),
135 Self::ImportFunctionTrampoline(index, _) => format!("i{}", index.as_u32()),
136 Self::Module => "Module".to_string(),
137 }
138 }
139}
140
141#[cfg(not(target_arch = "wasm32"))]
146pub fn save_assembly_to_file<C: Display>(
147 arch: Architecture,
148 path: PathBuf,
149 body: &[u8],
150 assembly_comments: HashMap<usize, C>,
151) -> Result<(), CompileError> {
152 use std::{fs::File, io::Write};
153 use which::which;
154
155 #[derive(Debug)]
156 struct DecodedInsn<'a> {
157 offset: usize,
158 insn: &'a str,
159 }
160
161 fn parse_instructions(content: &str) -> Result<Vec<DecodedInsn<'_>>, CompileError> {
162 content
163 .lines()
164 .map(|line| line.trim())
165 .skip_while(|l| !l.starts_with("0000000000000000"))
166 .skip(1)
167 .filter(|line| line.trim() != "...")
168 .map(|line| -> Result<DecodedInsn<'_>, CompileError> {
169 let (offset, insn_part) = line.split_once(':').ok_or(CompileError::Codegen(
170 format!("cannot parse objdump line: '{line}'"),
171 ))?;
172 let insn = insn_part
174 .trim()
175 .split_once('\t')
176 .map_or("", |(_data, insn)| insn)
177 .trim();
178 Ok(DecodedInsn {
179 offset: usize::from_str_radix(offset, 16).map_err(|err| {
180 CompileError::Codegen(format!("hex number expected: {err}"))
181 })?,
182 insn,
183 })
184 })
185 .collect()
186 }
187
188 let mut tmpfile = NamedTempFile::new()
190 .map_err(|err| CompileError::Codegen(format!("cannot create temporary file: {err}")))?;
191 tmpfile
192 .write_all(body)
193 .map_err(|err| CompileError::Codegen(format!("assembly dump write failed: {err}")))?;
194 tmpfile
195 .flush()
196 .map_err(|err| CompileError::Codegen(format!("flush failed: {err}")))?;
197
198 let (objdump_arch, objdump_binary) = match arch {
199 Architecture::X86_64 => ("i386:x86-64", "x86_64-linux-gnu-objdump"),
200 Architecture::Aarch64(..) => ("aarch64", "aarch64-linux-gnu-objdump"),
201 Architecture::Riscv64(..) => ("riscv:rv64", "riscv64-linux-gnu-objdump"),
202 _ => {
203 return Err(CompileError::Codegen(
204 "Assembly dumping is not supported for this architecture".to_string(),
205 ));
206 }
207 };
208
209 let bins = [objdump_binary, "objdump"];
210 let objdump_binary = bins.iter().find(|bin| which(bin).is_ok());
211 let Some(objdump_binary) = objdump_binary else {
212 return Ok(());
214 };
215
216 let command = Command::new(objdump_binary)
217 .arg("-b")
218 .arg("binary")
219 .arg("-m")
220 .arg(objdump_arch)
221 .arg("-D")
222 .arg(tmpfile.path())
223 .stdout(Stdio::piped())
224 .stderr(Stdio::null())
225 .spawn();
226
227 let Ok(command) = command else {
228 return Ok(());
230 };
231
232 let output = command
233 .wait_with_output()
234 .map_err(|err| CompileError::Codegen(format!("failed to read stdout: {err}")))?;
235 let content = String::from_utf8_lossy(&output.stdout);
236
237 let parsed_instructions = parse_instructions(content.as_ref())?;
238
239 let mut file = File::create(path).map_err(|err| {
240 CompileError::Codegen(format!("debug object file creation failed: {err}"))
241 })?;
242
243 for insn in parsed_instructions {
245 if let Some(comment) = assembly_comments.get(&insn.offset) {
246 file.write_all(format!(" \t\t;; {comment}\n").as_bytes())
247 .map_err(|err| {
248 CompileError::Codegen(format!("cannot write content to object file: {err}"))
249 })?;
250 }
251 file.write_all(format!("{:6x}:\t\t{}\n", insn.offset, insn.insn).as_bytes())
252 .map_err(|err| {
253 CompileError::Codegen(format!("cannot write content to object file: {err}"))
254 })?;
255 }
256
257 Ok(())
258}
259
260#[cfg(target_arch = "wasm32")]
265pub fn save_assembly_to_file<C: Display>(
266 _arch: Architecture,
267 _path: PathBuf,
268 _body: &[u8],
269 _assembly_comments: HashMap<usize, C>,
270) -> Result<(), CompileError> {
271 Ok(())
272}