Skip to content

Commit

Permalink
Refactor use of CodeBuilder on the CLI (#9125)
Browse files Browse the repository at this point in the history
* Refactor use of `CodeBuilder` on the CLI

This commit refactors `wasmtime run` and `wasmtime compile` to
unconditionally use `CodeBuilder` internally. This will in theory help
out in the future if more debug-related options are added to
`CodeBuilder` for example. This refactoring required some changes to
`CodeBuilder` to be able to support a query about whether the internal
bytes were a component or a module. The text format is now converted to
binary immediately when supplied rather than during the compilation
phase. This in turn required some API changes to make the selection of
supporting the text format a compile-time choice of method rather than a
runtime value.

* Fix compile

* Fix no-cranelift build of CLI
  • Loading branch information
alexcrichton authored Aug 14, 2024
1 parent e0a907a commit f673cde
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 126 deletions.
2 changes: 1 addition & 1 deletion crates/wasmtime/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use wasmtime_environ::{
};

mod code_builder;
pub use self::code_builder::{CodeBuilder, HashedEngineCompileEnv};
pub use self::code_builder::{CodeBuilder, CodeHint, HashedEngineCompileEnv};

#[cfg(feature = "runtime")]
mod runtime;
Expand Down
187 changes: 115 additions & 72 deletions crates/wasmtime/src/compile/code_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,36 @@ use std::path::Path;
/// [`Module::deserialize`](crate::Module::deserialize) instead.
///
/// A [`CodeBuilder`] requires a source of WebAssembly bytes to be configured
/// before calling [`compile_module_serialized`] or [`compile_module`]. This can be
/// provided with either the [`wasm`] or [`wasm_file`] method. Note that only
/// a single source of bytes can be provided.
/// before calling [`compile_module_serialized`] or [`compile_module`]. This can
/// be provided with either the [`wasm_binary`] or [`wasm_binary_file`] method.
/// Note that only a single source of bytes can be provided.
///
/// # WebAssembly Text Format
///
/// This builder supports the WebAssembly Text Format (`*.wat` files).
/// WebAssembly text files are automatically converted to a WebAssembly binary
/// and then the binary is compiled. This requires the `wat` feature of the
/// `wasmtime` crate to be enabled, and the feature is enabled by default.
///
/// If the text format is not desired then the [`CodeBuilder::wat`] method
/// can be used to disable this conversion.
/// This builder supports the WebAssembly Text Format (`*.wat` files) through
/// the [`CodeBuilder::wasm_binary_or_text`] and
/// [`CodeBuilder::wasm_binary_or_text_file`] methods. These methods
/// automatically convert WebAssembly text files to binary. Note though that
/// this behavior is disabled if the `wat` crate feature is not enabled.
///
/// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized
/// [`compile_module`]: CodeBuilder::compile_module
/// [`wasm`]: CodeBuilder::wasm
/// [`wasm_file`]: CodeBuilder::wasm_file
/// [`wasm_binary`]: CodeBuilder::wasm_binary
/// [`wasm_binary_file`]: CodeBuilder::wasm_binary_file
pub struct CodeBuilder<'a> {
pub(super) engine: &'a Engine,
wasm: Option<Cow<'a, [u8]>>,
wasm_path: Option<Cow<'a, Path>>,
dwarf_package: Option<Cow<'a, [u8]>>,
dwarf_package_path: Option<Cow<'a, Path>>,
wat: bool,
}

/// Return value of [`CodeBuilder::hint`]
pub enum CodeHint {
/// Hint that the code being compiled is a module.
Module,
/// Hint that the code being compiled is a component.
Component,
}

impl<'a> CodeBuilder<'a> {
Expand All @@ -55,15 +60,14 @@ impl<'a> CodeBuilder<'a> {
wasm_path: None,
dwarf_package: None,
dwarf_package_path: None,
wat: cfg!(feature = "wat"),
}
}

/// Configures the WebAssembly binary or text that is being compiled.
/// Configures the WebAssembly binary that is being compiled.
///
/// The `wasm_bytes` parameter is either a binary WebAssembly file or a
/// WebAssembly module in its text format. This will be stored within the
/// [`CodeBuilder`] for processing later when compilation is finalized.
/// The `wasm_bytes` parameter must be a binary WebAssembly file.
/// This will be stored within the [`CodeBuilder`] for processing later when
/// compilation is finalized.
///
/// The optional `wasm_path` parameter is the path to the `wasm_bytes` on
/// disk, if any. This may be used for diagnostics and other
Expand All @@ -72,11 +76,15 @@ impl<'a> CodeBuilder<'a> {
///
/// # Errors
///
/// If wasm bytes have already been configured via a call to this method or
/// [`CodeBuilder::wasm_file`] then an error will be returned.
pub fn wasm(&mut self, wasm_bytes: &'a [u8], wasm_path: Option<&'a Path>) -> Result<&mut Self> {
/// This method will return an error if WebAssembly bytes have already been
/// configured.
pub fn wasm_binary(
&mut self,
wasm_bytes: impl Into<Cow<'a, [u8]>>,
wasm_path: Option<&'a Path>,
) -> Result<&mut Self> {
if self.wasm.is_some() {
bail!("cannot call `wasm` or `wasm_file` twice");
bail!("cannot configure wasm bytes twice");
}
self.wasm = Some(wasm_bytes.into());
self.wasm_path = wasm_path.map(|p| p.into());
Expand All @@ -88,73 +96,89 @@ impl<'a> CodeBuilder<'a> {
Ok(self)
}

/// Configures whether the WebAssembly text format is supported in this
/// builder.
/// Equivalent of [`CodeBuilder::wasm_binary`] that also accepts the
/// WebAssembly text format.
///
/// This support is enabled by default if the `wat` crate feature is also
/// enabled.
/// This method will configure the WebAssembly binary to be compiled. The
/// input `wasm_bytes` may either be the wasm text format or the binary
/// format. If the `wat` crate feature is enabled, which is enabled by
/// default, then the text format will automatically be converted to the
/// binary format.
///
/// # Errors
///
/// If this feature is explicitly enabled here via this method and the
/// `wat` crate feature is disabled then an error will be returned.
pub fn wat(&mut self, enable: bool) -> Result<&mut Self> {
if !cfg!(feature = "wat") && enable {
bail!("support for `wat` was disabled at compile time");
}
self.wat = enable;
Ok(self)
/// This method will return an error if WebAssembly bytes have already been
/// configured. This method will also return an error if `wasm_bytes` is the
/// wasm text format and the text syntax is not valid.
pub fn wasm_binary_or_text(
&mut self,
wasm_bytes: &'a [u8],
wasm_path: Option<&'a Path>,
) -> Result<&mut Self> {
#[cfg(feature = "wat")]
let wasm_bytes = wat::parse_bytes(wasm_bytes).map_err(|mut e| {
if let Some(path) = wasm_path {
e.set_path(path);
}
e
})?;
self.wasm_binary(wasm_bytes, wasm_path)
}

/// Reads the `file` specified for the WebAssembly bytes that are going to
/// be compiled.
///
/// This method will read `file` from the filesystem and interpret it
/// either as a WebAssembly binary or as a WebAssembly text file. The
/// contents are inspected to do this, the file extension is not consulted.
/// as a WebAssembly binary.
///
/// A DWARF package file will be probed using the root of `file` and with a
/// `.dwp` extension. If found, it will be loaded and DWARF fusion
/// `.dwp` extension. If found, it will be loaded and DWARF fusion
/// performed.
///
/// # Errors
///
/// If wasm bytes have already been configured via a call to this method or
/// [`CodeBuilder::wasm`] then an error will be returned.
/// This method will return an error if WebAssembly bytes have already been
/// configured.
///
/// If `file` can't be read or an error happens reading it then that will
/// also be returned.
///
/// If DWARF fusion is performed and the DWARF packaged file cannot be read
/// then an error will be returned.
pub fn wasm_file(&mut self, file: &'a Path) -> Result<&mut Self> {
if self.wasm.is_some() {
bail!("cannot call `wasm` or `wasm_file` twice");
}
pub fn wasm_binary_file(&mut self, file: &'a Path) -> Result<&mut Self> {
let wasm = std::fs::read(file)
.with_context(|| format!("failed to read input file: {}", file.display()))?;
self.wasm = Some(wasm.into());
self.wasm_path = Some(file.into());
self.dwarf_package_from_wasm_path()?;

Ok(self)
self.wasm_binary(wasm, Some(file))
}

pub(super) fn wasm_binary(&self) -> Result<Cow<'_, [u8]>> {
let wasm = self
.wasm
.as_ref()
.ok_or_else(|| anyhow!("no wasm bytes have been configured"))?;
if self.wat {
#[cfg(feature = "wat")]
return wat::parse_bytes(wasm).map_err(|mut e| {
if let Some(path) = &self.wasm_path {
e.set_path(path);
}
e.into()
});
/// Equivalent of [`CodeBuilder::wasm_binary_file`] that also accepts the
/// WebAssembly text format.
///
/// This method is will read the file at `path` and interpret the contents
/// to determine if it's the wasm text format or binary format. The file
/// extension of `file` is not consulted. The text format is automatically
/// converted to the binary format if the crate feature `wat` is active.
///
/// # Errors
///
/// In addition to the errors returned by [`CodeBuilder::wasm_binary_file`]
/// this may also fail if the text format is read and the syntax is invalid.
pub fn wasm_binary_or_text_file(&mut self, file: &'a Path) -> Result<&mut Self> {
#[cfg(feature = "wat")]
{
let wasm = wat::parse_file(file)?;
self.wasm_binary(wasm, Some(file))
}
#[cfg(not(feature = "wat"))]
{
self.wasm_binary_file(file)
}
Ok((&wasm[..]).into())
}

pub(super) fn get_wasm(&self) -> Result<&[u8]> {
self.wasm
.as_deref()
.ok_or_else(|| anyhow!("no wasm bytes have been configured"))
}

/// Explicitly specify DWARF `.dwp` path.
Expand All @@ -163,7 +187,7 @@ impl<'a> CodeBuilder<'a> {
///
/// This method will return an error if the `.dwp` file has already been set
/// through [`CodeBuilder::dwarf_package`] or auto-detection in
/// [`CodeBuilder::wasm_file`].
/// [`CodeBuilder::wasm_binary_file`].
///
/// This method will also return an error if `file` cannot be read.
pub fn dwarf_package_file(&mut self, file: &Path) -> Result<&mut Self> {
Expand All @@ -189,8 +213,8 @@ impl<'a> CodeBuilder<'a> {
}

/// Gets the DWARF package.
pub(super) fn dwarf_package_binary(&self) -> Option<&[u8]> {
return self.dwarf_package.as_deref();
pub(super) fn get_dwarf_package(&self) -> Option<&[u8]> {
self.dwarf_package.as_deref()
}

/// Set the DWARF package binary.
Expand All @@ -202,7 +226,7 @@ impl<'a> CodeBuilder<'a> {
/// # Errors
///
/// Returns an error if the `*.dwp` file is already set via auto-probing in
/// [`CodeBuilder::wasm_file`] or explicitly via
/// [`CodeBuilder::wasm_binary_file`] or explicitly via
/// [`CodeBuilder::dwarf_package_file`].
pub fn dwarf_package(&mut self, dwp_bytes: &'a [u8]) -> Result<&mut Self> {
if self.dwarf_package.is_some() {
Expand All @@ -212,11 +236,30 @@ impl<'a> CodeBuilder<'a> {
Ok(self)
}

/// Returns a hint, if possible, of what the provided bytes are.
///
/// This method can be use to detect what the previously supplied bytes to
/// methods such as [`CodeBuilder::wasm_binary_or_text`] are. This will
/// return whether a module or a component was found in the provided bytes.
///
/// This method will return `None` if wasm bytes have not been configured
/// or if the provided bytes don't look like either a component or a
/// module.
pub fn hint(&self) -> Option<CodeHint> {
let wasm = self.wasm.as_ref()?;
if wasmparser::Parser::is_component(wasm) {
Some(CodeHint::Component)
} else if wasmparser::Parser::is_core_wasm(wasm) {
Some(CodeHint::Module)
} else {
None
}
}

/// Finishes this compilation and produces a serialized list of bytes.
///
/// This method requires that either [`CodeBuilder::wasm`] or
/// [`CodeBuilder::wasm_file`] was invoked prior to indicate what is
/// being compiled.
/// This method requires that either [`CodeBuilder::wasm_binary`] or
/// related methods were invoked prior to indicate what is being compiled.
///
/// This method will block the current thread until compilation has
/// finished, and when done the serialized artifact will be returned.
Expand All @@ -229,8 +272,8 @@ impl<'a> CodeBuilder<'a> {
/// This can fail if the input wasm module was not valid or if another
/// compilation-related error is encountered.
pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
let wasm = self.wasm_binary()?;
let dwarf_package = self.dwarf_package_binary();
let wasm = self.get_wasm()?;
let dwarf_package = self.get_dwarf_package();
let (v, _) = super::build_artifacts(self.engine, &wasm, dwarf_package.as_deref())?;
Ok(v)
}
Expand All @@ -240,7 +283,7 @@ impl<'a> CodeBuilder<'a> {
/// instead of a module.
#[cfg(feature = "component-model")]
pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
let bytes = self.wasm_binary()?;
let bytes = self.get_wasm()?;
let (v, _) = super::build_component_artifacts(self.engine, &bytes, None)?;
Ok(v)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/wasmtime/src/compile/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ impl<'a> CodeBuilder<'a> {
&self,
build_artifacts: fn(&Engine, &[u8], Option<&[u8]>) -> Result<(MmapVecWrapper, Option<T>)>,
) -> Result<(Arc<CodeMemory>, Option<T>)> {
let wasm = self.wasm_binary()?;
let dwarf_package = self.dwarf_package_binary();
let wasm = self.get_wasm()?;
let dwarf_package = self.get_dwarf_package();

self.engine
.check_compatible_with_native_host()
Expand Down
4 changes: 2 additions & 2 deletions crates/wasmtime/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ impl Engine {
/// [text]: https://webassembly.github.io/spec/core/text/index.html
pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
crate::CodeBuilder::new(self)
.wasm(bytes, None)?
.wasm_binary_or_text(bytes, None)?
.compile_module_serialized()
}

Expand All @@ -503,7 +503,7 @@ impl Engine {
#[cfg(feature = "component-model")]
pub fn precompile_component(&self, bytes: &[u8]) -> Result<Vec<u8>> {
crate::CodeBuilder::new(self)
.wasm(bytes, None)?
.wasm_binary_or_text(bytes, None)?
.compile_component_serialized()
}

Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ pub use runtime::*;
#[cfg(any(feature = "cranelift", feature = "winch"))]
mod compile;
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub use compile::CodeBuilder;
pub use compile::{CodeBuilder, CodeHint};

mod config;
mod engine;
Expand Down
7 changes: 3 additions & 4 deletions crates/wasmtime/src/runtime/component/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl Component {
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Component> {
crate::CodeBuilder::new(engine)
.wasm(bytes.as_ref(), None)?
.wasm_binary_or_text(bytes.as_ref(), None)?
.compile_component()
}

Expand All @@ -173,7 +173,7 @@ impl Component {
#[cfg(all(feature = "std", any(feature = "cranelift", feature = "winch")))]
pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Component> {
crate::CodeBuilder::new(engine)
.wasm_file(file.as_ref())?
.wasm_binary_or_text_file(file.as_ref())?
.compile_component()
}

Expand All @@ -189,8 +189,7 @@ impl Component {
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Component> {
crate::CodeBuilder::new(engine)
.wasm(binary, None)?
.wat(false)?
.wasm_binary(binary, None)?
.compile_component()
}

Expand Down
Loading

0 comments on commit f673cde

Please sign in to comment.