Skip to content

Commit

Permalink
Add support for extended-const to wasm-smith (bytecodealliance#1862)
Browse files Browse the repository at this point in the history
* Add support for `extended-const` to `wasm-smith`

This commit adds support for the `extended-const` proposal to the
`wasm-smith` generator crate. This is gated behind a new
`extended_const_enabled` field in `wasm_smith::Config`

This commit additionally refactors the `wasm-smith` test suite and
fuzzers in this repository to share the same source for generating a
`WasmFeatures` from a `Config`.

Finally this also fixes a few minor issues with wide arithmetic, namely
it was enabled in fuzzing for components by accident and needed to be
added to the `Config`-to-`WasmFeatures` conversion.

* Fix compile
  • Loading branch information
alexcrichton authored Oct 11, 2024
1 parent 60a3367 commit 9884abd
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 89 deletions.
9 changes: 9 additions & 0 deletions crates/wasm-encoder/src/core/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3861,6 +3861,15 @@ impl ConstExpr {
}
}

/// Create a constant expression with the sequence of instructions
pub fn extended<'a>(insns: impl IntoIterator<Item = Instruction<'a>>) -> Self {
let mut bytes = vec![];
for insn in insns {
insn.encode(&mut bytes);
}
Self { bytes }
}

fn new_insn(insn: Instruction) -> Self {
let mut bytes = vec![];
insn.encode(&mut bytes);
Expand Down
2 changes: 1 addition & 1 deletion crates/wasm-smith/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ leb128 = { workspace = true }
serde = { workspace = true, optional = true }
serde_derive = { workspace = true, optional = true }
wasm-encoder = { workspace = true }
wasmparser = { workspace = true, optional = true, features = ['validate'] }
wasmparser = { workspace = true, optional = true, features = ['validate', 'features'] }
wat = { workspace = true, optional = true }

[dev-dependencies]
Expand Down
51 changes: 51 additions & 0 deletions crates/wasm-smith/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,13 @@ define_config! {
///
/// Defaults to `false`.
pub wide_arithmetic_enabled: bool = false,

/// Determines whether the [extended-const proposal] is enabled.
///
/// [extended-const proposal]: https://github.com/WebAssembly/extended-const
///
/// Defaults to `true`.
pub extended_const_enabled: bool = true,
}
}

Expand Down Expand Up @@ -712,6 +719,7 @@ impl<'a> Arbitrary<'a> for Config {
max_table_elements: u.int_in_range(0..=1_000_000)?,
disallow_traps: u.arbitrary()?,
allow_floats: u.arbitrary()?,
extended_const_enabled: u.arbitrary()?,

// These fields, unlike the ones above, are less useful to set.
// They either make weird inputs or are for features not widely
Expand Down Expand Up @@ -779,4 +787,47 @@ impl Config {
self.relaxed_simd_enabled = false;
}
}

/// Returns the set of features that are necessary for validating against
/// this `Config`.
#[cfg(feature = "wasmparser")]
pub fn features(&self) -> wasmparser::WasmFeatures {
use wasmparser::WasmFeatures;

// Currently wasm-smith doesn't have knobs for the MVP (floats) or
// `mutable-global`. These are unconditionally enabled.
let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::WASM1;

// All other features that can be generated by wasm-smith are gated by
// configuration fields. Conditionally set each feature based on the
// status of the fields in `self`.
features.set(
WasmFeatures::SATURATING_FLOAT_TO_INT,
self.saturating_float_to_int_enabled,
);
features.set(
WasmFeatures::SIGN_EXTENSION,
self.sign_extension_ops_enabled,
);
features.set(WasmFeatures::REFERENCE_TYPES, self.reference_types_enabled);
features.set(WasmFeatures::MULTI_VALUE, self.multi_value_enabled);
features.set(WasmFeatures::BULK_MEMORY, self.bulk_memory_enabled);
features.set(WasmFeatures::SIMD, self.simd_enabled);
features.set(WasmFeatures::RELAXED_SIMD, self.relaxed_simd_enabled);
features.set(WasmFeatures::MULTI_MEMORY, self.max_memories > 1);
features.set(WasmFeatures::EXCEPTIONS, self.exceptions_enabled);
features.set(WasmFeatures::MEMORY64, self.memory64_enabled);
features.set(WasmFeatures::TAIL_CALL, self.tail_call_enabled);
features.set(WasmFeatures::FUNCTION_REFERENCES, self.gc_enabled);
features.set(WasmFeatures::GC, self.gc_enabled);
features.set(WasmFeatures::THREADS, self.threads_enabled);
features.set(
WasmFeatures::CUSTOM_PAGE_SIZES,
self.custom_page_sizes_enabled,
);
features.set(WasmFeatures::EXTENDED_CONST, self.extended_const_enabled);
features.set(WasmFeatures::WIDE_ARITHMETIC, self.wide_arithmetic_enabled);

features
}
}
77 changes: 74 additions & 3 deletions crates/wasm-smith/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1664,8 +1664,18 @@ impl Module {
// type of that value.
let ty = self.arbitrary_matching_val_type(u, ty)?;
match ty {
ValType::I32 => choices.push(Box::new(|u, _| Ok(ConstExpr::i32_const(u.arbitrary()?)))),
ValType::I64 => choices.push(Box::new(|u, _| Ok(ConstExpr::i64_const(u.arbitrary()?)))),
ValType::I32 => {
choices.push(Box::new(|u, _| Ok(ConstExpr::i32_const(u.arbitrary()?))));
if self.config.extended_const_enabled {
choices.push(Box::new(arbitrary_extended_const));
}
}
ValType::I64 => {
choices.push(Box::new(|u, _| Ok(ConstExpr::i64_const(u.arbitrary()?))));
if self.config.extended_const_enabled {
choices.push(Box::new(arbitrary_extended_const));
}
}
ValType::F32 => choices.push(Box::new(|u, _| Ok(ConstExpr::f32_const(u.arbitrary()?)))),
ValType::F64 => choices.push(Box::new(|u, _| Ok(ConstExpr::f64_const(u.arbitrary()?)))),
ValType::V128 => {
Expand Down Expand Up @@ -1707,7 +1717,68 @@ impl Module {
let f = u.choose(&choices)?;
let ret = f(u, ty);
self.const_expr_choices = choices;
ret
return ret;

/// Implementation of generation of expressions from the
/// `extended-const` proposal to WebAssembly. This proposal enabled
/// using `i{32,64}.{add,sub,mul}` in constant expressions in addition
/// to the previous `i{32,64}.const` instructions. Note that at this
/// time this doesn't use the full expression generator in
/// `code_builder.rs` but instead inlines just what's necessary for
/// constant expressions here.
fn arbitrary_extended_const(u: &mut Unstructured<'_>, ty: ValType) -> Result<ConstExpr> {
use wasm_encoder::Instruction::*;

// This only works for i32/i64, would need refactoring for different
// types.
assert!(ty == ValType::I32 || ty == ValType::I64);
let add = if ty == ValType::I32 { I32Add } else { I64Add };
let sub = if ty == ValType::I32 { I32Sub } else { I64Sub };
let mul = if ty == ValType::I32 { I32Mul } else { I64Mul };
let const_: fn(&mut Unstructured<'_>) -> Result<wasm_encoder::Instruction<'static>> =
if ty == ValType::I32 {
|u| u.arbitrary().map(I32Const)
} else {
|u| u.arbitrary().map(I64Const)
};

// Here `instrs` is the list of instructions, in reverse order, that
// are going to be emitted. The `needed` value keeps track of how
// many values are needed to complete this expression. New
// instructions must be generated while some more items are needed.
let mut instrs = Vec::new();
let mut needed = 1;
while needed > 0 {
// If fuzz data has been exhausted or if this is a "large
// enough" constant expression then force generation of
// constants to finish out the expression.
let choice = if u.is_empty() || instrs.len() > 10 {
0
} else {
u.int_in_range(0..=3)?
};
match choice {
0 => {
instrs.push(const_(u)?);
needed -= 1;
}
1 => {
instrs.push(add.clone());
needed += 1;
}
2 => {
instrs.push(sub.clone());
needed += 1;
}
3 => {
instrs.push(mul.clone());
needed += 1;
}
_ => unreachable!(),
}
}
Ok(ConstExpr::extended(instrs.into_iter().rev()))
}
}

fn arbitrary_globals(&mut self, u: &mut Unstructured) -> Result<()> {
Expand Down
4 changes: 2 additions & 2 deletions crates/wasm-smith/tests/available_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use wasmparser::Validator;
use wasmparser::{Parser, TypeRef, ValType};

mod common;
use common::{parser_features_from_config, validate};
use common::validate;

#[test]
fn smoke_test_imports_config() {
Expand All @@ -21,7 +21,7 @@ fn smoke_test_imports_config() {

let mut u = Unstructured::new(&buf);
let (config, available) = import_config(&mut u);
let features = parser_features_from_config(&config);
let features = config.features();

if let Ok(module) = Module::new(config, &mut u) {
let wasm_bytes = module.to_bytes();
Expand Down
35 changes: 1 addition & 34 deletions crates/wasm-smith/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,4 @@
use wasm_smith::Config;
use wasmparser::{types::Types, Validator, WasmFeatures};

pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::WASM1;
features.set(
WasmFeatures::SATURATING_FLOAT_TO_INT,
config.saturating_float_to_int_enabled,
);
features.set(
WasmFeatures::SIGN_EXTENSION,
config.sign_extension_ops_enabled,
);
features.set(
WasmFeatures::REFERENCE_TYPES,
config.reference_types_enabled,
);
features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
features.set(WasmFeatures::SIMD, config.simd_enabled);
features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
features.set(WasmFeatures::GC, config.gc_enabled);
features.set(WasmFeatures::THREADS, config.threads_enabled);
features.set(
WasmFeatures::CUSTOM_PAGE_SIZES,
config.custom_page_sizes_enabled,
);
features
}
use wasmparser::{types::Types, Validator};

pub fn validate(validator: &mut Validator, bytes: &[u8]) -> Types {
let err = match validator.validate_all(bytes) {
Expand Down
5 changes: 3 additions & 2 deletions crates/wasm-smith/tests/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use wasm_smith::{Config, Module};
use wasmparser::{Validator, WasmFeatures};

mod common;
use common::{parser_features_from_config, validate};
use common::validate;

#[test]
fn smoke_test_module() {
Expand Down Expand Up @@ -77,6 +77,7 @@ fn multi_value_disabled() {
}

#[test]
#[cfg(feature = "wasmparser")]
fn smoke_can_smith_valid_webassembly_one_point_oh() {
let mut rng = SmallRng::seed_from_u64(42);
let mut buf = vec![0; 10240];
Expand All @@ -97,7 +98,7 @@ fn smoke_can_smith_valid_webassembly_one_point_oh() {
cfg.gc_enabled = false;
cfg.max_memories = 1;
cfg.max_tables = 1;
let features = parser_features_from_config(&cfg);
let features = cfg.features();
if let Ok(module) = Module::new(cfg, &mut u) {
let wasm_bytes = module.to_bytes();
// This table should set to `true` only features specified in wasm-core-1 spec.
Expand Down
4 changes: 2 additions & 2 deletions crates/wasm-smith/tests/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use wasmparser::{
};

mod common;
use common::{parser_features_from_config, validate};
use common::validate;

#[derive(Debug, PartialEq)]
enum ExportType {
Expand Down Expand Up @@ -144,7 +144,7 @@ fn smoke_test_exports(exports_test_case: &str, seed: u64) {
let mut config = Config::arbitrary(&mut u).expect("arbitrary config");
config.exports = Some(wasm.clone());

let features = parser_features_from_config(&config);
let features = config.features();
let module = Module::new(config, &mut u).unwrap();
let wasm_bytes = module.to_bytes();

Expand Down
2 changes: 1 addition & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ libfuzzer-sys = { workspace = true }
log = { workspace = true }
tempfile = "3.0"
wasm-mutate = { workspace = true }
wasm-smith = { workspace = true, features = ['component-model'] }
wasm-smith = { workspace = true, features = ['component-model', 'wasmparser'] }
wasmparser = { workspace = true, features = ['features'] }
wasmprinter = { workspace = true }
wasmtime = { workspace = true, optional = true }
Expand Down
46 changes: 2 additions & 44 deletions fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use libfuzzer_sys::arbitrary::{Result, Unstructured};
use std::fmt::Debug;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasm_smith::{Component, Config, Module};
use wasmparser::WasmFeatures;

pub mod incremental_parse;
pub mod mutate;
Expand All @@ -27,6 +26,7 @@ pub fn generate_valid_module(
config.memory64_enabled = u.arbitrary()?;
config.canonicalize_nans = u.arbitrary()?;
config.custom_page_sizes_enabled = u.arbitrary()?;
config.wide_arithmetic_enabled = u.arbitrary()?;

configure(&mut config, u)?;

Expand Down Expand Up @@ -60,7 +60,6 @@ pub fn generate_valid_component(
config.memory64_enabled = u.arbitrary()?;
config.exceptions_enabled = u.arbitrary()?;
config.canonicalize_nans = u.arbitrary()?;
config.wide_arithmetic_enabled = u.arbitrary()?;

configure(&mut config, u)?;

Expand All @@ -75,48 +74,7 @@ pub fn generate_valid_component(
}

pub fn validator_for_config(config: &Config) -> wasmparser::Validator {
// Start with the bare-bones set of features that wasm started with. Then
// wasm-smith doesn't have knobs to enable/disable mutable globals so
// unconditionally enable that as well.
let mut features = WasmFeatures::WASM1 | WasmFeatures::MUTABLE_GLOBAL;

// Next conditionally enable/disable features based on `config`.
features.set(
WasmFeatures::SIGN_EXTENSION,
config.sign_extension_ops_enabled,
);
features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
features.set(
WasmFeatures::SATURATING_FLOAT_TO_INT,
config.saturating_float_to_int_enabled,
);
features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
features.set(
WasmFeatures::REFERENCE_TYPES,
config.reference_types_enabled,
);
features.set(WasmFeatures::SIMD, config.simd_enabled);
features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
features.set(WasmFeatures::THREADS, config.threads_enabled);
features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
features.set(
WasmFeatures::CUSTOM_PAGE_SIZES,
config.custom_page_sizes_enabled,
);
// TODO: determine our larger story for function-references in
// wasm-tools and whether we should just have a Wasm GC flag since
// function-references is effectively part of the Wasm GC proposal at
// this point.
features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
features.set(WasmFeatures::GC, config.gc_enabled);
features.set(
WasmFeatures::WIDE_ARITHMETIC,
config.wide_arithmetic_enabled,
);
wasmparser::Validator::new_with_features(features)
wasmparser::Validator::new_with_features(config.features())
}

// Optionally log the module and its configuration if we've gotten this
Expand Down

0 comments on commit 9884abd

Please sign in to comment.