1use std::cmp::Reverse;
5use std::fs::OpenOptions;
6use std::path::{Path, PathBuf};
7
8use crate::EH_FRAME_SECTION_NAME;
9use crate::misc::{CompiledFunctionExt, CompiledKind};
10use crate::object::get_object_for_target;
11use crate::progress::ProgressContext;
12use crate::types::function::Compilation;
13use crate::types::module::CompileModuleInfo;
14use crate::{
15 FunctionBodyData, ModuleTranslationState, WASMER_FUNCTION_OFFSETS_SECTION_NAME,
16 WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME,
17 lib::std::{boxed::Box, sync::Arc},
18 translator::ModuleMiddleware,
19};
20use crossbeam_channel::unbounded;
21use enumset::EnumSet;
22use itertools::Itertools;
23use object::write::{Relocation, StandardSegment, Symbol as ObjSymbol, SymbolSection};
24use object::{
25 RelocationEncoding, RelocationFlags, RelocationKind, SectionFlags, SectionKind, SymbolFlags,
26 SymbolKind, SymbolScope, elf,
27};
28use tempfile::NamedTempFile;
29use wasmer_types::{
30 CompilationProgressCallback, Features, FunctionIndex, LocalFunctionIndex,
31 entity::{EntityRef, PrimaryMap},
32 error::CompileError,
33 target::{CpuFeature, Target, UserCompilerOptimizations},
34};
35use wasmer_types::{FunctionType, SignatureIndex};
36#[cfg(feature = "translator")]
37use wasmparser::{Validator, WasmFeatures};
38
39pub trait CompilerConfig {
41 fn enable_pic(&mut self) {
47 }
50
51 fn enable_verifier(&mut self) {
56 }
59
60 fn enable_perfmap(&mut self) {
62 }
65
66 fn enable_non_volatile_memops(&mut self) {}
69
70 fn enable_experimental_unaligned_memory_accesses(&mut self) {}
75
76 fn enable_readonly_funcref_table(&mut self) {}
79
80 fn canonicalize_nans(&mut self, _enable: bool) {
85 }
88
89 fn compiler(self: Box<Self>) -> Box<dyn Compiler>;
91
92 fn default_features_for_target(&self, target: &Target) -> Features {
94 self.supported_features_for_target(target)
95 }
96
97 fn supported_features_for_target(&self, _target: &Target) -> Features {
99 Features::default()
100 }
101
102 fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>);
104}
105
106impl<T> From<T> for Box<dyn CompilerConfig + 'static>
107where
108 T: CompilerConfig + 'static,
109{
110 fn from(other: T) -> Self {
111 Box::new(other)
112 }
113}
114
115pub trait Compiler: Send + std::fmt::Debug {
117 fn name(&self) -> &str;
121
122 fn deterministic_id(&self) -> String;
125
126 fn with_opts(
134 &mut self,
135 suggested_compiler_opts: &UserCompilerOptimizations,
136 ) -> Result<(), CompileError> {
137 _ = suggested_compiler_opts;
138 Ok(())
139 }
140
141 #[cfg(feature = "translator")]
145 fn validate_module(&self, features: &Features, data: &[u8]) -> Result<(), CompileError> {
146 let mut wasm_features = WasmFeatures::empty();
147 wasm_features.set(WasmFeatures::BULK_MEMORY, features.bulk_memory);
148 wasm_features.set(WasmFeatures::THREADS, features.threads);
149 wasm_features.set(WasmFeatures::REFERENCE_TYPES, features.reference_types);
150 wasm_features.set(WasmFeatures::MULTI_VALUE, features.multi_value);
151 wasm_features.set(WasmFeatures::SIMD, features.simd);
152 wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
153 wasm_features.set(WasmFeatures::MULTI_MEMORY, features.multi_memory);
154 wasm_features.set(WasmFeatures::MEMORY64, features.memory64);
155 wasm_features.set(WasmFeatures::EXCEPTIONS, features.exceptions);
156 wasm_features.set(WasmFeatures::EXTENDED_CONST, features.extended_const);
157 wasm_features.set(WasmFeatures::RELAXED_SIMD, features.relaxed_simd);
158 wasm_features.set(WasmFeatures::WIDE_ARITHMETIC, features.wide_arithmetic);
159 wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
160 wasm_features.set(WasmFeatures::MUTABLE_GLOBAL, true);
161 wasm_features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true);
162 wasm_features.set(WasmFeatures::FLOATS, true);
163 wasm_features.set(WasmFeatures::SIGN_EXTENSION, true);
164 wasm_features.set(WasmFeatures::GC_TYPES, true);
165
166 let mut validator = Validator::new_with_features(wasm_features);
167 validator
168 .validate_all(data)
169 .map_err(|e| CompileError::Validate(format!("{e}")))?;
170 Ok(())
171 }
172
173 fn compile_module(
177 &self,
178 target: &Target,
179 module: &CompileModuleInfo,
180 compile_info_blob: &[u8],
181 module_translation: &ModuleTranslationState,
182 function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
184 progress_callback: Option<&CompilationProgressCallback>,
185 ) -> Result<Compilation, CompileError>;
186
187 fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>];
189
190 fn enable_readonly_funcref_table(&self) -> bool {
192 false
193 }
194
195 fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
197 *cpu_features
198 }
199
200 fn get_perfmap_enabled(&self) -> bool {
202 false
203 }
204}
205
206pub struct FunctionBucket<'a> {
208 functions: Vec<(LocalFunctionIndex, &'a FunctionBodyData<'a>)>,
209 pub size: usize,
211}
212
213impl<'a> FunctionBucket<'a> {
214 pub fn new() -> Self {
216 Self {
217 functions: Vec::new(),
218 size: 0,
219 }
220 }
221}
222
223pub fn build_function_buckets<'a>(
225 function_body_inputs: &'a PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
226 bucket_threshold_size: u64,
227) -> Vec<FunctionBucket<'a>> {
228 let mut function_bodies = function_body_inputs
229 .iter()
230 .sorted_by_key(|(id, body)| Reverse((body.data.len(), id.as_u32())))
231 .collect_vec();
232
233 let mut buckets = Vec::new();
234
235 while !function_bodies.is_empty() {
236 let mut next_function_body = Vec::with_capacity(function_bodies.len());
237 let mut bucket = FunctionBucket::new();
238
239 for (fn_index, fn_body) in function_bodies.into_iter() {
240 if bucket.size + fn_body.data.len() <= bucket_threshold_size as usize
241 || bucket.size == 0
243 {
244 bucket.size += fn_body.data.len();
245 bucket.functions.push((fn_index, fn_body));
246 } else {
247 next_function_body.push((fn_index, fn_body));
248 }
249 }
250
251 function_bodies = next_function_body;
252 buckets.push(bucket);
253 }
254
255 buckets
256}
257
258pub trait CompiledFunction {}
260
261pub trait FuncTranslator {}
263
264#[allow(clippy::too_many_arguments)]
266pub fn translate_function_buckets<'a, C, T, F, G>(
267 pool: &rayon::ThreadPool,
268 func_translator_builder: F,
269 translate_fn: G,
270 progress: Option<ProgressContext>,
271 buckets: &[FunctionBucket<'a>],
272) -> Result<Vec<C>, CompileError>
273where
274 T: FuncTranslator,
275 C: CompiledFunction + Send + Sync,
276 F: Fn() -> T + Send + Sync + Copy,
277 G: Fn(&mut T, &LocalFunctionIndex, &FunctionBodyData) -> Result<C, CompileError>
278 + Send
279 + Sync
280 + Copy,
281{
282 let progress = progress.as_ref();
283
284 let functions = pool.install(|| {
285 let (bucket_tx, bucket_rx) = unbounded::<&FunctionBucket<'a>>();
286 for bucket in buckets {
287 bucket_tx.send(bucket).map_err(|e| {
288 CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
289 })?;
290 }
291 drop(bucket_tx);
292
293 let (result_tx, result_rx) =
294 unbounded::<Result<Vec<(LocalFunctionIndex, C)>, CompileError>>();
295
296 pool.scope(|s| {
297 let worker_count = pool.current_num_threads().max(1);
298 for _ in 0..worker_count {
299 let bucket_rx = bucket_rx.clone();
300 let result_tx = result_tx.clone();
301 s.spawn(move |_| {
302 let mut func_translator = func_translator_builder();
303
304 while let Ok(bucket) = bucket_rx.recv() {
305 let bucket_result = (|| {
306 let mut translated_functions = Vec::new();
307 for (i, input) in bucket.functions.iter() {
308 let translated = translate_fn(&mut func_translator, i, input)?;
309 if let Some(progress) = progress {
310 progress.notify_steps(input.data.len() as u64)?;
311 }
312 translated_functions.push((*i, translated));
313 }
314 Ok(translated_functions)
315 })();
316
317 if result_tx.send(bucket_result).is_err() {
318 break;
319 }
320 }
321 });
322 }
323 });
324
325 drop(result_tx);
326 let mut functions = Vec::with_capacity(buckets.iter().map(|b| b.functions.len()).sum());
327 for _ in 0..buckets.len() {
328 match result_rx.recv().map_err(|e| {
329 CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
330 })? {
331 Ok(bucket_functions) => functions.extend(bucket_functions),
332 Err(err) => return Err(err),
333 }
334 }
335 Ok(functions)
336 })?;
337
338 Ok(functions
339 .into_iter()
340 .sorted_by_key(|x| x.0)
341 .map(|(_, body)| body)
342 .collect_vec())
343}
344
345pub const WASM_LARGE_FUNCTION_THRESHOLD: u64 = 100_000;
347
348pub const WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE: u64 = 1_000;
350
351pub struct CompiledObjects<'a> {
355 pub object_files: &'a [PathBuf],
357 pub import_trampoline_object_files: &'a [PathBuf],
359 pub trampoline_object_files: &'a [PathBuf],
361 pub dynamic_trampoline_object_files: &'a [PathBuf],
363}
364
365fn emit_wasmer_meta_object(
366 target: &Target,
367 compile_info_blob: &[u8],
368 build_directory: &Path,
369 compiled_objects: &CompiledObjects<'_>,
370) -> Result<PathBuf, String> {
371 let meta_object_path = build_directory.to_path_buf().join("__wasmer_meta.o");
372 let mut meta_object = OpenOptions::new()
373 .write(true)
374 .create(true)
375 .truncate(true)
376 .open(&meta_object_path)
377 .map_err(|e| {
378 format!(
379 "failed to create Wasmer meta object file {}: {e}",
380 meta_object_path.display()
381 )
382 })?;
383
384 let mut obj = get_object_for_target(target.triple())
385 .map_err(|e| format!("failed to create Wasmer meta object file: {e}"))?;
386
387 let section_id = obj.add_section(
388 obj.segment_name(StandardSegment::Data).to_vec(),
389 crate::WASMER_MODULE_INFO_SECTION_NAME.to_vec(),
390 SectionKind::Other,
391 );
392 obj.append_section_data(section_id, compile_info_blob, 8);
393 obj.section_mut(section_id).flags = SectionFlags::Elf {
394 sh_flags: u64::from(elf::SHF_GNU_RETAIN),
395 };
396
397 let section_id = obj.add_section(
399 obj.segment_name(StandardSegment::Debug).to_vec(),
400 EH_FRAME_SECTION_NAME.to_vec(),
401 SectionKind::Debug,
402 );
403 obj.append_section_data(section_id, &0u64.to_ne_bytes(), 4);
404
405 let section_id = obj.add_section(
407 obj.segment_name(StandardSegment::Data).to_vec(),
408 WASMER_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
409 SectionKind::Other,
410 );
411 obj.section_mut(section_id).flags = SectionFlags::Elf {
412 sh_flags: u64::from(elf::SHF_GNU_RETAIN),
413 };
414 let pointer_size = target
415 .triple()
416 .pointer_width()
417 .map_err(|_| "unknown pointer width".to_string())?
418 .bytes() as u64;
419 let pointer_bits = (pointer_size * 8) as u8;
420 let zero_pointer = vec![0; pointer_size as usize];
421
422 let function_offset_names = (0..compiled_objects.object_files.len())
423 .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).linkage_name())
424 .chain(
425 (0..compiled_objects.trampoline_object_files.len()).map(|i| {
426 CompiledKind::FunctionCallTrampoline(
427 SignatureIndex::new(i),
428 FunctionType::new([], []),
430 )
431 .linkage_name()
432 }),
433 )
434 .chain(
435 (0..compiled_objects.dynamic_trampoline_object_files.len()).map(|i| {
436 CompiledKind::DynamicFunctionTrampoline(
437 FunctionIndex::new(i),
438 FunctionType::new([], []),
440 )
441 .linkage_name()
442 }),
443 );
444 for function_name in function_offset_names {
445 let offset = obj.append_section_data(section_id, &zero_pointer, pointer_size);
446 let symbol_id = obj.add_symbol(ObjSymbol {
447 name: function_name.to_owned().into(),
448 value: 0,
449 size: 0,
450 kind: SymbolKind::Text,
451 scope: SymbolScope::Unknown,
452 weak: false,
453 section: SymbolSection::Undefined,
454 flags: SymbolFlags::None,
455 });
456 obj.add_relocation(
457 section_id,
458 Relocation {
459 offset,
460 flags: RelocationFlags::Generic {
461 kind: RelocationKind::Absolute,
462 encoding: RelocationEncoding::Generic,
463 size: pointer_bits,
464 },
465 symbol: symbol_id,
466 addend: 0,
467 },
468 )
469 .map_err(|e| {
470 format!("failed to add function offset relocation for {function_name}: {e}")
471 })?;
472 }
473
474 let trap_fn_offsets_section_id = obj.add_section(
475 obj.segment_name(StandardSegment::Data).to_vec(),
476 WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
477 SectionKind::Other,
478 );
479 obj.section_mut(trap_fn_offsets_section_id).flags = SectionFlags::Elf {
480 sh_flags: u64::from(elf::SHF_GNU_RETAIN),
481 };
482 for traps_name in (0..compiled_objects.object_files.len())
483 .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).traps_name())
484 {
485 let offset =
486 obj.append_section_data(trap_fn_offsets_section_id, &zero_pointer, pointer_size);
487 let symbol_id = obj.add_symbol(ObjSymbol {
488 name: traps_name.as_bytes().into(),
489 value: 0,
490 size: 0,
491 kind: SymbolKind::Data,
492 scope: SymbolScope::Linkage,
493 weak: true,
494 section: SymbolSection::Undefined,
495 flags: SymbolFlags::None,
496 });
497 obj.add_relocation(
498 trap_fn_offsets_section_id,
499 Relocation {
500 offset,
501 flags: RelocationFlags::Generic {
502 kind: RelocationKind::Absolute,
503 encoding: RelocationEncoding::Generic,
504 size: pointer_bits,
505 },
506 symbol: symbol_id,
507 addend: 0,
508 },
509 )
510 .map_err(|e| {
511 format!("failed to add function trap offset relocation for {traps_name}: {e}")
512 })?;
513 }
514
515 obj.write_stream(&mut meta_object).map_err(|e| {
517 format!(
518 "failed to write Wasmer meta object file {}: {e}",
519 meta_object_path.display(),
520 )
521 })?;
522
523 Ok(meta_object_path)
524}
525
526pub fn emit_metadata_and_link(
528 target: &Target,
529 compile_info_blob: &[u8],
530 build_directory: &Path,
531 module_file: NamedTempFile,
532 compiled_objects: &CompiledObjects<'_>,
533 mut debug_dir: Option<PathBuf>,
534 module_hash: Option<String>,
535) -> Result<NamedTempFile, CompileError> {
536 let meta_object_path =
537 emit_wasmer_meta_object(target, compile_info_blob, build_directory, compiled_objects)
538 .map_err(CompileError::Codegen)?;
539
540 let mut link_args = vec![
541 "ld".to_string(),
542 "-Bsymbolic".to_string(),
544 "-shared".to_string(),
545 "-z".to_string(),
546 "now".to_string(),
547 "-z".to_string(),
548 "relro".to_string(),
549 "-o".to_string(),
550 module_file.path().display().to_string(),
551 ];
552
553 link_args.extend(
554 compiled_objects
555 .object_files
556 .iter()
557 .chain(compiled_objects.import_trampoline_object_files.iter())
558 .chain(compiled_objects.trampoline_object_files.iter())
559 .chain(compiled_objects.dynamic_trampoline_object_files.iter())
560 .map(|path| path.display().to_string()),
561 );
562 link_args.push(meta_object_path.display().to_string());
566
567 let mut wild_args = wasmer_wild::Args::new(|| link_args.iter().map(String::as_str))
568 .map_err(|e| CompileError::Codegen(format!("failed to initialize Wild linker: {e:?}")))?;
569 wild_args
570 .parse(|| link_args.iter().map(String::as_str))
571 .map_err(|e| CompileError::Codegen(format!("failed to parse Wild linker args: {e:?}")))?;
572 let thread_pool = wasmer_wild::args::ThreadPool::new();
573 let linker = wasmer_wild::Linker::new();
574 linker
575 .run(&wild_args, &thread_pool)
576 .map_err(|e| CompileError::Codegen(format!("Wild linker failed: {e:?}")))?;
577
578 let path_buf = module_file.path().to_path_buf();
579 let (_, path) = module_file.into_parts();
580 let new_file = std::fs::File::open(&path_buf).map_err(|e| {
581 CompileError::Codegen(format!("cannot reopen final file after Wild linker: {e:?}"))
582 })?;
583 let module_file = NamedTempFile::from_parts(new_file, path);
584
585 if let Some(debug_dir) = debug_dir.as_mut() {
588 if let Some(ref hash) = module_hash {
589 debug_dir.push(hash);
590 }
591 std::fs::create_dir_all(&debug_dir).ok();
592 debug_dir.push("wasmer-image.so");
593 let _ = std::fs::copy(module_file.path(), &debug_dir);
594 }
595
596 Ok(module_file)
597}