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