wasmer_cli/commands/
gen_c_header.rs

1use std::path::PathBuf;
2
3use anyhow::{Context, Error};
4use bytes::Bytes;
5use clap::Parser;
6use wasmer_compiler::{
7    Artifact, object::ObjectMetadataBuilder, types::symbols::ModuleMetadataSymbolRegistry,
8};
9use wasmer_package::{package::WasmerPackageError, utils::from_bytes};
10use wasmer_types::target::{CpuFeature, Triple};
11use webc::{Container, ContainerError, DetectError, compat::SharedBytes};
12
13use crate::backend::RuntimeOptions;
14
15#[derive(Debug, Parser)]
16/// The options for the `wasmer gen-c-header` subcommand
17pub struct GenCHeader {
18    /// Input file
19    #[clap(name = "FILE")]
20    path: PathBuf,
21
22    /// Prefix hash (default: SHA256 of input .wasm file)
23    #[clap(long)]
24    prefix: Option<String>,
25
26    /// For pirita files: optional atom name to compile
27    #[clap(long)]
28    atom: Option<String>,
29
30    /// Output file
31    #[clap(name = "OUTPUT PATH", short = 'o')]
32    output: PathBuf,
33
34    /// Compilation Target triple
35    ///
36    /// Accepted target triple values must follow the
37    /// ['target_lexicon'](https://crates.io/crates/target-lexicon) crate format.
38    ///
39    /// The recommended targets we try to support are:
40    ///
41    /// - "x86_64-linux-gnu"
42    /// - "aarch64-linux-gnu"
43    /// - "x86_64-apple-darwin"
44    /// - "arm64-apple-darwin"
45    /// - "x86_64-windows-gnu"
46    #[clap(long = "target")]
47    target_triple: Option<Triple>,
48
49    #[clap(long, short = 'm', number_of_values = 1)]
50    cpu_features: Vec<CpuFeature>,
51}
52
53impl GenCHeader {
54    /// Runs logic for the `gen-c-header` subcommand
55    pub fn execute(&self) -> Result<(), Error> {
56        let file: Bytes = std::fs::read(&self.path)
57            .with_context(|| format!("Unable to read \"{}\"", self.path.display()))?
58            .into();
59        let prefix = match self.prefix.as_deref() {
60            Some(s) => s.to_string(),
61            None => crate::commands::PrefixMapCompilation::hash_for_bytes(&file),
62        };
63
64        let atom = match from_bytes(file.clone()) {
65            Ok(webc) => self.get_atom(&webc)?,
66            Err(WasmerPackageError::ContainerError(ContainerError::Detect(
67                DetectError::InvalidMagic { .. },
68            ))) => {
69                // we've probably got a WebAssembly file
70                file.into()
71            }
72            Err(other) => {
73                return Err(Error::new(other).context("Unable to parse the webc file"));
74            }
75        };
76
77        let target_triple = self.target_triple.clone().unwrap_or_else(Triple::host);
78        let target = crate::commands::create_exe::utils::target_triple_to_target(
79            &target_triple,
80            &self.cpu_features,
81        );
82        let engine =
83            RuntimeOptions::default().get_sys_compiler_engine_for_target(target.clone())?;
84        if !engine.is_sys() {
85            anyhow::bail!(
86                "Cannot use this engine to generate c-headers! Please, use one of `cranelift`, `llvm` or `singlepass`."
87            );
88        }
89        let engine = engine.into_sys();
90
91        let engine_inner = engine.inner();
92        let compiler = engine_inner.compiler()?;
93        let features = engine_inner.features();
94        let tunables = engine.tunables();
95        let (metadata, _, _) = Artifact::metadata(
96            compiler,
97            &atom,
98            Some(prefix.as_str()),
99            &target,
100            tunables,
101            features,
102        )
103        .map_err(|e| anyhow::anyhow!("could not generate metadata: {e}"))?;
104
105        let metadata_builder = ObjectMetadataBuilder::new(&metadata, &target_triple)
106            .map_err(|e| anyhow::anyhow!("failed to create relocs builder: {e}"))?;
107        let metadata_length = metadata_builder.placeholder_data().len();
108
109        let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
110            &prefix,
111            &metadata.compile_info.module,
112            &ModuleMetadataSymbolRegistry {
113                prefix: prefix.clone(),
114            },
115            metadata_length,
116        );
117
118        let output = crate::common::normalize_path(&self.output.display().to_string());
119
120        std::fs::write(&output, header_file_src)
121            .map_err(|e| anyhow::anyhow!("{e}"))
122            .with_context(|| anyhow::anyhow!("{output}"))?;
123
124        Ok(())
125    }
126
127    fn get_atom(&self, pirita: &Container) -> Result<SharedBytes, Error> {
128        let atoms = pirita.atoms();
129        let atom_names: Vec<_> = atoms.keys().map(|s| s.as_str()).collect();
130
131        match *atom_names.as_slice() {
132            [] => Err(Error::msg("The file doesn't contain any atoms")),
133            [name] => Ok(atoms[name].clone()),
134            [..] => match &self.atom {
135                Some(name) => atoms
136                    .get(name)
137                    .cloned()
138                    .with_context(|| format!("The file doesn't contain a \"{name}\" atom"))
139                    .with_context(|| {
140                        format!("-> note: available atoms are: {}", atom_names.join(", "))
141                    }),
142                None => {
143                    let err = Error::msg("file has multiple atoms, please specify which atom to generate the header file for")
144                            .context(format!("-> note: available atoms are: {}", atom_names.join(", ")));
145                    Err(err)
146                }
147            },
148        }
149    }
150}