2499: First pass on TableOps fuzzer generator wasm_encoder migration (#2501)
* 2499: First pass on TableOps fuzzer generator wasm_encoder migration
- wasm binary generated via sections and smushed together into a module
- test: compare generated wat against expected wat
- note: doesn't work
- Grouped instructions not implemented
- Vec<u8> to wat String not implemented
* 2499: Add typesection, abstract instruction puts, and update test
- TableOp.insert now will interact with a function object directly
- add types for generated function
- expected test string now reflects expected generated code
* 2499: Mark unused index as _i
* 2499: Function insertion is in proper stack order, and fix off by 1
index
- imported functions must be typed
- instructions operate on a stack ie. define values as instructions
before using
* 2499: Apply suggestions from code review
- typo fixing
- oracle ingests binary bytes itself
Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
* 2499: Code cleanup + renaming vars
- busywork, nothing to see here
Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2609,6 +2609,7 @@ dependencies = [
|
|||||||
"env_logger 0.8.1",
|
"env_logger 0.8.1",
|
||||||
"log",
|
"log",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"wasm-encoder",
|
||||||
"wasm-smith",
|
"wasm-smith",
|
||||||
"wasmi",
|
"wasmi",
|
||||||
"wasmparser 0.70.0",
|
"wasmparser 0.70.0",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ wasmparser = "0.70"
|
|||||||
wasmprinter = "0.2.17"
|
wasmprinter = "0.2.17"
|
||||||
wasmtime = { path = "../wasmtime" }
|
wasmtime = { path = "../wasmtime" }
|
||||||
wasmtime-wast = { path = "../wast" }
|
wasmtime-wast = { path = "../wast" }
|
||||||
|
wasm-encoder = "0.2"
|
||||||
wasm-smith = "0.3.0"
|
wasm-smith = "0.3.0"
|
||||||
wasmi = "0.7.0"
|
wasmi = "0.7.0"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
//! Generating series of `table.get` and `table.set` operations.
|
//! Generating series of `table.get` and `table.set` operations.
|
||||||
|
|
||||||
use arbitrary::Arbitrary;
|
use arbitrary::Arbitrary;
|
||||||
use std::fmt::Write;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use wasm_encoder::{
|
||||||
|
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection,
|
||||||
|
Instruction, Limits, Module, TableSection, TableType, TypeSection, ValType,
|
||||||
|
};
|
||||||
|
|
||||||
/// A description of a Wasm module that makes a series of `externref` table
|
/// A description of a Wasm module that makes a series of `externref` table
|
||||||
/// operations.
|
/// operations.
|
||||||
@@ -32,7 +35,7 @@ impl TableOps {
|
|||||||
table_size
|
table_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this into a WAT string.
|
/// Serialize this module into a Wasm binary.
|
||||||
///
|
///
|
||||||
/// The module requires a single import: `(import "" "gc" (func))`. This
|
/// The module requires a single import: `(import "" "gc" (func))`. This
|
||||||
/// should be a function to trigger GC.
|
/// should be a function to trigger GC.
|
||||||
@@ -43,32 +46,62 @@ impl TableOps {
|
|||||||
/// The "run" function is guaranteed to terminate (no loops or recursive
|
/// The "run" function is guaranteed to terminate (no loops or recursive
|
||||||
/// calls), but is not guaranteed to avoid traps (might access out-of-bounds
|
/// calls), but is not guaranteed to avoid traps (might access out-of-bounds
|
||||||
/// of the table).
|
/// of the table).
|
||||||
pub fn to_wat_string(&self) -> String {
|
pub fn to_wasm_binary(&self) -> Vec<u8> {
|
||||||
let mut wat = "(module\n".to_string();
|
let mut module = Module::new();
|
||||||
|
|
||||||
// Import the GC function.
|
// Import the GC function.
|
||||||
wat.push_str(" (import \"\" \"gc\" (func))\n");
|
let mut imports = ImportSection::new();
|
||||||
|
imports.import("", Some("gc"), EntityType::Function(0));
|
||||||
|
|
||||||
// Define our table.
|
// Define our table.
|
||||||
wat.push_str(" (table $table ");
|
let mut tables = TableSection::new();
|
||||||
write!(&mut wat, "{}", self.table_size()).unwrap();
|
tables.table(TableType {
|
||||||
wat.push_str(" externref)\n");
|
element_type: ValType::ExternRef,
|
||||||
|
limits: Limits {
|
||||||
|
min: self.table_size(),
|
||||||
|
max: None,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Encode the types for all functions that we are using.
|
||||||
|
let mut types = TypeSection::new();
|
||||||
|
types.function(vec![], vec![]); // 0: "gc"
|
||||||
|
let mut params: Vec<ValType> = Vec::with_capacity(self.num_params() as usize);
|
||||||
|
for _i in 0..self.num_params() {
|
||||||
|
params.push(ValType::ExternRef);
|
||||||
|
}
|
||||||
|
let results = vec![];
|
||||||
|
types.function(params, results); // 1: "run"
|
||||||
|
|
||||||
// Define the "run" function export.
|
// Define the "run" function export.
|
||||||
wat.push_str(r#" (func (export "run") (param"#);
|
let mut functions = FunctionSection::new();
|
||||||
for _ in 0..self.num_params() {
|
functions.function(1);
|
||||||
wat.push_str(" externref");
|
|
||||||
}
|
|
||||||
wat.push_str(")\n");
|
|
||||||
for op in self.ops.iter().take(MAX_OPS) {
|
|
||||||
wat.push_str(" ");
|
|
||||||
op.to_wat_string(&mut wat);
|
|
||||||
wat.push('\n');
|
|
||||||
}
|
|
||||||
wat.push_str(" )\n");
|
|
||||||
|
|
||||||
wat.push_str(")\n");
|
let mut exports = ExportSection::new();
|
||||||
wat
|
exports.export("run", Export::Function(1));
|
||||||
|
|
||||||
|
let mut params: Vec<(u32, ValType)> = Vec::with_capacity(self.num_params() as usize);
|
||||||
|
for _i in 0..self.num_params() {
|
||||||
|
params.push((0, ValType::ExternRef));
|
||||||
|
}
|
||||||
|
let mut func = Function::new(params);
|
||||||
|
|
||||||
|
for op in self.ops.iter().take(MAX_OPS) {
|
||||||
|
op.insert(&mut func);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut code = CodeSection::new();
|
||||||
|
code.function(&func);
|
||||||
|
|
||||||
|
module
|
||||||
|
.section(&types)
|
||||||
|
.section(&imports)
|
||||||
|
.section(&functions)
|
||||||
|
.section(&tables)
|
||||||
|
.section(&exports)
|
||||||
|
.section(&code);
|
||||||
|
|
||||||
|
module.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,37 +110,34 @@ pub(crate) enum TableOp {
|
|||||||
// `(call 0)`
|
// `(call 0)`
|
||||||
Gc,
|
Gc,
|
||||||
// `(drop (table.get x))`
|
// `(drop (table.get x))`
|
||||||
Get(u32),
|
Get(i32),
|
||||||
// `(table.set x (local.get y))`
|
// `(table.set x (local.get y))`
|
||||||
SetFromParam(u32, u8),
|
SetFromParam(i32, u32),
|
||||||
// `(table.set x (table.get y))`
|
// `(table.set x (table.get y))`
|
||||||
SetFromGet(u32, u32),
|
SetFromGet(i32, i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableOp {
|
impl TableOp {
|
||||||
fn to_wat_string(&self, wat: &mut String) {
|
fn insert(&self, func: &mut Function) {
|
||||||
match self {
|
match self {
|
||||||
Self::Gc => {
|
Self::Gc => {
|
||||||
wat.push_str("(call 0)");
|
func.instruction(Instruction::Call(0));
|
||||||
}
|
}
|
||||||
Self::Get(x) => {
|
Self::Get(x) => {
|
||||||
wat.push_str("(drop (table.get $table (i32.const ");
|
func.instruction(Instruction::I32Const(*x));
|
||||||
write!(wat, "{}", x).unwrap();
|
func.instruction(Instruction::TableGet { table: 0 });
|
||||||
wat.push_str(")))");
|
func.instruction(Instruction::Drop);
|
||||||
}
|
}
|
||||||
Self::SetFromParam(x, y) => {
|
Self::SetFromParam(x, y) => {
|
||||||
wat.push_str("(table.set $table (i32.const ");
|
func.instruction(Instruction::I32Const(*x));
|
||||||
write!(wat, "{}", x).unwrap();
|
func.instruction(Instruction::LocalGet(*y));
|
||||||
wat.push_str(") (local.get ");
|
func.instruction(Instruction::TableSet { table: 0 });
|
||||||
write!(wat, "{}", y).unwrap();
|
|
||||||
wat.push_str("))");
|
|
||||||
}
|
}
|
||||||
Self::SetFromGet(x, y) => {
|
Self::SetFromGet(x, y) => {
|
||||||
wat.push_str("(table.set $table (i32.const ");
|
func.instruction(Instruction::I32Const(*x));
|
||||||
write!(wat, "{}", x).unwrap();
|
func.instruction(Instruction::I32Const(*y));
|
||||||
wat.push_str(") (table.get $table (i32.const ");
|
func.instruction(Instruction::TableGet { table: 0 });
|
||||||
write!(wat, "{}", y).unwrap();
|
func.instruction(Instruction::TableSet { table: 0 });
|
||||||
wat.push_str(")))");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,17 +162,26 @@ mod tests {
|
|||||||
|
|
||||||
let expected = r#"
|
let expected = r#"
|
||||||
(module
|
(module
|
||||||
(import "" "gc" (func))
|
(type (;0;) (func))
|
||||||
(table $table 10 externref)
|
(type (;1;) (func (param externref externref)))
|
||||||
(func (export "run") (param externref externref)
|
(import "" "gc" (func (;0;) (type 0)))
|
||||||
(call 0)
|
(func (;1;) (type 1) (param externref externref)
|
||||||
(drop (table.get $table (i32.const 0)))
|
call 0
|
||||||
(table.set $table (i32.const 1) (local.get 2))
|
i32.const 0
|
||||||
(table.set $table (i32.const 3) (table.get $table (i32.const 4)))
|
table.get 0
|
||||||
)
|
drop
|
||||||
)
|
i32.const 1
|
||||||
|
local.get 2
|
||||||
|
table.set 0
|
||||||
|
i32.const 3
|
||||||
|
i32.const 4
|
||||||
|
table.get 0
|
||||||
|
table.set 0)
|
||||||
|
(table (;0;) 10 externref)
|
||||||
|
(export "run" (func 1)))
|
||||||
"#;
|
"#;
|
||||||
let actual = ops.to_wat_string();
|
let actual = ops.to_wasm_binary();
|
||||||
|
let actual = wasmprinter::print_bytes(&actual).unwrap();
|
||||||
assert_eq!(actual.trim(), expected.trim());
|
assert_eq!(actual.trim(), expected.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,17 +40,6 @@ fn log_wasm(wasm: &[u8]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_wat(wat: &str) {
|
|
||||||
if !log::log_enabled!(log::Level::Debug) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = CNT.fetch_add(1, SeqCst);
|
|
||||||
let name = format!("testcase{}.wat", i);
|
|
||||||
log::debug!("wrote wat file to `{}`", name);
|
|
||||||
std::fs::write(&name, wat).expect("failed to write wat file");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
|
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
|
||||||
/// panic or segfault or anything else that can be detected "passively".
|
/// panic or segfault or anything else that can be detected "passively".
|
||||||
///
|
///
|
||||||
@@ -418,9 +407,9 @@ pub fn table_ops(config: crate::generators::Config, ops: crate::generators::tabl
|
|||||||
let engine = Engine::new(&config);
|
let engine = Engine::new(&config);
|
||||||
let store = Store::new(&engine);
|
let store = Store::new(&engine);
|
||||||
|
|
||||||
let wat = ops.to_wat_string();
|
let wasm = ops.to_wasm_binary();
|
||||||
log_wat(&wat);
|
log_wasm(&wasm);
|
||||||
let module = match Module::new(&engine, &wat) {
|
let module = match Module::new(&engine, &wasm) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user