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    /// - "arm64-apple-darwin"
44    /// - "x86_64-windows-gnu"
45    #[clap(long = "target")]
46    target_triple: Option<Triple>,
47
48    #[clap(long, short = 'm', number_of_values = 1)]
49    cpu_features: Vec<CpuFeature>,
50}
51
52impl GenCHeader {
53    /// Runs logic for the `gen-c-header` subcommand
54    pub fn execute(&self) -> Result<(), Error> {
55        let file: Bytes = std::fs::read(&self.path)
56            .with_context(|| format!("Unable to read \"{}\"", self.path.display()))?
57            .into();
58        let prefix = match self.prefix.as_deref() {
59            Some(s) => s.to_string(),
60            None => crate::commands::PrefixMapCompilation::hash_for_bytes(&file),
61        };
62
63        let atom = match from_bytes(file.clone()) {
64            Ok(webc) => self.get_atom(&webc)?,
65            Err(WasmerPackageError::ContainerError(ContainerError::Detect(
66                DetectError::InvalidMagic { .. },
67            ))) => {
68                // we've probably got a WebAssembly file
69                file.into()
70            }
71            Err(other) => {
72                return Err(Error::new(other).context("Unable to parse the webc file"));
73            }
74        };
75
76        let target_triple = self.target_triple.clone().unwrap_or_else(Triple::host);
77        let target = crate::commands::create_exe::utils::target_triple_to_target(
78            &target_triple,
79            &self.cpu_features,
80        );
81        let engine =
82            RuntimeOptions::default().get_sys_compiler_engine_for_target(target.clone())?;
83        if !engine.is_sys() {
84            anyhow::bail!(
85                "Cannot use this engine to generate c-headers! Please, use one of `cranelift`, `llvm` or `singlepass`."
86            );
87        }
88        let engine = engine.into_sys();
89
90        let engine_inner = engine.inner();
91        let compiler = engine_inner.compiler()?;
92        let features = engine_inner.features();
93        let tunables = engine.tunables();
94        let (metadata, _, _) = Artifact::metadata(
95            compiler,
96            &atom,
97            Some(prefix.as_str()),
98            &target,
99            tunables,
100            features,
101        )
102        .map_err(|e| anyhow::anyhow!("could not generate metadata: {e}"))?;
103
104        let metadata_builder = ObjectMetadataBuilder::new(&metadata, &target_triple)
105            .map_err(|e| anyhow::anyhow!("failed to create relocs builder: {e}"))?;
106        let metadata_length = metadata_builder.placeholder_data().len();
107
108        let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
109            &prefix,
110            &metadata.compile_info.module,
111            &ModuleMetadataSymbolRegistry {
112                prefix: prefix.clone(),
113            },
114            metadata_length,
115        );
116
117        let output = crate::common::normalize_path(&self.output.display().to_string());
118
119        std::fs::write(&output, header_file_src)
120            .map_err(|e| anyhow::anyhow!("{e}"))
121            .with_context(|| anyhow::anyhow!("{output}"))?;
122
123        Ok(())
124    }
125
126    fn get_atom(&self, pirita: &Container) -> Result<SharedBytes, Error> {
127        let atoms = pirita.atoms();
128        let atom_names: Vec<_> = atoms.keys().map(|s| s.as_str()).collect();
129
130        match *atom_names.as_slice() {
131            [] => Err(Error::msg("The file doesn't contain any atoms")),
132            [name] => Ok(atoms[name].clone()),
133            [..] => match &self.atom {
134                Some(name) => atoms
135                    .get(name)
136                    .cloned()
137                    .with_context(|| format!("The file doesn't contain a \"{name}\" atom"))
138                    .with_context(|| {
139                        format!("-> note: available atoms are: {}", atom_names.join(", "))
140                    }),
141                None => {
142                    let err = Error::msg("file has multiple atoms, please specify which atom to generate the header file for")
143                            .context(format!("-> note: available atoms are: {}", atom_names.join(", ")));
144                    Err(err)
145                }
146            },
147        }
148    }
149}