Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ features = [
'memory-protection-keys',
'component-model-async',
'component-model-bytes',
'gc-copying'
'gc-copying',
]

[target.'cfg(windows)'.dev-dependencies]
Expand Down Expand Up @@ -523,6 +523,7 @@ default = [
"debug-builtins",
"component-model",
"component-model-async",
"compile-time-builtins",
"threads",
"gc",
"gc-copying",
Expand Down Expand Up @@ -589,6 +590,7 @@ component-model = [
"wasmtime-cli-flags/component-model",
"wasmtime-wizer?/component-model",
]
compile-time-builtins = ["component-model", "wasmtime/compile-time-builtins"]
wat = ["dep:wat", "wasmtime/wat"]
cache = ["dep:wasmtime-cache", "wasmtime-cli-flags/cache"]
parallel-compilation = ["wasmtime-cli-flags/parallel-compilation"]
Expand Down
68 changes: 68 additions & 0 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,55 @@ wasmtime_option_group! {
/// corruption in compiled code.
pub metadata_for_gc_heap_corruption: Option<bool>,

/// Expose Wasmtime's unsafe intrinsics to the Wasm being compiled under
/// the given instance import name, which defaults to
/// `unsafe-intrinsics` when the name is omitted.
///
/// The unsafe intrinsics are a set of raw, unchecked load and store
/// operations on the host's address space, along with the address of
/// the store's data. They may only be used with components, not core
/// Wasm modules.
///
/// This is wildly unsafe: the Wasm is given the ability to read and
/// write arbitrary host memory. Only use this with Wasm that you trust
/// as much as you trust the CLI itself.
///
/// When `-C compile-time-builtin` is also given, the intrinsics are
/// exposed only to the compile-time builtins and not to the main Wasm
/// program.
///
/// See the API documentation for
/// `CodeBuilder::expose_unsafe_intrinsics` for more details.
#[serde(default)]
#[serde(deserialize_with = "crate::opt::deserialize_cli_parse_wrapper")]
#[serde(serialize_with = "crate::opt::serialize_cli_parse_wrapper")]
pub unsafe_intrinsics: Option<UnsafeIntrinsicsImport>,

/// Define a compile-time builtin: satisfy the `<name>` instance import
/// of the main component with the component at `<path>` at compile
/// time, rather than with a host-defined import at instantiation time.
///
/// May be specified multiple times, once per builtin. Compile-time
/// builtins may only be used with components, not core Wasm modules,
/// and require `-C unsafe-intrinsics`.
///
/// The `<name>=` prefix may be omitted, in which case `<name>` defaults
/// to the file name of `<path>` without its extension. For example
/// `-C compile-time-builtin=path/to/my-host-api.wat` satisfies the
/// `my-host-api` import. Note that a `<path>` which itself contains an
/// `=` is interpreted as the `<name>=<path>` form; pass the explicit
/// form to disambiguate.
///
/// Compile-time builtins are part of your trusted compute base: they
/// are given access to the unsafe intrinsics described above. Calls
/// into them become direct calls, so pass `-C inlining=y` to let them
/// be inlined into their callers.
///
/// See the API documentation for
/// `CodeBuilder::compile_time_builtin_binary` for more details.
#[serde(skip)]
pub compile_time_builtin: Vec<CompileTimeBuiltin>,

#[prefixed = "cranelift"]
#[serde(default)]
/// Set a cranelift-specific option. Use `wasmtime settings` to see
Expand Down Expand Up @@ -621,6 +670,20 @@ pub struct KeyValuePair {
pub value: String,
}

/// The instance import name under which Wasmtime's unsafe intrinsics are
/// exposed to the Wasm being compiled.
#[derive(Debug, Clone, PartialEq)]
pub struct UnsafeIntrinsicsImport(pub String);

/// A compile-time builtin.
#[derive(Debug, Clone, PartialEq)]
pub struct CompileTimeBuiltin {
/// The instance import name that this satisfies in the main component.
pub name: String,
/// The path to the component that implements this builtin.
pub path: PathBuf,
}

/// Common options for commands that translate WebAssembly modules
#[derive(Clone)]
#[cfg_attr(feature = "clap", derive(clap::Parser))]
Expand Down Expand Up @@ -1419,6 +1482,11 @@ impl CommonOptions {
// arbitrary code-defined caches.
cache: None,
cache_config: None,

// These are configured per-`CodeBuilder`, so they cannot be
// recovered here.
unsafe_intrinsics: None,
compile_time_builtin: Vec::new(),
},
debug: DebugOptions {
address_map: Some(engine.get_generate_address_map()),
Expand Down
61 changes: 59 additions & 2 deletions crates/cli-flags/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! specifying options in a struct-like syntax where all other boilerplate about
//! option parsing is contained exclusively within this module.

use crate::{KeyValuePair, WasiNnGraph};
use crate::{CompileTimeBuiltin, KeyValuePair, UnsafeIntrinsicsImport, WasiNnGraph};
#[cfg(feature = "clap")]
use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
#[cfg(feature = "clap")]
Expand All @@ -14,7 +14,7 @@ use clap::error::{Error, ErrorKind};
use serde::de::{self, Visitor};
use std::fmt;
use std::num::NonZeroU32;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use wasmtime::error::Context;
Expand Down Expand Up @@ -705,6 +705,63 @@ impl WasmtimeOptionValue for KeyValuePair {
}
}

impl WasmtimeOptionValue for UnsafeIntrinsicsImport {
const VAL_HELP: &'static str = "[=name]";
fn parse(val: Option<&str>) -> Result<Self> {
match val {
None => Ok(UnsafeIntrinsicsImport("unsafe-intrinsics".to_string())),
Some("") => bail!("the unsafe intrinsics import name cannot be empty"),
Some(val) => Ok(UnsafeIntrinsicsImport(val.to_string())),
}
}

fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}

impl WasmtimeOptionValue for CompileTimeBuiltin {
const VAL_HELP: &'static str = "=[<name>=]<path>";
fn parse(val: Option<&str>) -> Result<Self> {
let val = String::parse(val)?;

if let Some((name, path)) = val.split_once('=') {
if name.is_empty() {
bail!("the compile-time builtin's name cannot be empty in `{val}`");
}
if path.is_empty() {
bail!("the compile-time builtin's path cannot be empty in `{val}`");
}
return Ok(CompileTimeBuiltin {
name: name.to_string(),
path: PathBuf::from(path),
});
}

// No `<name>` was given: derive it from the file name of `<path>`, with
// its extension removed.
let path = PathBuf::from(&val);
let name = path
.file_stem()
.ok_or_else(|| format_err!("cannot derive a compile-time builtin name from `{val}`"))?
.to_str()
.ok_or_else(|| {
format_err!("compile-time builtin name derived from `{val}` is not valid UTF-8")
})?
.to_string();
if name.is_empty() {
bail!("compile-time builtin name derived from `{val}` is empty");
}
Ok(CompileTimeBuiltin { name, path })
}

fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Always write the canonical `<name>=<path>` form so that this
// round-trips back through `parse` regardless of the file name.
write!(f, "{}={}", self.name, Path::display(&self.path))
}
}

pub trait OptionContainer<T> {
fn push(&mut self, val: T);
fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
Expand Down
34 changes: 34 additions & 0 deletions src/code_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use wasmtime::Result;
use wasmtime_cli_flags::CommonOptions;

/// Apply the `-C unsafe-intrinsics` and `-C compile-time-builtin` options in
/// `common` to `code`.
pub fn configure_code_builder(
common: &CommonOptions,
code: &mut wasmtime::CodeBuilder<'_>,
) -> Result<()> {
if let Some(name) = &common.codegen.unsafe_intrinsics {
// SAFETY: the user opted into this by passing flags that are documented
// as unsafe.
unsafe {
code.expose_unsafe_intrinsics(name.0.clone());
}
}

#[cfg(feature = "compile-time-builtins")]
for builtin in &common.codegen.compile_time_builtin {
// SAFETY: the user opted into this by passing flags that are documented
// as unsafe.
unsafe {
code.compile_time_builtins_binary_or_text_file(builtin.name.clone(), &builtin.path)?;
}
}

#[cfg(not(feature = "compile-time-builtins"))]
wasmtime::ensure!(
common.codegen.compile_time_builtin.is_empty(),
"support for compile-time-builtins disabled at compile time",
);

Ok(())
}
1 change: 1 addition & 0 deletions src/commands/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl CompileCommand {

let mut code = CodeBuilder::new(&engine);
code.wasm_binary_or_text_file(&self.module)?;
crate::code_builder::configure_code_builder(&self.common, &mut code)?;

let output = self.output.take().unwrap_or_else(|| {
let mut output: PathBuf = self.module.file_name().unwrap().into();
Expand Down
1 change: 1 addition & 0 deletions src/commands/hot_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ impl HotBlocksCommand {

let mut code = CodeBuilder::new(&engine);
code.wasm_binary_or_text(wasm_bytes, Some(&self.module))?;
crate::code_builder::configure_code_builder(&self.run.common, &mut code)?;

let serialized = match code.hint() {
#[cfg(feature = "component-model")]
Expand Down
1 change: 1 addition & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ impl RunCommon {
None => {
let mut code = wasmtime::CodeBuilder::new(engine);
code.wasm_binary_or_text(bytes, Some(path))?;
crate::code_builder::configure_code_builder(&self.common, &mut code)?;
match code.hint() {
Some(wasmtime::CodeHint::Component) => {
#[cfg(feature = "component-model")]
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

pub mod commands;

#[cfg(any(feature = "cranelift", feature = "winch"))]
pub(crate) mod code_builder;

#[cfg(any(feature = "run", feature = "wizer"))]
pub(crate) mod common;

Expand Down
Loading
Loading