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