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 object_filename(&self) -> String {
125 format!("{}.o", self.linkage_name())
126 }
127
128 fn traps_name(&self) -> String {
130 format!("{}.traps", self.linkage_name())
131 }
132}
133
134impl CompiledFunctionExt for CompiledKind {
135 fn linkage_name(&self) -> String {
136 match self {
137 Self::Local(index, _) => format!("f{}", index.as_u32()),
138 Self::FunctionCallTrampoline(index, _) => format!("t{}", index.as_u32()),
139 Self::DynamicFunctionTrampoline(index, _) => format!("dt{}", index.as_u32()),
140 Self::ImportFunctionTrampoline(index, _) => format!("i{}", index.as_u32()),
141 Self::Module => "Module".to_string(),
142 }
143 }
144}
145
146#[cfg(not(target_arch = "wasm32"))]
151pub fn save_assembly_to_file<C: Display>(
152 arch: Architecture,
153 path: PathBuf,
154 body: &[u8],
155 assembly_comments: HashMap<usize, C>,
156) -> Result<(), CompileError> {
157 use std::{fs::File, io::Write};
158 use which::which;
159
160 #[derive(Debug)]
161 struct DecodedInsn<'a> {
162 offset: usize,
163 insn: &'a str,
164 }
165
166 fn parse_instructions(content: &str) -> Result<Vec<DecodedInsn<'_>>, CompileError> {
167 content
168 .lines()
169 .map(|line| line.trim())
170 .skip_while(|l| !l.starts_with("0000000000000000"))
171 .skip(1)
172 .filter(|line| line.trim() != "...")
173 .map(|line| -> Result<DecodedInsn<'_>, CompileError> {
174 let (offset, insn_part) = line.split_once(':').ok_or(CompileError::Codegen(
175 format!("cannot parse objdump line: '{line}'"),
176 ))?;
177 let insn = insn_part
179 .trim()
180 .split_once('\t')
181 .map_or("", |(_data, insn)| insn)
182 .trim();
183 Ok(DecodedInsn {
184 offset: usize::from_str_radix(offset, 16).map_err(|err| {
185 CompileError::Codegen(format!("hex number expected: {err}"))
186 })?,
187 insn,
188 })
189 })
190 .collect()
191 }
192
193 let mut tmpfile = NamedTempFile::new()
195 .map_err(|err| CompileError::Codegen(format!("cannot create temporary file: {err}")))?;
196 tmpfile
197 .write_all(body)
198 .map_err(|err| CompileError::Codegen(format!("assembly dump write failed: {err}")))?;
199 tmpfile
200 .flush()
201 .map_err(|err| CompileError::Codegen(format!("flush failed: {err}")))?;
202
203 let (objdump_arch, objdump_binary) = match arch {
204 Architecture::X86_64 => ("i386:x86-64", "x86_64-linux-gnu-objdump"),
205 Architecture::Aarch64(..) => ("aarch64", "aarch64-linux-gnu-objdump"),
206 Architecture::Riscv64(..) => ("riscv:rv64", "riscv64-linux-gnu-objdump"),
207 _ => {
208 return Err(CompileError::Codegen(
209 "Assembly dumping is not supported for this architecture".to_string(),
210 ));
211 }
212 };
213
214 let bins = [objdump_binary, "objdump"];
215 let objdump_binary = bins.iter().find(|bin| which(bin).is_ok());
216 let Some(objdump_binary) = objdump_binary else {
217 return Ok(());
219 };
220
221 let command = Command::new(objdump_binary)
222 .arg("-b")
223 .arg("binary")
224 .arg("-m")
225 .arg(objdump_arch)
226 .arg("-D")
227 .arg(tmpfile.path())
228 .stdout(Stdio::piped())
229 .stderr(Stdio::null())
230 .spawn();
231
232 let Ok(command) = command else {
233 return Ok(());
235 };
236
237 let output = command
238 .wait_with_output()
239 .map_err(|err| CompileError::Codegen(format!("failed to read stdout: {err}")))?;
240 let content = String::from_utf8_lossy(&output.stdout);
241
242 let parsed_instructions = parse_instructions(content.as_ref())?;
243
244 let mut file = File::create(path).map_err(|err| {
245 CompileError::Codegen(format!("debug object file creation failed: {err}"))
246 })?;
247
248 for insn in parsed_instructions {
250 if let Some(comment) = assembly_comments.get(&insn.offset) {
251 file.write_all(format!(" \t\t;; {comment}\n").as_bytes())
252 .map_err(|err| {
253 CompileError::Codegen(format!("cannot write content to object file: {err}"))
254 })?;
255 }
256 file.write_all(format!("{:6x}:\t\t{}\n", insn.offset, insn.insn).as_bytes())
257 .map_err(|err| {
258 CompileError::Codegen(format!("cannot write content to object file: {err}"))
259 })?;
260 }
261
262 Ok(())
263}
264
265#[cfg(target_arch = "wasm32")]
270pub fn save_assembly_to_file<C: Display>(
271 _arch: Architecture,
272 _path: PathBuf,
273 _body: &[u8],
274 _assembly_comments: HashMap<usize, C>,
275) -> Result<(), CompileError> {
276 Ok(())
277}