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