1#![allow(unused_imports, dead_code)]
4
5use crate::codegen::FuncGen;
6use crate::config::{self, Singlepass};
7#[cfg(feature = "unwind")]
8use crate::dwarf::WriterRelocate;
9use crate::elf::{self, CompileOutput, compile_output_in_memory, compile_output_objects};
10use crate::machine::Machine;
11use crate::machine::{
12 gen_import_call_trampoline, gen_std_dynamic_import_trampoline, gen_std_trampoline,
13};
14use crate::machine_arm64::MachineARM64;
15use crate::machine_riscv::MachineRiscv;
16use crate::machine_x64::MachineX86_64;
17use crate::unwind::UnwindFrame;
18#[cfg(feature = "unwind")]
19use crate::unwind::create_systemv_cie;
20use enumset::EnumSet;
21#[cfg(feature = "unwind")]
22use gimli::write::{EhFrame, FrameTable, Writer};
23use rayon::prelude::{IntoParallelIterator, ParallelIterator};
24use std::collections::HashMap;
25use std::sync::Arc;
26use wasmer_compiler::WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE;
27use wasmer_compiler::misc::{CompiledKind, save_assembly_to_file, types_to_signature};
28use wasmer_compiler::progress::ProgressContext;
29use wasmer_compiler::serialize::SerializableModule;
30use wasmer_compiler::types::function::Compilation;
31use wasmer_compiler::{
32 Compiler, CompilerConfig, FunctionBinaryReader, FunctionBodyData, MiddlewareBinaryReader,
33 ModuleMiddleware, ModuleMiddlewareChain, ModuleTranslationState, WasmSourceMap,
34 types::{
35 function::{FunctionBody, RkyvCompilation, UnwindInfo},
36 module::CompileModuleInfo,
37 section::SectionIndex,
38 },
39};
40use wasmer_types::entity::{EntityRef, PrimaryMap};
41use wasmer_types::target::{Architecture, CallingConvention, CpuFeature, Target};
42use wasmer_types::{
43 CompilationProgressCallback, CompileError, FunctionIndex, FunctionType, LocalFunctionIndex,
44 MemoryIndex, ModuleInfo, TableIndex, TrapCode, TrapInformation, Type, VMOffsets,
45};
46
47#[derive(Debug)]
50pub struct SinglepassCompiler {
51 config: Singlepass,
52}
53
54impl SinglepassCompiler {
55 pub fn new(config: Singlepass) -> Self {
57 Self { config }
58 }
59
60 fn config(&self) -> &Singlepass {
62 &self.config
63 }
64
65 #[allow(clippy::too_many_arguments)]
66 fn compile_module_internal(
67 &self,
68 pool: &rayon::ThreadPool,
69 target: &Target,
70 compile_info: &CompileModuleInfo,
71 compile_info_blob: &[u8],
72 module_translation: &ModuleTranslationState,
73 function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
74 progress_callback: Option<&CompilationProgressCallback>,
75 ) -> Result<Compilation, CompileError> {
76 let arch = target.triple().architecture;
77 match arch {
78 Architecture::X86_64 => {}
79 Architecture::Aarch64(_) => {}
80 Architecture::Riscv64(_) => {}
81 _ => {
82 return Err(CompileError::UnsupportedTarget(
83 target.triple().architecture.to_string(),
84 ));
85 }
86 };
87
88 let calling_convention = match target.triple().default_calling_convention() {
89 Ok(CallingConvention::WindowsFastcall) => CallingConvention::WindowsFastcall,
90 Ok(CallingConvention::SystemV) => CallingConvention::SystemV,
91 Ok(CallingConvention::AppleAarch64) => CallingConvention::AppleAarch64,
92 _ => match target.triple().architecture {
93 Architecture::Riscv64(_) => CallingConvention::SystemV,
94 _ => {
95 return Err(CompileError::UnsupportedTarget(
96 "Unsupported Calling convention for Singlepass compiler".to_string(),
97 ));
98 }
99 },
100 };
101
102 let module = &compile_info.module;
103 let source_map = Arc::new(if self.config.experimental_artifact {
104 WasmSourceMap::new(module, module_translation, &function_body_inputs)
105 .map_err(CompileError::Codegen)?
106 } else {
107 WasmSourceMap::default()
108 });
109 let total_function_call_trampolines = module.signatures.len() as u64;
110 let total_dynamic_trampolines = module.num_imported_functions as u64;
111 let total_steps = WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE
112 * ((total_dynamic_trampolines + total_function_call_trampolines) as u64)
113 + function_body_inputs
114 .iter()
115 .map(|(_, body)| body.data.len() as u64)
116 .sum::<u64>();
117 let progress = progress_callback
118 .cloned()
119 .map(|cb| ProgressContext::new(cb, total_steps, "singlepass::functions"));
120
121 #[cfg(feature = "unwind")]
123 let dwarf_frametable = if function_body_inputs.is_empty() {
124 None
128 } else {
129 match target.triple().default_calling_convention() {
130 Ok(CallingConvention::SystemV) => {
131 match create_systemv_cie(target.triple().architecture) {
132 Some(cie) => {
133 let mut dwarf_frametable = FrameTable::default();
134 let cie_id = dwarf_frametable.add_cie(cie);
135 Some((dwarf_frametable, cie_id))
136 }
137 None => None,
138 }
139 }
140 _ => None,
141 }
142 };
143
144 let memory_styles = &compile_info.memory_styles;
145 let table_styles = &compile_info.table_styles;
146 let vmoffsets = VMOffsets::new(8, &compile_info.module);
147 let module = &compile_info.module;
148 let import_trampolines = (0..module.num_imported_functions)
149 .map(FunctionIndex::new)
150 .collect::<Vec<_>>()
151 .into_par_iter()
152 .map(|i| {
153 gen_import_call_trampoline(
154 &vmoffsets,
155 i,
156 &module.signatures[module.functions[i]],
157 target,
158 calling_convention,
159 self.config.experimental_artifact,
160 )
161 })
162 .collect::<Result<Vec<_>, CompileError>>()?;
163 let functions = function_body_inputs
164 .iter()
165 .collect::<Vec<(LocalFunctionIndex, &FunctionBodyData<'_>)>>()
166 .into_par_iter()
167 .map(|(i, input)| {
168 let middleware_chain = self
169 .config
170 .middlewares
171 .generate_function_middleware_chain(i);
172 let mut reader =
173 MiddlewareBinaryReader::new_with_offset(input.data, input.module_offset);
174 reader.set_middleware_chain(middleware_chain);
175
176 let mut locals = vec![];
178 let num_locals = reader.read_local_count()?;
179 for _ in 0..num_locals {
180 let (count, ty) = reader.read_local_decl()?;
181 for _ in 0..count {
182 locals.push(ty);
183 }
184 }
185
186 let res = match arch {
187 Architecture::X86_64 => {
188 let machine = MachineX86_64::new(Some(target.clone()))?;
189 let mut generator = FuncGen::new(
190 module,
191 &self.config,
192 &vmoffsets,
193 memory_styles,
194 table_styles,
195 i,
196 &locals,
197 machine,
198 calling_convention,
199 )?;
200 while generator.has_control_frames() {
201 generator.set_srcloc(reader.original_position() as u32);
202 let op = reader.read_operator()?;
203 generator.feed_operator(op)?;
204 }
205
206 generator.finalize(input, arch, target, &source_map)
207 }
208 Architecture::Aarch64(_) => {
209 let machine = MachineARM64::new(Some(target.clone()));
210 let mut generator = FuncGen::new(
211 module,
212 &self.config,
213 &vmoffsets,
214 memory_styles,
215 table_styles,
216 i,
217 &locals,
218 machine,
219 calling_convention,
220 )?;
221 while generator.has_control_frames() {
222 generator.set_srcloc(reader.original_position() as u32);
223 let op = reader.read_operator()?;
224 generator.feed_operator(op)?;
225 }
226
227 generator.finalize(input, arch, target, &source_map)
228 }
229 Architecture::Riscv64(_) => {
230 let machine = MachineRiscv::new(
231 Some(target.clone()),
232 self.config.allow_experimental_unaligned_memory_accesses,
233 )?;
234 let mut generator = FuncGen::new(
235 module,
236 &self.config,
237 &vmoffsets,
238 memory_styles,
239 table_styles,
240 i,
241 &locals,
242 machine,
243 calling_convention,
244 )?;
245 while generator.has_control_frames() {
246 generator.set_srcloc(reader.original_position() as u32);
247 let op = reader.read_operator()?;
248 generator.feed_operator(op)?;
249 }
250
251 generator.finalize(input, arch, target, &source_map)
252 }
253 _ => unimplemented!(),
254 }?;
255
256 if let Some(progress) = progress.as_ref() {
257 progress.notify_steps(input.data.len() as u64)?;
258 }
259
260 Ok(res)
261 })
262 .collect::<Result<Vec<_>, CompileError>>()?;
263 let function_max_stack_usage = functions
264 .iter()
265 .map(|output| match output {
266 CompileOutput::InMemory((function, _)) => function.maximum_stack_usage,
267 CompileOutput::Object(_, maximum_stack_usage) => *maximum_stack_usage,
268 })
269 .collect::<PrimaryMap<LocalFunctionIndex, Option<usize>>>();
270
271 let module_hash = module.hash_string();
272 let function_call_trampolines = module
273 .signatures
274 .iter()
275 .collect::<Vec<_>>()
276 .into_par_iter()
277 .map(
278 |(sig_index, func_type)| -> Result<CompileOutput<FunctionBody>, CompileError> {
279 let kind = CompiledKind::FunctionCallTrampoline(sig_index, func_type.clone());
280 let body = gen_std_trampoline(
281 func_type,
282 target,
283 calling_convention,
284 self.config.experimental_artifact.then_some(&kind),
285 )?;
286 if let Some(callbacks) = self.config.callbacks.as_ref()
287 && let CompileOutput::InMemory(body) = &body
288 {
289 callbacks.obj_memory_buffer(&kind, &module_hash, &body.body);
290 callbacks.asm_memory_buffer(
291 &kind,
292 &module_hash,
293 arch,
294 &body.body,
295 HashMap::new(),
296 )?;
297 }
298 if let Some(progress) = progress.as_ref() {
299 progress.notify_steps(WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE)?;
300 }
301
302 Ok(body)
303 },
304 )
305 .collect::<Result<Vec<_>, _>>()?;
306
307 let dynamic_function_trampolines = module
308 .imported_function_types()
309 .enumerate()
310 .collect::<Vec<_>>()
311 .into_par_iter()
312 .map(
313 |(index, func_type)| -> Result<CompileOutput<FunctionBody>, CompileError> {
314 let kind = CompiledKind::DynamicFunctionTrampoline(
315 FunctionIndex::from_u32(index as u32),
316 func_type.clone(),
317 );
318 let body = gen_std_dynamic_import_trampoline(
319 &vmoffsets,
320 &func_type,
321 target,
322 calling_convention,
323 self.config.experimental_artifact.then_some(&kind),
324 )?;
325 if let Some(callbacks) = self.config.callbacks.as_ref()
326 && let CompileOutput::InMemory(body) = &body
327 {
328 callbacks.obj_memory_buffer(&kind, &module_hash, &body.body);
329 callbacks.asm_memory_buffer(
330 &kind,
331 &module_hash,
332 arch,
333 &body.body,
334 HashMap::new(),
335 )?;
336 }
337 if let Some(progress) = progress.as_ref() {
338 progress.notify_steps(WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE)?;
339 }
340 Ok(body)
341 },
342 )
343 .collect::<Result<Vec<_>, _>>()?;
344
345 if self.config.experimental_artifact {
346 let object_files = compile_output_objects(functions);
347 let import_trampoline_objects = compile_output_objects(import_trampolines);
348 let trampoline_objects = compile_output_objects(function_call_trampolines);
349 let dynamic_trampoline_objects = compile_output_objects(dynamic_function_trampolines);
350
351 return elf::link_module(
352 pool,
353 target,
354 compile_info_blob,
355 object_files,
356 import_trampoline_objects,
357 trampoline_objects,
358 dynamic_trampoline_objects,
359 self.config
360 .callbacks
361 .as_ref()
362 .map(|callbacks| callbacks.debug_dir().clone()),
363 module.hash().map(|hash| hash.to_string()),
364 function_max_stack_usage,
365 );
366 }
367
368 #[cfg_attr(not(feature = "unwind"), allow(unused_variables))]
369 let (functions, fdes): (Vec<_>, Vec<_>) =
370 compile_output_in_memory(functions).into_iter().unzip();
371 #[cfg_attr(not(feature = "unwind"), allow(unused_mut))]
372 let mut custom_sections = compile_output_in_memory(import_trampolines)
373 .into_iter()
374 .collect::<PrimaryMap<SectionIndex, _>>();
375 let function_call_trampolines = compile_output_in_memory(function_call_trampolines)
376 .into_iter()
377 .collect::<PrimaryMap<_, _>>();
378 let dynamic_function_trampolines = compile_output_in_memory(dynamic_function_trampolines)
379 .into_iter()
380 .collect::<PrimaryMap<FunctionIndex, _>>();
381
382 #[allow(unused_mut)]
383 let mut unwind_info = UnwindInfo::default();
384
385 #[cfg(feature = "unwind")]
386 if let Some((mut dwarf_frametable, cie_id)) = dwarf_frametable {
387 for fde in fdes.into_iter().flatten() {
388 match fde {
389 UnwindFrame::SystemV(fde) => dwarf_frametable.add_fde(cie_id, fde),
390 }
391 }
392 let mut eh_frame = EhFrame(WriterRelocate::new(target.triple().endianness().ok()));
393 dwarf_frametable.write_eh_frame(&mut eh_frame).unwrap();
394 eh_frame.write(&[0, 0, 0, 0]).unwrap(); let eh_frame_section = eh_frame.0.into_section();
397 custom_sections.push(eh_frame_section);
398 unwind_info.eh_frame = Some(SectionIndex::new(custom_sections.len() - 1))
399 };
400
401 let got = wasmer_compiler::types::function::GOT::empty();
402
403 Ok(Compilation::Rkyv {
404 compilation: RkyvCompilation {
405 functions: functions.into_iter().collect(),
406 custom_sections,
407 function_call_trampolines,
408 dynamic_function_trampolines,
409 unwind_info,
410 got,
411 },
412 function_max_stack_usage,
413 })
414 }
415}
416
417impl Compiler for SinglepassCompiler {
418 fn name(&self) -> &str {
419 "singlepass"
420 }
421
422 fn get_debugger(&self) -> Option<wasmer_compiler::Debugger> {
423 self.config.debugger
424 }
425
426 fn deterministic_id(&self) -> String {
427 if self.config.experimental_artifact {
428 String::from("singlepass-elf")
429 } else {
430 String::from("singlepass")
431 }
432 }
433
434 fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>] {
436 &self.config.middlewares
437 }
438
439 fn compile_module(
442 &self,
443 target: &Target,
444 compile_info: &CompileModuleInfo,
445 compile_info_blob: &[u8],
446 module_translation: &ModuleTranslationState,
447 function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
448 progress_callback: Option<&CompilationProgressCallback>,
449 ) -> Result<Compilation, CompileError> {
450 let num_threads = self.config.num_threads.get();
451 let pool = rayon::ThreadPoolBuilder::new()
452 .num_threads(num_threads)
453 .build()
454 .map_err(|e| {
455 CompileError::Codegen(format!("failed to build rayon thread pool: {e}"))
456 })?;
457
458 pool.install(|| {
459 self.compile_module_internal(
460 &pool,
461 target,
462 compile_info,
463 compile_info_blob,
464 module_translation,
465 function_body_inputs,
466 progress_callback,
467 )
468 })
469 }
470
471 fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
472 let used = CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1;
473 cpu_features.intersection(used)
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480 use std::str::FromStr;
481 use target_lexicon::triple;
482 use wasmer_compiler::Features;
483 use wasmer_types::{
484 MemoryStyle, TableStyle,
485 target::{CpuFeature, Triple},
486 };
487
488 fn dummy_compilation_ingredients<'a>() -> (
489 CompileModuleInfo,
490 ModuleTranslationState,
491 PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
492 ) {
493 let compile_info = CompileModuleInfo {
494 features: Features::new(),
495 module: Arc::new(ModuleInfo::new()),
496 memory_styles: PrimaryMap::<MemoryIndex, MemoryStyle>::new(),
497 table_styles: PrimaryMap::<TableIndex, TableStyle>::new(),
498 function_max_stack_usage: PrimaryMap::new(),
499 };
500 let module_translation = ModuleTranslationState::new();
501 let function_body_inputs = PrimaryMap::<LocalFunctionIndex, FunctionBodyData<'_>>::new();
502 (compile_info, module_translation, function_body_inputs)
503 }
504
505 #[test]
506 fn errors_for_unsupported_targets() {
507 let compiler = SinglepassCompiler::new(Singlepass::default());
508
509 let linux32 = Target::new(triple!("i686-unknown-linux-gnu"), CpuFeature::for_host());
511 let (info, translation, inputs) = dummy_compilation_ingredients();
512 let result = compiler.compile_module(&linux32, &info, &[], &translation, inputs, None);
513 match result.unwrap_err() {
514 CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"),
515 error => panic!("Unexpected error: {error:?}"),
516 };
517
518 let win32 = Target::new(triple!("i686-pc-windows-gnu"), CpuFeature::for_host());
520 let (info, translation, inputs) = dummy_compilation_ingredients();
521 let result = compiler.compile_module(&win32, &info, &[], &translation, inputs, None);
522 match result.unwrap_err() {
523 CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), error => panic!("Unexpected error: {error:?}"),
525 };
526 }
527
528 #[test]
529 fn errors_for_unsupported_cpufeatures() {
530 let compiler = SinglepassCompiler::new(Singlepass::default());
531 let mut features =
532 CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1;
533 assert!(
535 compiler.get_cpu_features_used(&features).is_subset(
536 CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1
537 )
538 );
539 assert!(
541 !compiler
542 .get_cpu_features_used(&features)
543 .is_subset(CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1)
544 );
545 features.insert_all(CpuFeature::AVX512DQ | CpuFeature::AVX512F);
547 assert!(
548 compiler.get_cpu_features_used(&features).is_subset(
549 CpuFeature::AVX | CpuFeature::SSE42 | CpuFeature::LZCNT | CpuFeature::BMI1
550 )
551 );
552 }
553}