Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for extended-const to wasm-smith #1862

Merged
merged 2 commits into from
Oct 11, 2024
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
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