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