1use std::cmp::Reverse;
5use std::collections::HashMap;
6use std::fs::File;
7use std::path::{Path, PathBuf};
8use std::sync::Mutex;
9
10use crate::EH_FRAME_SECTION_NAME;
11use crate::misc::{CompiledFunctionExt, CompiledKind};
12use crate::object::get_object_for_target;
13use crate::progress::ProgressContext;
14use crate::types::function::Compilation;
15use crate::types::module::CompileModuleInfo;
16use crate::{
17 FunctionBodyData, ModuleTranslationState, WASMER_FUNCTION_OFFSETS_SECTION_NAME,
18 WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME, translator::ModuleMiddleware,
19};
20use crossbeam_channel::unbounded;
21use enumset::EnumSet;
22use itertools::Itertools;
23use libwild::{
24 Args, FileSystem, FileType, InputFileData, Linker, OutputFileData, OutputOptions, error,
25};
26use object::write::{Relocation, StandardSegment, Symbol as ObjSymbol, SymbolSection};
27use object::{
28 RelocationEncoding, RelocationFlags, RelocationKind, SectionFlags, SectionKind, SymbolFlags,
29 SymbolKind, SymbolScope, elf,
30};
31use std::{boxed::Box, sync::Arc};
32use wasmer_types::{
33 CompilationProgressCallback, Features, FunctionIndex, LocalFunctionIndex,
34 entity::{EntityRef, PrimaryMap},
35 error::CompileError,
36 target::{CpuFeature, Target, UserCompilerOptimizations},
37};
38use wasmer_types::{FunctionType, SignatureIndex};
39#[cfg(feature = "translator")]
40use wasmparser::{Validator, WasmFeatures};
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
44pub enum Debugger {
45 #[strum(serialize = "GDB")]
47 Gdb,
48 #[strum(serialize = "LLDB")]
50 Lldb,
51}
52
53pub trait CompilerConfig {
55 fn experimental_artifact(&mut self, _enable: bool) {}
57
58 fn enable_pic(&mut self) {
64 }
67
68 fn enable_verifier(&mut self) {
73 }
76
77 fn enable_perfmap(&mut self) {
79 }
81
82 fn enable_debugger(&mut self, _debugger: Debugger) {
84 }
86
87 fn enable_non_volatile_memops(&mut self) {}
90
91 fn enable_experimental_unaligned_memory_accesses(&mut self) {}
96
97 fn enable_readonly_funcref_table(&mut self) {}
100
101 fn canonicalize_nans(&mut self, _enable: bool) {
106 }
109
110 fn compiler(self: Box<Self>) -> Box<dyn Compiler>;
112
113 fn default_features_for_target(&self, target: &Target) -> Features {
115 self.supported_features_for_target(target)
116 }
117
118 fn supported_features_for_target(&self, _target: &Target) -> Features {
120 Features::default()
121 }
122
123 fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>);
125}
126
127impl<T> From<T> for Box<dyn CompilerConfig + 'static>
128where
129 T: CompilerConfig + 'static,
130{
131 fn from(other: T) -> Self {
132 Box::new(other)
133 }
134}
135
136pub trait Compiler: Send + std::fmt::Debug {
138 fn name(&self) -> &str;
142
143 fn deterministic_id(&self) -> String;
146
147 fn with_opts(
155 &mut self,
156 suggested_compiler_opts: &UserCompilerOptimizations,
157 ) -> Result<(), CompileError> {
158 _ = suggested_compiler_opts;
159 Ok(())
160 }
161
162 #[cfg(feature = "translator")]
166 fn validate_module(&self, features: &Features, data: &[u8]) -> Result<(), CompileError> {
167 let mut wasm_features = WasmFeatures::empty();
168 wasm_features.set(WasmFeatures::BULK_MEMORY, features.bulk_memory);
169 wasm_features.set(WasmFeatures::THREADS, features.threads);
170 wasm_features.set(WasmFeatures::REFERENCE_TYPES, features.reference_types);
171 wasm_features.set(WasmFeatures::MULTI_VALUE, features.multi_value);
172 wasm_features.set(WasmFeatures::SIMD, features.simd);
173 wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
174 wasm_features.set(WasmFeatures::MULTI_MEMORY, features.multi_memory);
175 wasm_features.set(WasmFeatures::MEMORY64, features.memory64);
176 wasm_features.set(WasmFeatures::EXCEPTIONS, features.exceptions);
177 wasm_features.set(WasmFeatures::EXTENDED_CONST, features.extended_const);
178 wasm_features.set(WasmFeatures::RELAXED_SIMD, features.relaxed_simd);
179 wasm_features.set(WasmFeatures::WIDE_ARITHMETIC, features.wide_arithmetic);
180 wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
181 wasm_features.set(WasmFeatures::MUTABLE_GLOBAL, true);
182 wasm_features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true);
183 wasm_features.set(WasmFeatures::FLOATS, true);
184 wasm_features.set(WasmFeatures::SIGN_EXTENSION, true);
185 wasm_features.set(WasmFeatures::GC_TYPES, true);
186
187 let mut validator = Validator::new_with_features(wasm_features);
188 validator
189 .validate_all(data)
190 .map_err(|e| CompileError::Validate(format!("{e}")))?;
191 Ok(())
192 }
193
194 fn compile_module(
198 &self,
199 target: &Target,
200 module: &CompileModuleInfo,
201 compile_info_blob: &[u8],
202 module_translation: &ModuleTranslationState,
203 function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
205 progress_callback: Option<&CompilationProgressCallback>,
206 ) -> Result<Compilation, CompileError>;
207
208 fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>];
210
211 fn enable_readonly_funcref_table(&self) -> bool {
213 false
214 }
215
216 fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
218 *cpu_features
219 }
220
221 fn get_perfmap_enabled(&self) -> bool {
223 false
224 }
225
226 fn get_debugger(&self) -> Option<Debugger> {
228 None
229 }
230}
231
232pub struct FunctionBucket<'a> {
234 functions: Vec<(LocalFunctionIndex, &'a FunctionBodyData<'a>)>,
235 pub size: usize,
237}
238
239impl<'a> FunctionBucket<'a> {
240 pub fn new() -> Self {
242 Self {
243 functions: Vec::new(),
244 size: 0,
245 }
246 }
247}
248
249pub fn build_function_buckets<'a>(
251 function_body_inputs: &'a PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
252 bucket_threshold_size: u64,
253) -> Vec<FunctionBucket<'a>> {
254 let mut function_bodies = function_body_inputs
255 .iter()
256 .sorted_by_key(|(id, body)| Reverse((body.data.len(), id.as_u32())))
257 .collect_vec();
258
259 let mut buckets = Vec::new();
260
261 while !function_bodies.is_empty() {
262 let mut next_function_body = Vec::with_capacity(function_bodies.len());
263 let mut bucket = FunctionBucket::new();
264
265 for (fn_index, fn_body) in function_bodies.into_iter() {
266 if bucket.size + fn_body.data.len() <= bucket_threshold_size as usize
267 || bucket.size == 0
269 {
270 bucket.size += fn_body.data.len();
271 bucket.functions.push((fn_index, fn_body));
272 } else {
273 next_function_body.push((fn_index, fn_body));
274 }
275 }
276
277 function_bodies = next_function_body;
278 buckets.push(bucket);
279 }
280
281 buckets
282}
283
284pub trait CompiledFunction {}
286
287pub trait FuncTranslator {}
289
290#[allow(clippy::too_many_arguments)]
292pub fn translate_function_buckets<'a, C, T, F, G>(
293 pool: &rayon::ThreadPool,
294 func_translator_builder: F,
295 translate_fn: G,
296 progress: Option<ProgressContext>,
297 buckets: &[FunctionBucket<'a>],
298) -> Result<Vec<C>, CompileError>
299where
300 T: FuncTranslator,
301 C: CompiledFunction + Send + Sync,
302 F: Fn() -> T + Send + Sync + Copy,
303 G: Fn(&mut T, &LocalFunctionIndex, &FunctionBodyData) -> Result<C, CompileError>
304 + Send
305 + Sync
306 + Copy,
307{
308 let progress = progress.as_ref();
309
310 let functions = pool.install(|| {
311 let (bucket_tx, bucket_rx) = unbounded::<&FunctionBucket<'a>>();
312 for bucket in buckets {
313 bucket_tx.send(bucket).map_err(|e| {
314 CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
315 })?;
316 }
317 drop(bucket_tx);
318
319 let (result_tx, result_rx) =
320 unbounded::<Result<Vec<(LocalFunctionIndex, C)>, CompileError>>();
321
322 pool.scope(|s| {
323 let worker_count = pool.current_num_threads().max(1);
324 for _ in 0..worker_count {
325 let bucket_rx = bucket_rx.clone();
326 let result_tx = result_tx.clone();
327 s.spawn(move |_| {
328 let mut func_translator = func_translator_builder();
329
330 while let Ok(bucket) = bucket_rx.recv() {
331 let bucket_result = (|| {
332 let mut translated_functions = Vec::new();
333 for (i, input) in bucket.functions.iter() {
334 let translated = translate_fn(&mut func_translator, i, input)?;
335 if let Some(progress) = progress {
336 progress.notify_steps(input.data.len() as u64)?;
337 }
338 translated_functions.push((*i, translated));
339 }
340 Ok(translated_functions)
341 })();
342
343 if result_tx.send(bucket_result).is_err() {
344 break;
345 }
346 }
347 });
348 }
349 });
350
351 drop(result_tx);
352 let mut functions = Vec::with_capacity(buckets.iter().map(|b| b.functions.len()).sum());
353 for _ in 0..buckets.len() {
354 match result_rx.recv().map_err(|e| {
355 CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
356 })? {
357 Ok(bucket_functions) => functions.extend(bucket_functions),
358 Err(err) => return Err(err),
359 }
360 }
361 Ok(functions)
362 })?;
363
364 Ok(functions
365 .into_iter()
366 .sorted_by_key(|x| x.0)
367 .map(|(_, body)| body)
368 .collect_vec())
369}
370
371pub const WASM_LARGE_FUNCTION_THRESHOLD: u64 = 100_000;
373
374pub const WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE: u64 = 1_000;
376
377pub struct CompiledObjects {
381 pub object_files: Vec<Vec<u8>>,
383 pub import_trampoline_object_files: Vec<Vec<u8>>,
385 pub trampoline_object_files: Vec<Vec<u8>>,
387 pub dynamic_trampoline_object_files: Vec<Vec<u8>>,
389}
390
391fn emit_wasmer_meta_object(
392 target: &Target,
393 compile_info_blob: &[u8],
394 compiled_objects: &CompiledObjects,
395) -> Result<Vec<u8>, String> {
396 let mut obj = get_object_for_target(target.triple())
397 .map_err(|e| format!("failed to create Wasmer meta object: {e}"))?;
398
399 let section_id = obj.add_section(
400 obj.segment_name(StandardSegment::Data).to_vec(),
401 crate::WASMER_MODULE_INFO_SECTION_NAME.to_vec(),
402 SectionKind::Other,
403 );
404 obj.append_section_data(section_id, compile_info_blob, 8);
405 obj.section_mut(section_id).flags = SectionFlags::Elf {
406 sh_type: elf::SHT_PROGBITS,
407 sh_flags: elf::SHF_GNU_RETAIN,
408 };
409
410 let section_id = obj.add_section(
412 obj.segment_name(StandardSegment::Debug).to_vec(),
413 EH_FRAME_SECTION_NAME.to_vec(),
414 SectionKind::Debug,
415 );
416 obj.append_section_data(section_id, &0u64.to_ne_bytes(), 4);
417
418 let section_id = obj.add_section(
420 obj.segment_name(StandardSegment::Data).to_vec(),
421 WASMER_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
422 SectionKind::Other,
423 );
424 obj.section_mut(section_id).flags = SectionFlags::Elf {
425 sh_type: elf::SHT_PROGBITS,
426 sh_flags: elf::SHF_GNU_RETAIN,
427 };
428 let pointer_size = target
429 .triple()
430 .pointer_width()
431 .map_err(|_| "unknown pointer width".to_string())?
432 .bytes() as u64;
433 let pointer_bits = (pointer_size * 8) as u8;
434 let zero_pointer = vec![0; pointer_size as usize];
435
436 let function_offset_names = (0..compiled_objects.object_files.len())
437 .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).linkage_name())
438 .chain(
439 (0..compiled_objects.trampoline_object_files.len()).map(|i| {
440 CompiledKind::FunctionCallTrampoline(
441 SignatureIndex::new(i),
442 FunctionType::new([], []),
444 )
445 .linkage_name()
446 }),
447 )
448 .chain(
449 (0..compiled_objects.dynamic_trampoline_object_files.len()).map(|i| {
450 CompiledKind::DynamicFunctionTrampoline(
451 FunctionIndex::new(i),
452 FunctionType::new([], []),
454 )
455 .linkage_name()
456 }),
457 );
458 for function_name in function_offset_names {
459 let offset = obj.append_section_data(section_id, &zero_pointer, pointer_size);
460 let symbol_id = obj.add_symbol(ObjSymbol {
461 name: function_name.to_owned().into(),
462 value: 0,
463 size: 0,
464 kind: SymbolKind::Text,
465 scope: SymbolScope::Unknown,
466 weak: false,
467 section: SymbolSection::Undefined,
468 flags: SymbolFlags::None,
469 });
470 obj.add_relocation(
471 section_id,
472 Relocation {
473 offset,
474 flags: RelocationFlags::Generic {
475 kind: RelocationKind::Absolute,
476 encoding: RelocationEncoding::Generic,
477 size: pointer_bits,
478 },
479 symbol: symbol_id,
480 addend: 0,
481 },
482 )
483 .map_err(|e| {
484 format!("failed to add function offset relocation for {function_name}: {e}")
485 })?;
486 }
487
488 let trap_fn_offsets_section_id = obj.add_section(
489 obj.segment_name(StandardSegment::Data).to_vec(),
490 WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
491 SectionKind::Other,
492 );
493 obj.section_mut(trap_fn_offsets_section_id).flags = SectionFlags::Elf {
494 sh_type: elf::SHT_PROGBITS,
495 sh_flags: elf::SHF_GNU_RETAIN,
496 };
497 for traps_name in (0..compiled_objects.object_files.len())
498 .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).traps_name())
499 {
500 let offset =
501 obj.append_section_data(trap_fn_offsets_section_id, &zero_pointer, pointer_size);
502 let symbol_id = obj.add_symbol(ObjSymbol {
503 name: traps_name.as_bytes().into(),
504 value: 0,
505 size: 0,
506 kind: SymbolKind::Data,
507 scope: SymbolScope::Linkage,
508 weak: true,
509 section: SymbolSection::Undefined,
510 flags: SymbolFlags::None,
511 });
512 obj.add_relocation(
513 trap_fn_offsets_section_id,
514 Relocation {
515 offset,
516 flags: RelocationFlags::Generic {
517 kind: RelocationKind::Absolute,
518 encoding: RelocationEncoding::Generic,
519 size: pointer_bits,
520 },
521 symbol: symbol_id,
522 addend: 0,
523 },
524 )
525 .map_err(|e| {
526 format!("failed to add function trap offset relocation for {traps_name}: {e}")
527 })?;
528 }
529
530 obj.write()
531 .map_err(|e| format!("failed to serialize Wasmer meta object: {e}"))
532}
533
534#[derive(Clone, Default)]
535struct InMemoryFileSystem {
536 files: Arc<Mutex<HashMap<PathBuf, Arc<Vec<u8>>>>>,
537}
538
539#[derive(Debug)]
540struct InMemoryInput(Arc<Vec<u8>>);
541
542impl InputFileData for InMemoryInput {
543 fn bytes(&self) -> &[u8] {
544 &self.0
545 }
546}
547
548struct InMemoryOutput {
549 path: PathBuf,
550 bytes: Vec<u8>,
551 files: Arc<Mutex<HashMap<PathBuf, Arc<Vec<u8>>>>>,
552}
553
554impl OutputFileData for InMemoryOutput {
555 fn bytes(&self) -> &[u8] {
556 &self.bytes
557 }
558 fn bytes_mut(&mut self) -> &mut [u8] {
559 &mut self.bytes
560 }
561 fn finish(mut self) -> error::Result {
562 self.files
563 .lock()
564 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
565 .insert(self.path, Arc::new(std::mem::take(&mut self.bytes)));
566 Ok(())
567 }
568}
569
570impl FileSystem for InMemoryFileSystem {
571 type Input = InMemoryInput;
572 type Output = InMemoryOutput;
573
574 fn open_input(&self, path: &Path, _: bool) -> error::Result<(Self::Input, Option<Arc<File>>)> {
575 let bytes = self
576 .files
577 .lock()
578 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
579 .get(path)
580 .map(Arc::clone)
581 .ok_or_else(|| error!("No such in-memory file: {}", path.display()))?;
582 Ok((InMemoryInput(bytes), None))
583 }
584
585 fn file_type(&self, path: &Path) -> error::Result<FileType> {
586 self.files
587 .lock()
588 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
589 .contains_key(path)
590 .then_some(FileType::File)
591 .ok_or_else(|| error!("no such in-memory file"))
592 }
593
594 fn canonicalize(&self, path: &Path) -> error::Result<PathBuf> {
595 Ok(path.to_path_buf())
596 }
597 fn remove_file(&self, path: &Path) -> error::Result<()> {
598 self.files
599 .lock()
600 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
601 .remove(path)
602 .map(|_| ())
603 .ok_or_else(|| error!("no such in-memory file"))
604 }
605 fn rename_file(&self, path: &Path, new_path: &Path) -> error::Result<()> {
606 let mut files = self
607 .files
608 .lock()
609 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?;
610 let bytes = files
611 .remove(path)
612 .ok_or_else(|| error!("no such in-memory file"))?;
613 files.insert(new_path.to_path_buf(), bytes);
614 Ok(())
615 }
616 fn create_output(
617 &self,
618 path: Arc<Path>,
619 options: OutputOptions,
620 ) -> error::Result<Self::Output> {
621 let size = usize::try_from(options.size).map_err(|_| error!("output is too large"))?;
622 Ok(InMemoryOutput {
623 path: path.to_path_buf(),
624 bytes: vec![0; size],
625 files: Arc::clone(&self.files),
626 })
627 }
628 fn write_auxiliary(&self, path: &Path, bytes: &[u8]) -> error::Result {
629 self.files
630 .lock()
631 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
632 .insert(path.to_path_buf(), Arc::new(bytes.to_vec()));
633 Ok(())
634 }
635}
636
637const WASMER_IMAGE_FILENAME: &str = "wasmer-image.so";
638const WASMER_META_FILENAME: &str = "__wasmer_meta.o";
639
640pub fn emit_metadata_and_link(
642 pool: &rayon::ThreadPool,
643 target: &Target,
644 compile_info_blob: &[u8],
645 compiled_objects: CompiledObjects,
646 mut debug_dir: Option<PathBuf>,
647 module_hash: Option<String>,
648) -> Result<Vec<u8>, CompileError> {
649 pool.install(|| {
650 let meta_object = emit_wasmer_meta_object(target, compile_info_blob, &compiled_objects)
651 .map_err(CompileError::Codegen)?;
652 let CompiledObjects {
653 object_files,
654 import_trampoline_object_files,
655 trampoline_object_files,
656 dynamic_trampoline_object_files,
657 } = compiled_objects;
658 let fs = InMemoryFileSystem::default();
659 let mut link_args = vec![
660 "ld".to_string(),
661 "-Bsymbolic".to_string(),
663 "-shared".to_string(),
664 "-z".to_string(),
665 "now".to_string(),
666 "-z".to_string(),
667 "relro".to_string(),
668 "-o".to_string(),
669 WASMER_IMAGE_FILENAME.to_string(),
670 ];
671
672 {
673 let mut files = fs
674 .files
675 .lock()
676 .map_err(|e| CompileError::Codegen(format!("cannot lock in-memory FS: {e}")))?;
677 for (index, object) in object_files
678 .into_iter()
679 .chain(import_trampoline_object_files)
680 .chain(trampoline_object_files)
681 .chain(dynamic_trampoline_object_files)
682 .enumerate()
683 {
684 let path = PathBuf::from(format!("object-{index}.o"));
685 files.insert(path.clone(), Arc::new(object));
686 link_args.push(path.display().to_string());
687 }
688 files.insert(PathBuf::from(WASMER_META_FILENAME), Arc::new(meta_object));
689 }
690 link_args.push(WASMER_META_FILENAME.to_string());
694
695 let mut wild_args = Args::new(|| link_args.iter().map(String::as_str)).map_err(|e| {
696 CompileError::Codegen(format!("failed to initialize Wild linker: {e:?}"))
697 })?;
698 wild_args
699 .parse(|| link_args.iter().map(String::as_str))
700 .map_err(|e| {
701 CompileError::Codegen(format!("failed to parse Wild linker args: {e:?}"))
702 })?;
703 Linker::with_file_system(fs.clone())
704 .run(&wild_args)
705 .map_err(|e| CompileError::Codegen(format!("Wild linker failed: {e:?}")))?;
706
707 let image = fs
708 .files
709 .lock()
710 .map_err(|e| CompileError::Codegen(format!("cannot lock in-memory FS: {e}")))?
711 .remove(Path::new(WASMER_IMAGE_FILENAME))
712 .ok_or_else(|| CompileError::Codegen("Wild linker did not produce an output".into()))?;
713 let image = Arc::try_unwrap(image).map_err(|_| {
714 CompileError::Codegen("Wild linker retained a reference to the output buffer".into())
715 })?;
716
717 if let Some(debug_dir) = debug_dir.as_mut() {
720 if let Some(ref hash) = module_hash {
721 debug_dir.push(hash);
722 }
723 std::fs::create_dir_all(&debug_dir).ok();
724 debug_dir.push(WASMER_IMAGE_FILENAME);
725 let _ = std::fs::write(debug_dir, &image);
726 }
727 Ok(image)
728 })
729}