Make the table_ops test case generator use globals as well

This will make it generate `table.set`s that come from `global.get`s and
`global.get`s that come from `table.set`s. Ultimately, it should give us much
more fuzzing coverage of `externref` globals, their barriers, and passing
`externref`s into and out of Wasm to get or set globals.
This commit is contained in:
Nick Fitzgerald
2022-01-27 13:59:05 -08:00
parent cbc6f6071f
commit cc8d7778e2

View File

@@ -3,8 +3,8 @@
use arbitrary::Arbitrary; use arbitrary::Arbitrary;
use std::ops::Range; use std::ops::Range;
use wasm_encoder::{ use wasm_encoder::{
CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, ImportSection, CodeSection, EntityType, Export, ExportSection, Function, FunctionSection, GlobalSection,
Instruction, Module, TableSection, TableType, TypeSection, ValType, ImportSection, Instruction, 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
@@ -12,11 +12,13 @@ use wasm_encoder::{
#[derive(Arbitrary, Debug)] #[derive(Arbitrary, Debug)]
pub struct TableOps { pub struct TableOps {
num_params: u8, num_params: u8,
num_globals: u8,
table_size: u32, table_size: u32,
ops: Vec<TableOp>, ops: Vec<TableOp>,
} }
const NUM_PARAMS_RANGE: Range<u8> = 1..10; const NUM_PARAMS_RANGE: Range<u8> = 1..10;
const NUM_GLOBALS_RANGE: Range<u8> = 1..10;
const TABLE_SIZE_RANGE: Range<u32> = 1..100; const TABLE_SIZE_RANGE: Range<u32> = 1..100;
const MAX_OPS: usize = 100; const MAX_OPS: usize = 100;
@@ -28,6 +30,13 @@ impl TableOps {
num_params num_params
} }
/// Get the number of globals this module has.
pub fn num_globals(&self) -> u8 {
let num_globals = std::cmp::max(self.num_globals, NUM_GLOBALS_RANGE.start);
let num_globals = std::cmp::min(num_globals, NUM_GLOBALS_RANGE.end);
num_globals
}
/// Get the size of the table that this module uses. /// Get the size of the table that this module uses.
pub fn table_size(&self) -> u32 { pub fn table_size(&self) -> u32 {
let table_size = std::cmp::max(self.table_size, TABLE_SIZE_RANGE.start); let table_size = std::cmp::max(self.table_size, TABLE_SIZE_RANGE.start);
@@ -98,6 +107,18 @@ impl TableOps {
maximum: None, maximum: None,
}); });
// Define our globals.
let mut globals = GlobalSection::new();
for _ in 0..self.num_globals() {
globals.global(
wasm_encoder::GlobalType {
val_type: wasm_encoder::ValType::ExternRef,
mutable: true,
},
Instruction::RefNull(wasm_encoder::ValType::ExternRef),
);
}
// Define the "run" function export. // Define the "run" function export.
let mut functions = FunctionSection::new(); let mut functions = FunctionSection::new();
functions.function(1); functions.function(1);
@@ -111,7 +132,12 @@ impl TableOps {
func.instruction(Instruction::Loop(wasm_encoder::BlockType::Empty)); func.instruction(Instruction::Loop(wasm_encoder::BlockType::Empty));
for op in self.ops.iter().take(MAX_OPS) { for op in self.ops.iter().take(MAX_OPS) {
op.insert(&mut func, self.num_params() as u32, self.table_size()); op.insert(
&mut func,
self.num_params() as u32,
self.table_size(),
self.num_globals() as u32,
);
} }
func.instruction(Instruction::Br(0)); func.instruction(Instruction::Br(0));
func.instruction(Instruction::End); func.instruction(Instruction::End);
@@ -125,6 +151,7 @@ impl TableOps {
.section(&imports) .section(&imports)
.section(&functions) .section(&functions)
.section(&tables) .section(&tables)
.section(&globals)
.section(&exports) .section(&exports)
.section(&code); .section(&code);
@@ -132,32 +159,56 @@ impl TableOps {
} }
} }
#[derive(Arbitrary, Debug)] #[derive(Arbitrary, Copy, Clone, Debug)]
pub(crate) enum TableOp { pub(crate) enum TableOp {
// `call $gc; drop; drop; drop;` // `call $gc; drop; drop; drop;`
Gc, Gc,
// `(drop (table.get x))` // `(drop (table.get x))`
Get(i32), Get(i32),
// `(drop (global.get i))`
GetGlobal(u32),
// `(table.set x (local.get y))` // `(table.set x (local.get y))`
SetFromParam(i32, u32), SetFromParam(i32, u32),
// `(table.set x (table.get y))` // `(table.set x (table.get y))`
SetFromGet(i32, i32), SetFromGet(i32, i32),
// `call $make_refs; table.set x; table.set y; table.set z` // `call $make_refs; table.set x; table.set y; table.set z`
SetFromMake(i32, i32, i32), SetFromMake(i32, i32, i32),
// `(global.set x (local.get y))`
SetGlobalFromParam(u32, u32),
// `(global.set x (table.get y))`
SetGlobalFromGet(u32, i32),
// `call $make_refs; global.set x; global.set y; global.set z`
SetGlobalFromMake(u32, u32, u32),
// `call $make_refs; drop; drop; drop;` // `call $make_refs; drop; drop; drop;`
Make, Make,
// `local.get x; local.get y; local.get z; call $take_refs` // `local.get x; local.get y; local.get z; call $take_refs`
TakeFromParams(u32, u32, u32), TakeFromParams(u32, u32, u32),
// `table.get x; table.get y; table.get z; call $take_refs` // `table.get x; table.get y; table.get z; call $take_refs`
TakeFromGet(i32, i32, i32), TakeFromGet(i32, i32, i32),
// `global.get x; global.get y; global.get z; call $take_refs`
TakeFromGlobalGet(u32, u32, u32),
// `call $make_refs; call $take_refs` // `call $make_refs; call $take_refs`
TakeFromMake, TakeFromMake,
// `call $gc; call $take_refs` // `call $gc; call $take_refs`
TakeFromGc, TakeFromGc,
} }
impl TableOp { impl TableOp {
fn insert(&self, func: &mut Function, num_params: u32, table_size: u32) { fn insert(self, func: &mut Function, num_params: u32, table_size: u32, num_globals: u32) {
assert!(num_params > 0); assert!(num_params > 0);
assert!(table_size > 0); assert!(table_size > 0);
@@ -165,49 +216,53 @@ impl TableOp {
// but still rare. // but still rare.
let table_mod = table_size as i32 + 1; let table_mod = table_size as i32 + 1;
let gc_func_idx = 0;
let take_refs_func_idx = 1;
let make_refs_func_idx = 2;
match self { match self {
Self::Gc => { Self::Gc => {
func.instruction(Instruction::Call(0)); func.instruction(Instruction::Call(gc_func_idx));
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
} }
Self::Get(x) => { Self::Get(x) => {
func.instruction(Instruction::I32Const(*x % table_mod)); func.instruction(Instruction::I32Const(x % table_mod));
func.instruction(Instruction::TableGet { table: 0 }); func.instruction(Instruction::TableGet { table: 0 });
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
} }
Self::SetFromParam(x, y) => { Self::SetFromParam(x, y) => {
func.instruction(Instruction::I32Const(*x % table_mod)); func.instruction(Instruction::I32Const(x % table_mod));
func.instruction(Instruction::LocalGet(*y % num_params)); func.instruction(Instruction::LocalGet(y % num_params));
func.instruction(Instruction::TableSet { table: 0 }); func.instruction(Instruction::TableSet { table: 0 });
} }
Self::SetFromGet(x, y) => { Self::SetFromGet(x, y) => {
func.instruction(Instruction::I32Const(*x % table_mod)); func.instruction(Instruction::I32Const(x % table_mod));
func.instruction(Instruction::I32Const(*y % table_mod)); func.instruction(Instruction::I32Const(y % table_mod));
func.instruction(Instruction::TableGet { table: 0 }); func.instruction(Instruction::TableGet { table: 0 });
func.instruction(Instruction::TableSet { table: 0 }); func.instruction(Instruction::TableSet { table: 0 });
} }
Self::SetFromMake(x, y, z) => { Self::SetFromMake(x, y, z) => {
func.instruction(Instruction::Call(2)); func.instruction(Instruction::Call(make_refs_func_idx));
func.instruction(Instruction::LocalSet(num_params)); func.instruction(Instruction::LocalSet(num_params));
func.instruction(Instruction::I32Const(*x % table_mod)); func.instruction(Instruction::I32Const(x % table_mod));
func.instruction(Instruction::LocalGet(num_params)); func.instruction(Instruction::LocalGet(num_params));
func.instruction(Instruction::TableSet { table: 0 }); func.instruction(Instruction::TableSet { table: 0 });
func.instruction(Instruction::LocalSet(num_params)); func.instruction(Instruction::LocalSet(num_params));
func.instruction(Instruction::I32Const(*y % table_mod)); func.instruction(Instruction::I32Const(y % table_mod));
func.instruction(Instruction::LocalGet(num_params)); func.instruction(Instruction::LocalGet(num_params));
func.instruction(Instruction::TableSet { table: 0 }); func.instruction(Instruction::TableSet { table: 0 });
func.instruction(Instruction::LocalSet(num_params)); func.instruction(Instruction::LocalSet(num_params));
func.instruction(Instruction::I32Const(*z % table_mod)); func.instruction(Instruction::I32Const(z % table_mod));
func.instruction(Instruction::LocalGet(num_params)); func.instruction(Instruction::LocalGet(num_params));
func.instruction(Instruction::TableSet { table: 0 }); func.instruction(Instruction::TableSet { table: 0 });
} }
TableOp::Make => { TableOp::Make => {
func.instruction(Instruction::Call(2)); func.instruction(Instruction::Call(make_refs_func_idx));
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
func.instruction(Instruction::Drop); func.instruction(Instruction::Drop);
@@ -216,27 +271,52 @@ impl TableOp {
func.instruction(Instruction::LocalGet(x % num_params)); func.instruction(Instruction::LocalGet(x % num_params));
func.instruction(Instruction::LocalGet(y % num_params)); func.instruction(Instruction::LocalGet(y % num_params));
func.instruction(Instruction::LocalGet(z % num_params)); func.instruction(Instruction::LocalGet(z % num_params));
func.instruction(Instruction::Call(1)); func.instruction(Instruction::Call(take_refs_func_idx));
} }
TableOp::TakeFromGet(x, y, z) => { TableOp::TakeFromGet(x, y, z) => {
func.instruction(Instruction::I32Const(*x % table_mod)); func.instruction(Instruction::I32Const(x % table_mod));
func.instruction(Instruction::TableGet { table: 0 }); func.instruction(Instruction::TableGet { table: 0 });
func.instruction(Instruction::I32Const(*y % table_mod)); func.instruction(Instruction::I32Const(y % table_mod));
func.instruction(Instruction::TableGet { table: 0 }); func.instruction(Instruction::TableGet { table: 0 });
func.instruction(Instruction::I32Const(*z % table_mod)); func.instruction(Instruction::I32Const(z % table_mod));
func.instruction(Instruction::TableGet { table: 0 }); func.instruction(Instruction::TableGet { table: 0 });
func.instruction(Instruction::Call(1)); func.instruction(Instruction::Call(take_refs_func_idx));
} }
TableOp::TakeFromMake => { TableOp::TakeFromMake => {
func.instruction(Instruction::Call(2)); func.instruction(Instruction::Call(make_refs_func_idx));
func.instruction(Instruction::Call(1)); func.instruction(Instruction::Call(take_refs_func_idx));
} }
Self::TakeFromGc => { Self::TakeFromGc => {
func.instruction(Instruction::Call(0)); func.instruction(Instruction::Call(gc_func_idx));
func.instruction(Instruction::Call(1)); func.instruction(Instruction::Call(take_refs_func_idx));
}
TableOp::GetGlobal(x) => {
func.instruction(Instruction::GlobalGet(x % num_globals));
func.instruction(Instruction::Drop);
}
TableOp::SetGlobalFromParam(global, param) => {
func.instruction(Instruction::LocalGet(param % num_params));
func.instruction(Instruction::GlobalSet(global % num_globals));
}
TableOp::SetGlobalFromGet(global, x) => {
func.instruction(Instruction::I32Const(x));
func.instruction(Instruction::TableGet { table: 0 });
func.instruction(Instruction::GlobalSet(global % num_globals));
}
TableOp::SetGlobalFromMake(x, y, z) => {
func.instruction(Instruction::Call(make_refs_func_idx));
func.instruction(Instruction::GlobalSet(x % num_globals));
func.instruction(Instruction::GlobalSet(y % num_globals));
func.instruction(Instruction::GlobalSet(z % num_globals));
}
TableOp::TakeFromGlobalGet(x, y, z) => {
func.instruction(Instruction::GlobalGet(x % num_globals));
func.instruction(Instruction::GlobalGet(y % num_globals));
func.instruction(Instruction::GlobalGet(z % num_globals));
func.instruction(Instruction::Call(take_refs_func_idx));
} }
} }
} }
@@ -250,6 +330,7 @@ mod tests {
fn test_wat_string() { fn test_wat_string() {
let ops = TableOps { let ops = TableOps {
num_params: 5, num_params: 5,
num_globals: 1,
table_size: 20, table_size: 20,
ops: vec![ ops: vec![
TableOp::Gc, TableOp::Gc,
@@ -261,6 +342,11 @@ mod tests {
TableOp::TakeFromParams(8, 9, 10), TableOp::TakeFromParams(8, 9, 10),
TableOp::TakeFromGet(11, 12, 13), TableOp::TakeFromGet(11, 12, 13),
TableOp::TakeFromMake, TableOp::TakeFromMake,
TableOp::GetGlobal(14),
TableOp::SetGlobalFromParam(15, 16),
TableOp::SetGlobalFromGet(17, 18),
TableOp::SetGlobalFromMake(19, 20, 21),
TableOp::TakeFromGlobalGet(22, 23, 24),
], ],
}; };
@@ -320,9 +406,25 @@ mod tests {
call 1 call 1
call 2 call 2
call 1 call 1
global.get 0
drop
local.get 1
global.set 0
i32.const 18
table.get 0
global.set 0
call 2
global.set 0
global.set 0
global.set 0
global.get 0
global.get 0
global.get 0
call 1
br 0 (;@1;) br 0 (;@1;)
end) end)
(table (;0;) 20 externref) (table (;0;) 20 externref)
(global (;0;) (mut externref) (ref.null extern))
(export "run" (func 3))) (export "run" (func 3)))
"#; "#;
eprintln!("expected WAT = {}", expected); eprintln!("expected WAT = {}", expected);