mirror of
https://github.com/mii443/wasmer.git
synced 2025-08-22 16:35:33 +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",
|
||||
"url",
|
||||
"wasmer-config",
|
||||
"wasmer-types",
|
||||
"webc",
|
||||
]
|
||||
|
||||
@ -7603,9 +7604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webc"
|
||||
version = "8.0.0"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3bdee7f8a5c220e497d264a89fca8e29627a702e2cc2fb8846426b8f0d2e2d5"
|
||||
checksum = "38544ae3a351279fa913b4dee9c548f0aa3b27ca05756531c3f2e6bc4e22c27d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
@ -92,7 +92,7 @@ wasmer-config = { path = "./lib/config" }
|
||||
wasmer-wasix = { path = "./lib/wasix" }
|
||||
|
||||
# Wasmer-owned crates
|
||||
webc = "=8.0"
|
||||
webc = "=9.0"
|
||||
shared-buffer = "0.1.4"
|
||||
loupe = "0.2.0"
|
||||
|
||||
|
@ -4,11 +4,9 @@ use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use wasmer_compiler::{
|
||||
types::target::{Architecture, CpuFeature, Target, Triple},
|
||||
ArtifactBuild, ArtifactCreate, ModuleEnvironment,
|
||||
};
|
||||
use wasmer_compiler::{ArtifactBuild, ArtifactCreate, ModuleEnvironment};
|
||||
use wasmer_types::entity::PrimaryMap;
|
||||
use wasmer_types::target::{Architecture, CpuFeature, Target, Triple};
|
||||
use wasmer_types::{CompileError, MemoryIndex, MemoryStyle, TableIndex, TableStyle};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::store::StoreOptions;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use clap::Parser;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
use wasmer_compiler::types::target::{CpuFeature, Target, Triple};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use wasmer_types::is_wasm;
|
||||
use wasmer_types::target::{CpuFeature, Target, Triple};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
/// The options for the `wasmer validate` subcommand
|
||||
|
@ -9,10 +9,8 @@ use std::path::PathBuf;
|
||||
use std::string::ToString;
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::Arc;
|
||||
use wasmer_compiler::{
|
||||
types::target::{PointerWidth, Target},
|
||||
CompilerConfig, EngineBuilder, Features,
|
||||
};
|
||||
use wasmer_compiler::{CompilerConfig, EngineBuilder, Features};
|
||||
use wasmer_types::target::{PointerWidth, Target};
|
||||
#[cfg(doc)]
|
||||
use wasmer_types::Type;
|
||||
use wasmer_types::{MemoryStyle, MemoryType, Pages, TableStyle, TableType};
|
||||
|
@ -265,9 +265,9 @@ impl RuntimeOptions {
|
||||
let backends = self.get_available_backends()?;
|
||||
let required_features = Features::default();
|
||||
backends
|
||||
.get(0)
|
||||
.first()
|
||||
.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> {
|
||||
@ -275,20 +275,25 @@ impl RuntimeOptions {
|
||||
.detect_features_from_wasm(module_contents)
|
||||
.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 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();
|
||||
if backends.len() == 1 && enabled_backends.len() > 1 {
|
||||
// If the user has chosen an specific backend, we can suggest to use another one
|
||||
let filtered_backends = Self::filter_backends_by_features(
|
||||
enabled_backends,
|
||||
&required_features,
|
||||
&target,
|
||||
);
|
||||
let extra_text: String = if filtered_backends.len() > 0 {
|
||||
let filtered_backends =
|
||||
Self::filter_backends_by_features(enabled_backends, required_features, target);
|
||||
let extra_text: String = if !filtered_backends.is_empty() {
|
||||
format!(". You can use --{} instead", filtered_backends[0])
|
||||
} else {
|
||||
"".to_string()
|
||||
@ -303,9 +308,9 @@ impl RuntimeOptions {
|
||||
}
|
||||
}
|
||||
filtered_backends
|
||||
.get(0)
|
||||
.first()
|
||||
.unwrap()
|
||||
.get_engine(&target, &required_features)
|
||||
.get_engine(target, required_features)
|
||||
}
|
||||
|
||||
#[cfg(feature = "compiler")]
|
||||
@ -380,7 +385,6 @@ impl RuntimeOptions {
|
||||
features.exceptions(true);
|
||||
}
|
||||
|
||||
tracing::info!("Detected features: {:?}", features);
|
||||
Ok(features)
|
||||
}
|
||||
|
||||
@ -390,7 +394,7 @@ impl RuntimeOptions {
|
||||
target: Target,
|
||||
) -> std::result::Result<Engine, anyhow::Error> {
|
||||
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 features = self.get_features(&default_features)?;
|
||||
Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
|
||||
@ -656,13 +660,10 @@ impl BackendType {
|
||||
};
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -173,20 +173,23 @@ impl Run {
|
||||
// Get engine with feature-based backend selection if possible
|
||||
let mut engine = match &wasm_bytes {
|
||||
Some(wasm_bytes) => {
|
||||
tracing::info!("Attempting to detect WebAssembly features");
|
||||
tracing::info!("Attempting to detect WebAssembly features from binary");
|
||||
|
||||
self.rt
|
||||
.get_engine_for_module(wasm_bytes, &Target::default())?
|
||||
}
|
||||
None => {
|
||||
// No WebAssembly file available for analysis, use default engine selection
|
||||
// TODO: get backends from the webc file
|
||||
self.rt.get_engine(&Target::default())?
|
||||
// No WebAssembly file available for analysis, check if we have a webc package
|
||||
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())?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("Executing on backend {}", engine.deterministic_id());
|
||||
|
||||
#[cfg(feature = "sys")]
|
||||
if engine.is_sys() {
|
||||
if self.stack_size.is_some() {
|
||||
@ -232,7 +235,61 @@ impl Run {
|
||||
module_hash,
|
||||
path,
|
||||
} => 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]
|
||||
webc.workspace = true
|
||||
wasmer-config = { version = "0.13.0", path = "../config" }
|
||||
wasmer-types = { path = "../types" }
|
||||
toml = { workspace = true }
|
||||
bytes = "1.8.0"
|
||||
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
|
||||
.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 {
|
||||
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
|
||||
.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 {
|
||||
for mapping in ann.0 {
|
||||
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
|
||||
.annotation::<webc::metadata::annotations::Atom>(webc::metadata::annotations::Atom::KEY)
|
||||
.map_err(|err| {
|
||||
ConversionError::with_cause(
|
||||
format!("could not read atom annotation for command '{name}'"),
|
||||
err,
|
||||
)
|
||||
ConversionError::msg(format!(
|
||||
"could not read atom annotation for command '{name}': {err}"
|
||||
))
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
ConversionError::msg(format!(
|
||||
|
@ -296,9 +296,59 @@ fn transform_atoms_shared(
|
||||
let mut metadata = IndexMap::new();
|
||||
|
||||
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 {
|
||||
kind: atom_kind(kind.as_ref().map(|s| s.as_str()))?,
|
||||
signature: atom_signature(content),
|
||||
annotations,
|
||||
};
|
||||
|
||||
if metadata.contains_key(name) {
|
||||
|
@ -57,6 +57,39 @@ impl BinaryPackageCommand {
|
||||
pub fn hash(&self) -> &ModuleHash {
|
||||
&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.
|
||||
|
Reference in New Issue
Block a user