mirror of
https://github.com/mii443/wasmer.git
synced 2025-08-23 16:59:27 +00:00
feat: enhance WebAssembly feature detection for WebC packages
This commit improves the Wasmer runtime's ability to select the appropriate engine based on WebAssembly features: - Add detection of WebAssembly features from WebC packages - Extract Wasm features from command metadata or binary analysis - Implement engine selection based on detected features - Refactor type imports from wasmer_compiler::types to wasmer_types - Add feature annotation support in package manifests - Improve error messages in WebC package conversion - Update webc dependency from 8.0 to 9.0 - General code quality improvements (using first() instead of get(0), is_empty() instead of len() == 0) This enables more intelligent backend selection when running WebC packages,ensuring the appropriate compiler features are enabled based on module requirements. Signed-off-by: Charalampos Mitrodimas <charalampos@wasmer.io>
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -7148,6 +7148,7 @@ dependencies = [
|
|||||||
"ureq",
|
"ureq",
|
||||||
"url",
|
"url",
|
||||||
"wasmer-config",
|
"wasmer-config",
|
||||||
|
"wasmer-types",
|
||||||
"webc",
|
"webc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -7603,9 +7604,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webc"
|
name = "webc"
|
||||||
version = "8.0.0"
|
version = "9.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3bdee7f8a5c220e497d264a89fca8e29627a702e2cc2fb8846426b8f0d2e2d5"
|
checksum = "38544ae3a351279fa913b4dee9c548f0aa3b27ca05756531c3f2e6bc4e22c27d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
@ -92,7 +92,7 @@ wasmer-config = { path = "./lib/config" }
|
|||||||
wasmer-wasix = { path = "./lib/wasix" }
|
wasmer-wasix = { path = "./lib/wasix" }
|
||||||
|
|
||||||
# Wasmer-owned crates
|
# Wasmer-owned crates
|
||||||
webc = "=8.0"
|
webc = "=9.0"
|
||||||
shared-buffer = "0.1.4"
|
shared-buffer = "0.1.4"
|
||||||
loupe = "0.2.0"
|
loupe = "0.2.0"
|
||||||
|
|
||||||
|
@ -4,11 +4,9 @@ use anyhow::{Context, Result};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use wasmer_compiler::{
|
use wasmer_compiler::{ArtifactBuild, ArtifactCreate, ModuleEnvironment};
|
||||||
types::target::{Architecture, CpuFeature, Target, Triple},
|
|
||||||
ArtifactBuild, ArtifactCreate, ModuleEnvironment,
|
|
||||||
};
|
|
||||||
use wasmer_types::entity::PrimaryMap;
|
use wasmer_types::entity::PrimaryMap;
|
||||||
|
use wasmer_types::target::{Architecture, CpuFeature, Target, Triple};
|
||||||
use wasmer_types::{CompileError, MemoryIndex, MemoryStyle, TableIndex, TableStyle};
|
use wasmer_types::{CompileError, MemoryIndex, MemoryStyle, TableIndex, TableStyle};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::store::StoreOptions;
|
use crate::store::StoreOptions;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::{path::PathBuf, str::FromStr};
|
use std::path::PathBuf;
|
||||||
use wasmer_compiler::types::target::{CpuFeature, Target, Triple};
|
use std::str::FromStr;
|
||||||
use wasmer_types::is_wasm;
|
use wasmer_types::is_wasm;
|
||||||
|
use wasmer_types::target::{CpuFeature, Target, Triple};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
/// The options for the `wasmer validate` subcommand
|
/// The options for the `wasmer validate` subcommand
|
||||||
|
@ -9,10 +9,8 @@ use std::path::PathBuf;
|
|||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmer_compiler::{
|
use wasmer_compiler::{CompilerConfig, EngineBuilder, Features};
|
||||||
types::target::{PointerWidth, Target},
|
use wasmer_types::target::{PointerWidth, Target};
|
||||||
CompilerConfig, EngineBuilder, Features,
|
|
||||||
};
|
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use wasmer_types::Type;
|
use wasmer_types::Type;
|
||||||
use wasmer_types::{MemoryStyle, MemoryType, Pages, TableStyle, TableType};
|
use wasmer_types::{MemoryStyle, MemoryType, Pages, TableStyle, TableType};
|
||||||
|
@ -265,9 +265,9 @@ impl RuntimeOptions {
|
|||||||
let backends = self.get_available_backends()?;
|
let backends = self.get_available_backends()?;
|
||||||
let required_features = Features::default();
|
let required_features = Features::default();
|
||||||
backends
|
backends
|
||||||
.get(0)
|
.first()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_engine(&target, &required_features)
|
.get_engine(target, &required_features)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_engine_for_module(&self, module_contents: &[u8], target: &Target) -> Result<Engine> {
|
pub fn get_engine_for_module(&self, module_contents: &[u8], target: &Target) -> Result<Engine> {
|
||||||
@ -275,20 +275,25 @@ impl RuntimeOptions {
|
|||||||
.detect_features_from_wasm(module_contents)
|
.detect_features_from_wasm(module_contents)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
self.get_engine_for_features(&required_features, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_engine_for_features(
|
||||||
|
&self,
|
||||||
|
required_features: &Features,
|
||||||
|
target: &Target,
|
||||||
|
) -> Result<Engine> {
|
||||||
let backends = self.get_available_backends()?;
|
let backends = self.get_available_backends()?;
|
||||||
let filtered_backends =
|
let filtered_backends =
|
||||||
Self::filter_backends_by_features(backends.clone(), &required_features, &target);
|
Self::filter_backends_by_features(backends.clone(), required_features, target);
|
||||||
|
|
||||||
if filtered_backends.len() == 0 {
|
if filtered_backends.is_empty() {
|
||||||
let enabled_backends = BackendType::enabled();
|
let enabled_backends = BackendType::enabled();
|
||||||
if backends.len() == 1 && enabled_backends.len() > 1 {
|
if backends.len() == 1 && enabled_backends.len() > 1 {
|
||||||
// If the user has chosen an specific backend, we can suggest to use another one
|
// If the user has chosen an specific backend, we can suggest to use another one
|
||||||
let filtered_backends = Self::filter_backends_by_features(
|
let filtered_backends =
|
||||||
enabled_backends,
|
Self::filter_backends_by_features(enabled_backends, required_features, target);
|
||||||
&required_features,
|
let extra_text: String = if !filtered_backends.is_empty() {
|
||||||
&target,
|
|
||||||
);
|
|
||||||
let extra_text: String = if filtered_backends.len() > 0 {
|
|
||||||
format!(". You can use --{} instead", filtered_backends[0])
|
format!(". You can use --{} instead", filtered_backends[0])
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
@ -303,9 +308,9 @@ impl RuntimeOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
filtered_backends
|
filtered_backends
|
||||||
.get(0)
|
.first()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_engine(&target, &required_features)
|
.get_engine(target, required_features)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compiler")]
|
#[cfg(feature = "compiler")]
|
||||||
@ -380,7 +385,6 @@ impl RuntimeOptions {
|
|||||||
features.exceptions(true);
|
features.exceptions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Detected features: {:?}", features);
|
|
||||||
Ok(features)
|
Ok(features)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,7 +394,7 @@ impl RuntimeOptions {
|
|||||||
target: Target,
|
target: Target,
|
||||||
) -> std::result::Result<Engine, anyhow::Error> {
|
) -> std::result::Result<Engine, anyhow::Error> {
|
||||||
let backends = self.get_available_backends()?;
|
let backends = self.get_available_backends()?;
|
||||||
let compiler_config = self.get_sys_compiler_config(&backends.get(0).unwrap())?;
|
let compiler_config = self.get_sys_compiler_config(backends.first().unwrap())?;
|
||||||
let default_features = compiler_config.default_features_for_target(&target);
|
let default_features = compiler_config.default_features_for_target(&target);
|
||||||
let features = self.get_features(&default_features)?;
|
let features = self.get_features(&default_features)?;
|
||||||
Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
|
Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
|
||||||
@ -656,13 +660,10 @@ impl BackendType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get the supported features from the backend
|
// Get the supported features from the backend
|
||||||
let supported = wasmer::Engine::supported_features_for_backend(&backend_kind, &target);
|
let supported = wasmer::Engine::supported_features_for_backend(&backend_kind, target);
|
||||||
|
|
||||||
// Check if the backend supports all required features
|
// Check if the backend supports all required features
|
||||||
if !supported.contains_features(required_features) {
|
if !supported.contains_features(required_features) {
|
||||||
tracing::info!("Backend {:?} doesn't support all required features", self);
|
|
||||||
tracing::info!("Supported: {:?}", supported);
|
|
||||||
tracing::info!("Required: {:?}", required_features);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,20 +173,23 @@ impl Run {
|
|||||||
// Get engine with feature-based backend selection if possible
|
// Get engine with feature-based backend selection if possible
|
||||||
let mut engine = match &wasm_bytes {
|
let mut engine = match &wasm_bytes {
|
||||||
Some(wasm_bytes) => {
|
Some(wasm_bytes) => {
|
||||||
tracing::info!("Attempting to detect WebAssembly features");
|
tracing::info!("Attempting to detect WebAssembly features from binary");
|
||||||
|
|
||||||
self.rt
|
self.rt
|
||||||
.get_engine_for_module(wasm_bytes, &Target::default())?
|
.get_engine_for_module(wasm_bytes, &Target::default())?
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// No WebAssembly file available for analysis, use default engine selection
|
// No WebAssembly file available for analysis, check if we have a webc package
|
||||||
// TODO: get backends from the webc file
|
if let PackageSource::Package(ref pkg_source) = &self.input {
|
||||||
|
tracing::info!("Checking package for WebAssembly features: {}", pkg_source);
|
||||||
|
self.rt.get_engine(&Target::default())?
|
||||||
|
} else {
|
||||||
|
tracing::info!("No feature detection possible, using default engine");
|
||||||
self.rt.get_engine(&Target::default())?
|
self.rt.get_engine(&Target::default())?
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::info!("Executing on backend {}", engine.deterministic_id());
|
|
||||||
|
|
||||||
#[cfg(feature = "sys")]
|
#[cfg(feature = "sys")]
|
||||||
if engine.is_sys() {
|
if engine.is_sys() {
|
||||||
if self.stack_size.is_some() {
|
if self.stack_size.is_some() {
|
||||||
@ -232,7 +235,61 @@ impl Run {
|
|||||||
module_hash,
|
module_hash,
|
||||||
path,
|
path,
|
||||||
} => self.execute_wasm(&path, module, module_hash, runtime.clone()),
|
} => self.execute_wasm(&path, module, module_hash, runtime.clone()),
|
||||||
ExecutableTarget::Package(pkg) => self.execute_webc(&pkg, runtime.clone()),
|
ExecutableTarget::Package(pkg) => {
|
||||||
|
// Check if we should update the engine based on the WebC package features
|
||||||
|
if let Some(cmd) = pkg.get_entrypoint_command() {
|
||||||
|
if let Some(features) = cmd.wasm_features() {
|
||||||
|
// Get the right engine for these features
|
||||||
|
let backends = self.rt.get_available_backends()?;
|
||||||
|
let available_engines = backends
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
let filtered_backends = RuntimeOptions::filter_backends_by_features(
|
||||||
|
backends.clone(),
|
||||||
|
&features,
|
||||||
|
&Target::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !filtered_backends.is_empty() {
|
||||||
|
let engine_id = filtered_backends[0].to_string();
|
||||||
|
|
||||||
|
// Get a new engine that's compatible with the required features
|
||||||
|
if let Ok(new_engine) =
|
||||||
|
filtered_backends[0].get_engine(&Target::default(), &features)
|
||||||
|
{
|
||||||
|
tracing::info!(
|
||||||
|
"The command '{}' requires to run the Wasm module with the features {:?}. The backends available are {}. Choosing {}.",
|
||||||
|
cmd.name(),
|
||||||
|
features,
|
||||||
|
available_engines,
|
||||||
|
engine_id
|
||||||
|
);
|
||||||
|
// Create a new runtime with the updated engine
|
||||||
|
let new_runtime = self.wasi.prepare_runtime(
|
||||||
|
new_engine,
|
||||||
|
&self.env,
|
||||||
|
&capabilities::get_capability_cache_path(
|
||||||
|
&self.env,
|
||||||
|
&self.input,
|
||||||
|
)?,
|
||||||
|
tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()?,
|
||||||
|
preferred_webc_version,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let new_runtime =
|
||||||
|
Arc::new(MonitoringRuntime::new(new_runtime, pb.clone()));
|
||||||
|
return self.execute_webc(&pkg, new_runtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.execute_webc(&pkg, runtime.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ rust-version.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
webc.workspace = true
|
webc.workspace = true
|
||||||
wasmer-config = { version = "0.13.0", path = "../config" }
|
wasmer-config = { version = "0.13.0", path = "../config" }
|
||||||
|
wasmer-types = { path = "../types" }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
bytes = "1.8.0"
|
bytes = "1.8.0"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
@ -16,7 +16,7 @@ pub fn webc_to_package_dir(webc: &Container, target_dir: &Path) -> Result<(), Co
|
|||||||
|
|
||||||
let pkg_annotation = manifest
|
let pkg_annotation = manifest
|
||||||
.wapm()
|
.wapm()
|
||||||
.map_err(|err| ConversionError::with_cause("could not read package annotation", err))?;
|
.map_err(|err| ConversionError::msg(format!("could not read package annotation: {err}")))?;
|
||||||
if let Some(ann) = pkg_annotation {
|
if let Some(ann) = pkg_annotation {
|
||||||
let mut pkg = wasmer_config::package::Package::new_empty();
|
let mut pkg = wasmer_config::package::Package::new_empty();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ pub fn webc_to_package_dir(webc: &Container, target_dir: &Path) -> Result<(), Co
|
|||||||
|
|
||||||
let fs_annotation = manifest
|
let fs_annotation = manifest
|
||||||
.filesystem()
|
.filesystem()
|
||||||
.map_err(|err| ConversionError::with_cause("could n ot read fs annotation", err))?;
|
.map_err(|err| ConversionError::msg(format!("could not read fs annotation: {err}")))?;
|
||||||
if let Some(ann) = fs_annotation {
|
if let Some(ann) = fs_annotation {
|
||||||
for mapping in ann.0 {
|
for mapping in ann.0 {
|
||||||
if mapping.from.is_some() {
|
if mapping.from.is_some() {
|
||||||
@ -177,10 +177,9 @@ pub fn webc_to_package_dir(webc: &Container, target_dir: &Path) -> Result<(), Co
|
|||||||
let atom_annotation = spec
|
let atom_annotation = spec
|
||||||
.annotation::<webc::metadata::annotations::Atom>(webc::metadata::annotations::Atom::KEY)
|
.annotation::<webc::metadata::annotations::Atom>(webc::metadata::annotations::Atom::KEY)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
ConversionError::with_cause(
|
ConversionError::msg(format!(
|
||||||
format!("could not read atom annotation for command '{name}'"),
|
"could not read atom annotation for command '{name}': {err}"
|
||||||
err,
|
))
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ConversionError::msg(format!(
|
ConversionError::msg(format!(
|
||||||
|
@ -296,9 +296,59 @@ fn transform_atoms_shared(
|
|||||||
let mut metadata = IndexMap::new();
|
let mut metadata = IndexMap::new();
|
||||||
|
|
||||||
for (name, (kind, content)) in atoms.iter() {
|
for (name, (kind, content)) in atoms.iter() {
|
||||||
|
// Create atom with annotations including Wasm features if available
|
||||||
|
let mut annotations = IndexMap::new();
|
||||||
|
|
||||||
|
// Detect required WebAssembly features by analyzing the module binary
|
||||||
|
let features_result = wasmer_types::Features::detect_from_wasm(content);
|
||||||
|
|
||||||
|
if let Ok(features) = features_result {
|
||||||
|
// Convert wasmer_types::Features to webc::metadata::annotations::Wasm
|
||||||
|
let mut feature_strings = Vec::new();
|
||||||
|
|
||||||
|
if features.simd {
|
||||||
|
feature_strings.push("simd".to_string());
|
||||||
|
}
|
||||||
|
if features.bulk_memory {
|
||||||
|
feature_strings.push("bulk-memory".to_string());
|
||||||
|
}
|
||||||
|
if features.reference_types {
|
||||||
|
feature_strings.push("reference-types".to_string());
|
||||||
|
}
|
||||||
|
if features.multi_value {
|
||||||
|
feature_strings.push("multi-value".to_string());
|
||||||
|
}
|
||||||
|
if features.threads {
|
||||||
|
feature_strings.push("threads".to_string());
|
||||||
|
}
|
||||||
|
if features.exceptions {
|
||||||
|
feature_strings.push("exception-handling".to_string());
|
||||||
|
}
|
||||||
|
if features.memory64 {
|
||||||
|
feature_strings.push("memory64".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only create annotation if we detected features
|
||||||
|
if !feature_strings.is_empty() {
|
||||||
|
let wasm = webc::metadata::annotations::Wasm::new(feature_strings);
|
||||||
|
match ciborium::value::Value::serialized(&wasm) {
|
||||||
|
Ok(wasm_value) => {
|
||||||
|
annotations.insert(
|
||||||
|
webc::metadata::annotations::Wasm::KEY.to_string(),
|
||||||
|
wasm_value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to serialize wasm features: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let atom = Atom {
|
let atom = Atom {
|
||||||
kind: atom_kind(kind.as_ref().map(|s| s.as_str()))?,
|
kind: atom_kind(kind.as_ref().map(|s| s.as_str()))?,
|
||||||
signature: atom_signature(content),
|
signature: atom_signature(content),
|
||||||
|
annotations,
|
||||||
};
|
};
|
||||||
|
|
||||||
if metadata.contains_key(name) {
|
if metadata.contains_key(name) {
|
||||||
|
@ -57,6 +57,39 @@ impl BinaryPackageCommand {
|
|||||||
pub fn hash(&self) -> &ModuleHash {
|
pub fn hash(&self) -> &ModuleHash {
|
||||||
&self.hash
|
&self.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the WebAssembly features required by this command's module
|
||||||
|
pub fn wasm_features(&self) -> Option<wasmer_types::Features> {
|
||||||
|
// Create default features
|
||||||
|
let mut features = wasmer_types::Features::default();
|
||||||
|
|
||||||
|
// Try to extract information from any annotations that might exist
|
||||||
|
if let Some(wasm_anno) = self.metadata().annotations.get("wasm") {
|
||||||
|
tracing::debug!("Found wasm annotation: {:?}", wasm_anno);
|
||||||
|
// We found an annotation, enable the basic features by default
|
||||||
|
features.reference_types(true);
|
||||||
|
features.bulk_memory(true);
|
||||||
|
|
||||||
|
// We'll use a simpler approach - if we have wasm annotations, just enable
|
||||||
|
// the features that are commonly needed for most modules
|
||||||
|
features.exceptions(true);
|
||||||
|
|
||||||
|
tracing::debug!("WebC module features from annotations: {:?}", features);
|
||||||
|
return Some(features);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try to detect features from the atom bytes
|
||||||
|
match wasmer_types::Features::detect_from_wasm(&self.atom) {
|
||||||
|
Ok(features) => {
|
||||||
|
tracing::debug!("WebC module features detected from binary: {:?}", features);
|
||||||
|
Some(features)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("Failed to detect WebAssembly features from atom: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A WebAssembly package that has been loaded into memory.
|
/// A WebAssembly package that has been loaded into memory.
|
||||||
|
Reference in New Issue
Block a user