Skip to content

Commit 9884abd

Browse files
authored
Add support for extended-const to wasm-smith (#1862)
* 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
1 parent 60a3367 commit 9884abd

File tree

10 files changed

+146
-89
lines changed

10 files changed

+146
-89
lines changed

crates/wasm-encoder/src/core/code.rs

+9
Original file line numberDiff line numberDiff line change
@@ -3861,6 +3861,15 @@ impl ConstExpr {
38613861
}
38623862
}
38633863

3864+
/// Create a constant expression with the sequence of instructions
3865+
pub fn extended<'a>(insns: impl IntoIterator<Item = Instruction<'a>>) -> Self {
3866+
let mut bytes = vec![];
3867+
for insn in insns {
3868+
insn.encode(&mut bytes);
3869+
}
3870+
Self { bytes }
3871+
}
3872+
38643873
fn new_insn(insn: Instruction) -> Self {
38653874
let mut bytes = vec![];
38663875
insn.encode(&mut bytes);

crates/wasm-smith/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ leb128 = { workspace = true }
3232
serde = { workspace = true, optional = true }
3333
serde_derive = { workspace = true, optional = true }
3434
wasm-encoder = { workspace = true }
35-
wasmparser = { workspace = true, optional = true, features = ['validate'] }
35+
wasmparser = { workspace = true, optional = true, features = ['validate', 'features'] }
3636
wat = { workspace = true, optional = true }
3737

3838
[dev-dependencies]

crates/wasm-smith/src/config.rs

+51
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,13 @@ define_config! {
606606
///
607607
/// Defaults to `false`.
608608
pub wide_arithmetic_enabled: bool = false,
609+
610+
/// Determines whether the [extended-const proposal] is enabled.
611+
///
612+
/// [extended-const proposal]: https://github.com/WebAssembly/extended-const
613+
///
614+
/// Defaults to `true`.
615+
pub extended_const_enabled: bool = true,
609616
}
610617
}
611618

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

716724
// These fields, unlike the ones above, are less useful to set.
717725
// They either make weird inputs or are for features not widely
@@ -779,4 +787,47 @@ impl Config {
779787
self.relaxed_simd_enabled = false;
780788
}
781789
}
790+
791+
/// Returns the set of features that are necessary for validating against
792+
/// this `Config`.
793+
#[cfg(feature = "wasmparser")]
794+
pub fn features(&self) -> wasmparser::WasmFeatures {
795+
use wasmparser::WasmFeatures;
796+
797+
// Currently wasm-smith doesn't have knobs for the MVP (floats) or
798+
// `mutable-global`. These are unconditionally enabled.
799+
let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::WASM1;
800+
801+
// All other features that can be generated by wasm-smith are gated by
802+
// configuration fields. Conditionally set each feature based on the
803+
// status of the fields in `self`.
804+
features.set(
805+
WasmFeatures::SATURATING_FLOAT_TO_INT,
806+
self.saturating_float_to_int_enabled,
807+
);
808+
features.set(
809+
WasmFeatures::SIGN_EXTENSION,
810+
self.sign_extension_ops_enabled,
811+
);
812+
features.set(WasmFeatures::REFERENCE_TYPES, self.reference_types_enabled);
813+
features.set(WasmFeatures::MULTI_VALUE, self.multi_value_enabled);
814+
features.set(WasmFeatures::BULK_MEMORY, self.bulk_memory_enabled);
815+
features.set(WasmFeatures::SIMD, self.simd_enabled);
816+
features.set(WasmFeatures::RELAXED_SIMD, self.relaxed_simd_enabled);
817+
features.set(WasmFeatures::MULTI_MEMORY, self.max_memories > 1);
818+
features.set(WasmFeatures::EXCEPTIONS, self.exceptions_enabled);
819+
features.set(WasmFeatures::MEMORY64, self.memory64_enabled);
820+
features.set(WasmFeatures::TAIL_CALL, self.tail_call_enabled);
821+
features.set(WasmFeatures::FUNCTION_REFERENCES, self.gc_enabled);
822+
features.set(WasmFeatures::GC, self.gc_enabled);
823+
features.set(WasmFeatures::THREADS, self.threads_enabled);
824+
features.set(
825+
WasmFeatures::CUSTOM_PAGE_SIZES,
826+
self.custom_page_sizes_enabled,
827+
);
828+
features.set(WasmFeatures::EXTENDED_CONST, self.extended_const_enabled);
829+
features.set(WasmFeatures::WIDE_ARITHMETIC, self.wide_arithmetic_enabled);
830+
831+
features
832+
}
782833
}

crates/wasm-smith/src/core.rs

+74-3
Original file line numberDiff line numberDiff line change
@@ -1664,8 +1664,18 @@ impl Module {
16641664
// type of that value.
16651665
let ty = self.arbitrary_matching_val_type(u, ty)?;
16661666
match ty {
1667-
ValType::I32 => choices.push(Box::new(|u, _| Ok(ConstExpr::i32_const(u.arbitrary()?)))),
1668-
ValType::I64 => choices.push(Box::new(|u, _| Ok(ConstExpr::i64_const(u.arbitrary()?)))),
1667+
ValType::I32 => {
1668+
choices.push(Box::new(|u, _| Ok(ConstExpr::i32_const(u.arbitrary()?))));
1669+
if self.config.extended_const_enabled {
1670+
choices.push(Box::new(arbitrary_extended_const));
1671+
}
1672+
}
1673+
ValType::I64 => {
1674+
choices.push(Box::new(|u, _| Ok(ConstExpr::i64_const(u.arbitrary()?))));
1675+
if self.config.extended_const_enabled {
1676+
choices.push(Box::new(arbitrary_extended_const));
1677+
}
1678+
}
16691679
ValType::F32 => choices.push(Box::new(|u, _| Ok(ConstExpr::f32_const(u.arbitrary()?)))),
16701680
ValType::F64 => choices.push(Box::new(|u, _| Ok(ConstExpr::f64_const(u.arbitrary()?)))),
16711681
ValType::V128 => {
@@ -1707,7 +1717,68 @@ impl Module {
17071717
let f = u.choose(&choices)?;
17081718
let ret = f(u, ty);
17091719
self.const_expr_choices = choices;
1710-
ret
1720+
return ret;
1721+
1722+
/// Implementation of generation of expressions from the
1723+
/// `extended-const` proposal to WebAssembly. This proposal enabled
1724+
/// using `i{32,64}.{add,sub,mul}` in constant expressions in addition
1725+
/// to the previous `i{32,64}.const` instructions. Note that at this
1726+
/// time this doesn't use the full expression generator in
1727+
/// `code_builder.rs` but instead inlines just what's necessary for
1728+
/// constant expressions here.
1729+
fn arbitrary_extended_const(u: &mut Unstructured<'_>, ty: ValType) -> Result<ConstExpr> {
1730+
use wasm_encoder::Instruction::*;
1731+
1732+
// This only works for i32/i64, would need refactoring for different
1733+
// types.
1734+
assert!(ty == ValType::I32 || ty == ValType::I64);
1735+
let add = if ty == ValType::I32 { I32Add } else { I64Add };
1736+
let sub = if ty == ValType::I32 { I32Sub } else { I64Sub };
1737+
let mul = if ty == ValType::I32 { I32Mul } else { I64Mul };
1738+
let const_: fn(&mut Unstructured<'_>) -> Result<wasm_encoder::Instruction<'static>> =
1739+
if ty == ValType::I32 {
1740+
|u| u.arbitrary().map(I32Const)
1741+
} else {
1742+
|u| u.arbitrary().map(I64Const)
1743+
};
1744+
1745+
// Here `instrs` is the list of instructions, in reverse order, that
1746+
// are going to be emitted. The `needed` value keeps track of how
1747+
// many values are needed to complete this expression. New
1748+
// instructions must be generated while some more items are needed.
1749+
let mut instrs = Vec::new();
1750+
let mut needed = 1;
1751+
while needed > 0 {
1752+
// If fuzz data has been exhausted or if this is a "large
1753+
// enough" constant expression then force generation of
1754+
// constants to finish out the expression.
1755+
let choice = if u.is_empty() || instrs.len() > 10 {
1756+
0
1757+
} else {
1758+
u.int_in_range(0..=3)?
1759+
};
1760+
match choice {
1761+
0 => {
1762+
instrs.push(const_(u)?);
1763+
needed -= 1;
1764+
}
1765+
1 => {
1766+
instrs.push(add.clone());
1767+
needed += 1;
1768+
}
1769+
2 => {
1770+
instrs.push(sub.clone());
1771+
needed += 1;
1772+
}
1773+
3 => {
1774+
instrs.push(mul.clone());
1775+
needed += 1;
1776+
}
1777+
_ => unreachable!(),
1778+
}
1779+
}
1780+
Ok(ConstExpr::extended(instrs.into_iter().rev()))
1781+
}
17111782
}
17121783

17131784
fn arbitrary_globals(&mut self, u: &mut Unstructured) -> Result<()> {

crates/wasm-smith/tests/available_imports.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use wasmparser::Validator;
88
use wasmparser::{Parser, TypeRef, ValType};
99

1010
mod common;
11-
use common::{parser_features_from_config, validate};
11+
use common::validate;
1212

1313
#[test]
1414
fn smoke_test_imports_config() {
@@ -21,7 +21,7 @@ fn smoke_test_imports_config() {
2121

2222
let mut u = Unstructured::new(&buf);
2323
let (config, available) = import_config(&mut u);
24-
let features = parser_features_from_config(&config);
24+
let features = config.features();
2525

2626
if let Ok(module) = Module::new(config, &mut u) {
2727
let wasm_bytes = module.to_bytes();

crates/wasm-smith/tests/common/mod.rs

+1-34
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,4 @@
1-
use wasm_smith::Config;
2-
use wasmparser::{types::Types, Validator, WasmFeatures};
3-
4-
pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
5-
let mut features = WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::WASM1;
6-
features.set(
7-
WasmFeatures::SATURATING_FLOAT_TO_INT,
8-
config.saturating_float_to_int_enabled,
9-
);
10-
features.set(
11-
WasmFeatures::SIGN_EXTENSION,
12-
config.sign_extension_ops_enabled,
13-
);
14-
features.set(
15-
WasmFeatures::REFERENCE_TYPES,
16-
config.reference_types_enabled,
17-
);
18-
features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
19-
features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
20-
features.set(WasmFeatures::SIMD, config.simd_enabled);
21-
features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
22-
features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
23-
features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
24-
features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
25-
features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
26-
features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
27-
features.set(WasmFeatures::GC, config.gc_enabled);
28-
features.set(WasmFeatures::THREADS, config.threads_enabled);
29-
features.set(
30-
WasmFeatures::CUSTOM_PAGE_SIZES,
31-
config.custom_page_sizes_enabled,
32-
);
33-
features
34-
}
1+
use wasmparser::{types::Types, Validator};
352

363
pub fn validate(validator: &mut Validator, bytes: &[u8]) -> Types {
374
let err = match validator.validate_all(bytes) {

crates/wasm-smith/tests/core.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use wasm_smith::{Config, Module};
44
use wasmparser::{Validator, WasmFeatures};
55

66
mod common;
7-
use common::{parser_features_from_config, validate};
7+
use common::validate;
88

99
#[test]
1010
fn smoke_test_module() {
@@ -77,6 +77,7 @@ fn multi_value_disabled() {
7777
}
7878

7979
#[test]
80+
#[cfg(feature = "wasmparser")]
8081
fn smoke_can_smith_valid_webassembly_one_point_oh() {
8182
let mut rng = SmallRng::seed_from_u64(42);
8283
let mut buf = vec![0; 10240];
@@ -97,7 +98,7 @@ fn smoke_can_smith_valid_webassembly_one_point_oh() {
9798
cfg.gc_enabled = false;
9899
cfg.max_memories = 1;
99100
cfg.max_tables = 1;
100-
let features = parser_features_from_config(&cfg);
101+
let features = cfg.features();
101102
if let Ok(module) = Module::new(cfg, &mut u) {
102103
let wasm_bytes = module.to_bytes();
103104
// This table should set to `true` only features specified in wasm-core-1 spec.

crates/wasm-smith/tests/exports.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use wasmparser::{
88
};
99

1010
mod common;
11-
use common::{parser_features_from_config, validate};
11+
use common::validate;
1212

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

147-
let features = parser_features_from_config(&config);
147+
let features = config.features();
148148
let module = Module::new(config, &mut u).unwrap();
149149
let wasm_bytes = module.to_bytes();
150150

fuzz/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ libfuzzer-sys = { workspace = true }
1818
log = { workspace = true }
1919
tempfile = "3.0"
2020
wasm-mutate = { workspace = true }
21-
wasm-smith = { workspace = true, features = ['component-model'] }
21+
wasm-smith = { workspace = true, features = ['component-model', 'wasmparser'] }
2222
wasmparser = { workspace = true, features = ['features'] }
2323
wasmprinter = { workspace = true }
2424
wasmtime = { workspace = true, optional = true }

fuzz/src/lib.rs

+2-44
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use libfuzzer_sys::arbitrary::{Result, Unstructured};
22
use std::fmt::Debug;
33
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
44
use wasm_smith::{Component, Config, Module};
5-
use wasmparser::WasmFeatures;
65

76
pub mod incremental_parse;
87
pub mod mutate;
@@ -27,6 +26,7 @@ pub fn generate_valid_module(
2726
config.memory64_enabled = u.arbitrary()?;
2827
config.canonicalize_nans = u.arbitrary()?;
2928
config.custom_page_sizes_enabled = u.arbitrary()?;
29+
config.wide_arithmetic_enabled = u.arbitrary()?;
3030

3131
configure(&mut config, u)?;
3232

@@ -60,7 +60,6 @@ pub fn generate_valid_component(
6060
config.memory64_enabled = u.arbitrary()?;
6161
config.exceptions_enabled = u.arbitrary()?;
6262
config.canonicalize_nans = u.arbitrary()?;
63-
config.wide_arithmetic_enabled = u.arbitrary()?;
6463

6564
configure(&mut config, u)?;
6665

@@ -75,48 +74,7 @@ pub fn generate_valid_component(
7574
}
7675

7776
pub fn validator_for_config(config: &Config) -> wasmparser::Validator {
78-
// Start with the bare-bones set of features that wasm started with. Then
79-
// wasm-smith doesn't have knobs to enable/disable mutable globals so
80-
// unconditionally enable that as well.
81-
let mut features = WasmFeatures::WASM1 | WasmFeatures::MUTABLE_GLOBAL;
82-
83-
// Next conditionally enable/disable features based on `config`.
84-
features.set(
85-
WasmFeatures::SIGN_EXTENSION,
86-
config.sign_extension_ops_enabled,
87-
);
88-
features.set(WasmFeatures::TAIL_CALL, config.tail_call_enabled);
89-
features.set(
90-
WasmFeatures::SATURATING_FLOAT_TO_INT,
91-
config.saturating_float_to_int_enabled,
92-
);
93-
features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled);
94-
features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1);
95-
features.set(WasmFeatures::BULK_MEMORY, config.bulk_memory_enabled);
96-
features.set(
97-
WasmFeatures::REFERENCE_TYPES,
98-
config.reference_types_enabled,
99-
);
100-
features.set(WasmFeatures::SIMD, config.simd_enabled);
101-
features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled);
102-
features.set(WasmFeatures::MEMORY64, config.memory64_enabled);
103-
features.set(WasmFeatures::THREADS, config.threads_enabled);
104-
features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled);
105-
features.set(
106-
WasmFeatures::CUSTOM_PAGE_SIZES,
107-
config.custom_page_sizes_enabled,
108-
);
109-
// TODO: determine our larger story for function-references in
110-
// wasm-tools and whether we should just have a Wasm GC flag since
111-
// function-references is effectively part of the Wasm GC proposal at
112-
// this point.
113-
features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
114-
features.set(WasmFeatures::GC, config.gc_enabled);
115-
features.set(
116-
WasmFeatures::WIDE_ARITHMETIC,
117-
config.wide_arithmetic_enabled,
118-
);
119-
wasmparser::Validator::new_with_features(features)
77+
wasmparser::Validator::new_with_features(config.features())
12078
}
12179

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

0 commit comments

Comments
 (0)