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
53#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
56#[allow(missing_docs)]
57pub enum DeterministicIdComponent {
58 #[strum(serialize = "llvm")]
59 Llvm,
60 #[strum(serialize = "cranelift")]
61 Cranelift,
62 #[strum(serialize = "singlepass")]
63 Singlepass,
64 #[strum(serialize = "opt0")]
65 OptNone,
66 #[strum(serialize = "optl")]
67 OptLess,
68 #[strum(serialize = "optd")]
69 OptDefault,
70 #[strum(serialize = "opta")]
71 OptAggressive,
72 #[strum(serialize = "opts")]
73 OptSpeed,
74 #[strum(serialize = "optsz")]
75 OptSpeedAndSize,
76 #[strum(serialize = "nan_canon")]
77 NanCanonicalization,
78 #[strum(serialize = "non_vol_mem")]
79 NonVolatileMemops,
80 #[strum(serialize = "pic")]
81 Pic,
82 #[strum(serialize = "ro_ftable")]
83 ReadonlyFuncrefTable,
84 #[strum(serialize = "unaligned_mem")]
85 ExperimentalUnalignedMemoryAccesses,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
90pub enum ArtifactFormat {
91 #[strum(serialize = "rkyv")]
93 Rkyv,
94 #[strum(serialize = "native")]
96 Native,
97}
98
99pub trait CompilerConfig {
101 fn experimental_artifact(&mut self, _enable: bool) {}
103
104 fn enable_pic(&mut self) {
110 }
113
114 fn enable_verifier(&mut self) {
119 }
122
123 fn enable_perfmap(&mut self) {
125 }
127
128 fn enable_debugger(&mut self, _debugger: Debugger) {
130 }
132
133 fn enable_non_volatile_memops(&mut self) {}
136
137 fn enable_experimental_unaligned_memory_accesses(&mut self) {}
142
143 fn enable_readonly_funcref_table(&mut self) {}
146
147 fn canonicalize_nans(&mut self, _enable: bool) {
152 }
155
156 fn compiler(self: Box<Self>) -> Box<dyn Compiler>;
158
159 fn default_features_for_target(&self, target: &Target) -> Features {
161 self.supported_features_for_target(target)
162 }
163
164 fn supported_features_for_target(&self, _target: &Target) -> Features {
166 Features::default()
167 }
168
169 fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>);
171}
172
173impl<T> From<T> for Box<dyn CompilerConfig + 'static>
174where
175 T: CompilerConfig + 'static,
176{
177 fn from(other: T) -> Self {
178 Box::new(other)
179 }
180}
181
182pub trait Compiler: Send + std::fmt::Debug {
184 fn name(&self) -> &str;
188
189 fn deterministic_id(&self) -> String;
192
193 fn artifact_format(&self) -> String {
195 ArtifactFormat::Rkyv.to_string()
196 }
197
198 fn with_opts(
206 &mut self,
207 suggested_compiler_opts: &UserCompilerOptimizations,
208 ) -> Result<(), CompileError> {
209 _ = suggested_compiler_opts;
210 Ok(())
211 }
212
213 #[cfg(feature = "translator")]
217 fn validate_module(&self, features: &Features, data: &[u8]) -> Result<(), CompileError> {
218 let mut wasm_features = WasmFeatures::empty();
219 wasm_features.set(WasmFeatures::BULK_MEMORY, features.bulk_memory);
220 wasm_features.set(WasmFeatures::THREADS, features.threads);
221 wasm_features.set(WasmFeatures::REFERENCE_TYPES, features.reference_types);
222 wasm_features.set(WasmFeatures::MULTI_VALUE, features.multi_value);
223 wasm_features.set(WasmFeatures::SIMD, features.simd);
224 wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
225 wasm_features.set(WasmFeatures::MULTI_MEMORY, features.multi_memory);
226 wasm_features.set(WasmFeatures::MEMORY64, features.memory64);
227 wasm_features.set(WasmFeatures::EXCEPTIONS, features.exceptions);
228 wasm_features.set(WasmFeatures::EXTENDED_CONST, features.extended_const);
229 wasm_features.set(WasmFeatures::RELAXED_SIMD, features.relaxed_simd);
230 wasm_features.set(WasmFeatures::WIDE_ARITHMETIC, features.wide_arithmetic);
231 wasm_features.set(WasmFeatures::TAIL_CALL, features.tail_call);
232 wasm_features.set(WasmFeatures::MUTABLE_GLOBAL, true);
233 wasm_features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true);
234 wasm_features.set(WasmFeatures::FLOATS, true);
235 wasm_features.set(WasmFeatures::SIGN_EXTENSION, true);
236 wasm_features.set(WasmFeatures::GC_TYPES, true);
237
238 let mut validator = Validator::new_with_features(wasm_features);
239 validator
240 .validate_all(data)
241 .map_err(|e| CompileError::Validate(format!("{e}")))?;
242 Ok(())
243 }
244
245 fn compile_module(
249 &self,
250 target: &Target,
251 module: &CompileModuleInfo,
252 compile_info_blob: &[u8],
253 module_translation: &ModuleTranslationState,
254 function_body_inputs: PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
256 progress_callback: Option<&CompilationProgressCallback>,
257 ) -> Result<Compilation, CompileError>;
258
259 fn get_middlewares(&self) -> &[Arc<dyn ModuleMiddleware>];
261
262 fn enable_readonly_funcref_table(&self) -> bool {
264 false
265 }
266
267 fn get_cpu_features_used(&self, cpu_features: &EnumSet<CpuFeature>) -> EnumSet<CpuFeature> {
269 *cpu_features
270 }
271
272 fn get_perfmap_enabled(&self) -> bool {
274 false
275 }
276
277 fn get_debugger(&self) -> Option<Debugger> {
279 None
280 }
281}
282
283pub struct FunctionBucket<'a> {
285 functions: Vec<(LocalFunctionIndex, &'a FunctionBodyData<'a>)>,
286 pub size: usize,
288}
289
290impl<'a> FunctionBucket<'a> {
291 pub fn new() -> Self {
293 Self {
294 functions: Vec::new(),
295 size: 0,
296 }
297 }
298}
299
300pub fn build_function_buckets<'a>(
302 function_body_inputs: &'a PrimaryMap<LocalFunctionIndex, FunctionBodyData<'a>>,
303 bucket_threshold_size: u64,
304) -> Vec<FunctionBucket<'a>> {
305 let mut function_bodies = function_body_inputs
306 .iter()
307 .sorted_by_key(|(id, body)| Reverse((body.data.len(), id.as_u32())))
308 .collect_vec();
309
310 let mut buckets = Vec::new();
311
312 while !function_bodies.is_empty() {
313 let mut next_function_body = Vec::with_capacity(function_bodies.len());
314 let mut bucket = FunctionBucket::new();
315
316 for (fn_index, fn_body) in function_bodies.into_iter() {
317 if bucket.size + fn_body.data.len() <= bucket_threshold_size as usize
318 || bucket.size == 0
320 {
321 bucket.size += fn_body.data.len();
322 bucket.functions.push((fn_index, fn_body));
323 } else {
324 next_function_body.push((fn_index, fn_body));
325 }
326 }
327
328 function_bodies = next_function_body;
329 buckets.push(bucket);
330 }
331
332 buckets
333}
334
335pub trait CompiledFunction {}
337
338pub trait FuncTranslator {}
340
341#[allow(clippy::too_many_arguments)]
343pub fn translate_function_buckets<'a, C, T, F, G>(
344 pool: &rayon::ThreadPool,
345 func_translator_builder: F,
346 translate_fn: G,
347 progress: Option<ProgressContext>,
348 buckets: &[FunctionBucket<'a>],
349) -> Result<Vec<C>, CompileError>
350where
351 T: FuncTranslator,
352 C: CompiledFunction + Send + Sync,
353 F: Fn() -> T + Send + Sync + Copy,
354 G: Fn(&mut T, &LocalFunctionIndex, &FunctionBodyData) -> Result<C, CompileError>
355 + Send
356 + Sync
357 + Copy,
358{
359 let progress = progress.as_ref();
360
361 let functions = pool.install(|| {
362 let (bucket_tx, bucket_rx) = unbounded::<&FunctionBucket<'a>>();
363 for bucket in buckets {
364 bucket_tx.send(bucket).map_err(|e| {
365 CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
366 })?;
367 }
368 drop(bucket_tx);
369
370 let (result_tx, result_rx) =
371 unbounded::<Result<Vec<(LocalFunctionIndex, C)>, CompileError>>();
372
373 pool.scope(|s| {
374 let worker_count = pool.current_num_threads().max(1);
375 for _ in 0..worker_count {
376 let bucket_rx = bucket_rx.clone();
377 let result_tx = result_tx.clone();
378 s.spawn(move |_| {
379 let mut func_translator = func_translator_builder();
380
381 while let Ok(bucket) = bucket_rx.recv() {
382 let bucket_result = (|| {
383 let mut translated_functions = Vec::new();
384 for (i, input) in bucket.functions.iter() {
385 let translated = translate_fn(&mut func_translator, i, input)?;
386 if let Some(progress) = progress {
387 progress.notify_steps(input.data.len() as u64)?;
388 }
389 translated_functions.push((*i, translated));
390 }
391 Ok(translated_functions)
392 })();
393
394 if result_tx.send(bucket_result).is_err() {
395 break;
396 }
397 }
398 });
399 }
400 });
401
402 drop(result_tx);
403 let mut functions = Vec::with_capacity(buckets.iter().map(|b| b.functions.len()).sum());
404 for _ in 0..buckets.len() {
405 match result_rx.recv().map_err(|e| {
406 CompileError::Resource(format!("cannot allocate crossbeam channel item: {e}"))
407 })? {
408 Ok(bucket_functions) => functions.extend(bucket_functions),
409 Err(err) => return Err(err),
410 }
411 }
412 Ok(functions)
413 })?;
414
415 Ok(functions
416 .into_iter()
417 .sorted_by_key(|x| x.0)
418 .map(|(_, body)| body)
419 .collect_vec())
420}
421
422pub const WASM_LARGE_FUNCTION_THRESHOLD: u64 = 100_000;
424
425pub const WASM_TRAMPOLINE_ESTIMATED_BODY_SIZE: u64 = 1_000;
427
428pub struct CompiledObjects {
432 pub object_files: Vec<Vec<u8>>,
434 pub import_trampoline_object_files: Vec<Vec<u8>>,
436 pub trampoline_object_files: Vec<Vec<u8>>,
438 pub dynamic_trampoline_object_files: Vec<Vec<u8>>,
440}
441
442fn emit_wasmer_meta_object(
443 target: &Target,
444 compile_info_blob: &[u8],
445 compiled_objects: &CompiledObjects,
446) -> Result<Vec<u8>, String> {
447 let mut obj = get_object_for_target(target.triple())
448 .map_err(|e| format!("failed to create Wasmer meta object: {e}"))?;
449
450 let section_id = obj.add_section(
451 obj.segment_name(StandardSegment::Data).to_vec(),
452 crate::WASMER_MODULE_INFO_SECTION_NAME.to_vec(),
453 SectionKind::Other,
454 );
455 obj.append_section_data(section_id, compile_info_blob, 8);
456 obj.section_mut(section_id).flags = SectionFlags::Elf {
457 sh_type: elf::SHT_PROGBITS,
458 sh_flags: elf::SHF_GNU_RETAIN,
459 };
460
461 let section_id = obj.add_section(
463 obj.segment_name(StandardSegment::Debug).to_vec(),
464 EH_FRAME_SECTION_NAME.to_vec(),
465 SectionKind::Debug,
466 );
467 obj.append_section_data(section_id, &0u64.to_ne_bytes(), 4);
468
469 let section_id = obj.add_section(
471 obj.segment_name(StandardSegment::Data).to_vec(),
472 WASMER_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
473 SectionKind::Other,
474 );
475 obj.section_mut(section_id).flags = SectionFlags::Elf {
476 sh_type: elf::SHT_PROGBITS,
477 sh_flags: elf::SHF_GNU_RETAIN,
478 };
479 let pointer_size = target
480 .triple()
481 .pointer_width()
482 .map_err(|_| "unknown pointer width".to_string())?
483 .bytes() as u64;
484 let pointer_bits = (pointer_size * 8) as u8;
485 let zero_pointer = vec![0; pointer_size as usize];
486
487 let function_offset_names = (0..compiled_objects.object_files.len())
488 .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).linkage_name())
489 .chain(
490 (0..compiled_objects.trampoline_object_files.len()).map(|i| {
491 CompiledKind::FunctionCallTrampoline(
492 SignatureIndex::new(i),
493 FunctionType::new([], []),
495 )
496 .linkage_name()
497 }),
498 )
499 .chain(
500 (0..compiled_objects.dynamic_trampoline_object_files.len()).map(|i| {
501 CompiledKind::DynamicFunctionTrampoline(
502 FunctionIndex::new(i),
503 FunctionType::new([], []),
505 )
506 .linkage_name()
507 }),
508 );
509 for function_name in function_offset_names {
510 let offset = obj.append_section_data(section_id, &zero_pointer, pointer_size);
511 let symbol_id = obj.add_symbol(ObjSymbol {
512 name: function_name.to_owned().into(),
513 value: 0,
514 size: 0,
515 kind: SymbolKind::Text,
516 scope: SymbolScope::Unknown,
517 weak: false,
518 section: SymbolSection::Undefined,
519 flags: SymbolFlags::None,
520 });
521 obj.add_relocation(
522 section_id,
523 Relocation {
524 offset,
525 flags: RelocationFlags::Generic {
526 kind: RelocationKind::Absolute,
527 encoding: RelocationEncoding::Generic,
528 size: pointer_bits,
529 },
530 symbol: symbol_id,
531 addend: 0,
532 },
533 )
534 .map_err(|e| {
535 format!("failed to add function offset relocation for {function_name}: {e}")
536 })?;
537 }
538
539 let trap_fn_offsets_section_id = obj.add_section(
540 obj.segment_name(StandardSegment::Data).to_vec(),
541 WASMER_TRAP_FUNCTION_OFFSETS_SECTION_NAME.to_vec(),
542 SectionKind::Other,
543 );
544 obj.section_mut(trap_fn_offsets_section_id).flags = SectionFlags::Elf {
545 sh_type: elf::SHT_PROGBITS,
546 sh_flags: elf::SHF_GNU_RETAIN,
547 };
548 for traps_name in (0..compiled_objects.object_files.len())
549 .map(|i| CompiledKind::Local(LocalFunctionIndex::new(i), String::new()).traps_name())
550 {
551 let offset =
552 obj.append_section_data(trap_fn_offsets_section_id, &zero_pointer, pointer_size);
553 let symbol_id = obj.add_symbol(ObjSymbol {
554 name: traps_name.as_bytes().into(),
555 value: 0,
556 size: 0,
557 kind: SymbolKind::Data,
558 scope: SymbolScope::Linkage,
559 weak: true,
560 section: SymbolSection::Undefined,
561 flags: SymbolFlags::None,
562 });
563 obj.add_relocation(
564 trap_fn_offsets_section_id,
565 Relocation {
566 offset,
567 flags: RelocationFlags::Generic {
568 kind: RelocationKind::Absolute,
569 encoding: RelocationEncoding::Generic,
570 size: pointer_bits,
571 },
572 symbol: symbol_id,
573 addend: 0,
574 },
575 )
576 .map_err(|e| {
577 format!("failed to add function trap offset relocation for {traps_name}: {e}")
578 })?;
579 }
580
581 obj.write()
582 .map_err(|e| format!("failed to serialize Wasmer meta object: {e}"))
583}
584
585#[derive(Clone, Default)]
586struct InMemoryFileSystem {
587 files: Arc<Mutex<HashMap<PathBuf, Arc<Vec<u8>>>>>,
588}
589
590#[derive(Debug)]
591struct InMemoryInput(Arc<Vec<u8>>);
592
593impl InputFileData for InMemoryInput {
594 fn bytes(&self) -> &[u8] {
595 &self.0
596 }
597}
598
599struct InMemoryOutput {
600 path: PathBuf,
601 bytes: Vec<u8>,
602 files: Arc<Mutex<HashMap<PathBuf, Arc<Vec<u8>>>>>,
603}
604
605impl OutputFileData for InMemoryOutput {
606 fn bytes(&self) -> &[u8] {
607 &self.bytes
608 }
609 fn bytes_mut(&mut self) -> &mut [u8] {
610 &mut self.bytes
611 }
612 fn finish(mut self) -> error::Result {
613 self.files
614 .lock()
615 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
616 .insert(self.path, Arc::new(std::mem::take(&mut self.bytes)));
617 Ok(())
618 }
619}
620
621impl FileSystem for InMemoryFileSystem {
622 type Input = InMemoryInput;
623 type Output = InMemoryOutput;
624
625 fn open_input(&self, path: &Path, _: bool) -> error::Result<(Self::Input, Option<Arc<File>>)> {
626 let bytes = self
627 .files
628 .lock()
629 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
630 .get(path)
631 .map(Arc::clone)
632 .ok_or_else(|| error!("No such in-memory file: {}", path.display()))?;
633 Ok((InMemoryInput(bytes), None))
634 }
635
636 fn file_type(&self, path: &Path) -> error::Result<FileType> {
637 self.files
638 .lock()
639 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
640 .contains_key(path)
641 .then_some(FileType::File)
642 .ok_or_else(|| error!("no such in-memory file"))
643 }
644
645 fn canonicalize(&self, path: &Path) -> error::Result<PathBuf> {
646 Ok(path.to_path_buf())
647 }
648 fn remove_file(&self, path: &Path) -> error::Result<()> {
649 self.files
650 .lock()
651 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
652 .remove(path)
653 .map(|_| ())
654 .ok_or_else(|| error!("no such in-memory file"))
655 }
656 fn rename_file(&self, path: &Path, new_path: &Path) -> error::Result<()> {
657 let mut files = self
658 .files
659 .lock()
660 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?;
661 let bytes = files
662 .remove(path)
663 .ok_or_else(|| error!("no such in-memory file"))?;
664 files.insert(new_path.to_path_buf(), bytes);
665 Ok(())
666 }
667 fn create_output(
668 &self,
669 path: Arc<Path>,
670 options: OutputOptions,
671 ) -> error::Result<Self::Output> {
672 let size = usize::try_from(options.size).map_err(|_| error!("output is too large"))?;
673 Ok(InMemoryOutput {
674 path: path.to_path_buf(),
675 bytes: vec![0; size],
676 files: Arc::clone(&self.files),
677 })
678 }
679 fn write_auxiliary(&self, path: &Path, bytes: &[u8]) -> error::Result {
680 self.files
681 .lock()
682 .map_err(|e| format!("cannot lock in-memory FS: {e}"))?
683 .insert(path.to_path_buf(), Arc::new(bytes.to_vec()));
684 Ok(())
685 }
686}
687
688const WASMER_IMAGE_FILENAME: &str = "wasmer-image.so";
689const WASMER_META_FILENAME: &str = "__wasmer_meta.o";
690
691pub fn emit_metadata_and_link(
693 pool: &rayon::ThreadPool,
694 target: &Target,
695 compile_info_blob: &[u8],
696 compiled_objects: CompiledObjects,
697 mut debug_dir: Option<PathBuf>,
698 module_hash: Option<String>,
699) -> Result<Vec<u8>, CompileError> {
700 pool.install(|| {
701 let meta_object = emit_wasmer_meta_object(target, compile_info_blob, &compiled_objects)
702 .map_err(CompileError::Codegen)?;
703 let CompiledObjects {
704 object_files,
705 import_trampoline_object_files,
706 trampoline_object_files,
707 dynamic_trampoline_object_files,
708 } = compiled_objects;
709 let fs = InMemoryFileSystem::default();
710 let mut link_args = vec![
711 "ld".to_string(),
712 "-Bsymbolic".to_string(),
714 "-shared".to_string(),
715 "-z".to_string(),
716 "now".to_string(),
717 "-z".to_string(),
718 "relro".to_string(),
719 "-o".to_string(),
720 WASMER_IMAGE_FILENAME.to_string(),
721 ];
722
723 {
724 let mut files = fs
725 .files
726 .lock()
727 .map_err(|e| CompileError::Codegen(format!("cannot lock in-memory FS: {e}")))?;
728 for (index, object) in object_files
729 .into_iter()
730 .chain(import_trampoline_object_files)
731 .chain(trampoline_object_files)
732 .chain(dynamic_trampoline_object_files)
733 .enumerate()
734 {
735 let path = PathBuf::from(format!("object-{index}.o"));
736 files.insert(path.clone(), Arc::new(object));
737 link_args.push(path.display().to_string());
738 }
739 files.insert(PathBuf::from(WASMER_META_FILENAME), Arc::new(meta_object));
740 }
741 link_args.push(WASMER_META_FILENAME.to_string());
745
746 let mut wild_args = Args::new(|| link_args.iter().map(String::as_str)).map_err(|e| {
747 CompileError::Codegen(format!("failed to initialize Wild linker: {e:?}"))
748 })?;
749 wild_args
750 .parse(|| link_args.iter().map(String::as_str))
751 .map_err(|e| {
752 CompileError::Codegen(format!("failed to parse Wild linker args: {e:?}"))
753 })?;
754 Linker::with_file_system(fs.clone())
755 .run(&wild_args)
756 .map_err(|e| CompileError::Codegen(format!("Wild linker failed: {e:?}")))?;
757
758 let image = fs
759 .files
760 .lock()
761 .map_err(|e| CompileError::Codegen(format!("cannot lock in-memory FS: {e}")))?
762 .remove(Path::new(WASMER_IMAGE_FILENAME))
763 .ok_or_else(|| CompileError::Codegen("Wild linker did not produce an output".into()))?;
764 let image = Arc::try_unwrap(image).map_err(|_| {
765 CompileError::Codegen("Wild linker retained a reference to the output buffer".into())
766 })?;
767
768 if let Some(debug_dir) = debug_dir.as_mut() {
771 if let Some(ref hash) = module_hash {
772 debug_dir.push(hash);
773 }
774 std::fs::create_dir_all(&debug_dir).ok();
775 debug_dir.push(WASMER_IMAGE_FILENAME);
776 let _ = std::fs::write(debug_dir, &image);
777 }
778 Ok(image)
779 })
780}