fuzzgen: Fuzz Switch API (#4502)
* fuzzgen: Use Switch interface Turns out this is an interface that the frontend provides. We should fuzz it. * cranelift: Restrict index range in Switch emission on fuzzgen
This commit is contained in:
@@ -16,8 +16,16 @@ pub struct Config {
|
|||||||
/// This value does not apply to block0 which takes the function params
|
/// This value does not apply to block0 which takes the function params
|
||||||
/// and is thus governed by `signature_params`
|
/// and is thus governed by `signature_params`
|
||||||
pub block_signature_params: RangeInclusive<usize>,
|
pub block_signature_params: RangeInclusive<usize>,
|
||||||
|
/// Max number of jump tables generated per function
|
||||||
|
/// Note, the actual number of jump tables may be larger if the Switch interface
|
||||||
|
/// decides to insert more.
|
||||||
pub jump_tables_per_function: RangeInclusive<usize>,
|
pub jump_tables_per_function: RangeInclusive<usize>,
|
||||||
pub jump_table_entries: RangeInclusive<usize>,
|
pub jump_table_entries: RangeInclusive<usize>,
|
||||||
|
/// The Switch API specializes either individual blocks or contiguous ranges.
|
||||||
|
/// In `switch_cases` we decide to produce either a single block or a range.
|
||||||
|
/// The size of the range is controlled by `switch_max_range_size`.
|
||||||
|
pub switch_cases: RangeInclusive<usize>,
|
||||||
|
pub switch_max_range_size: RangeInclusive<usize>,
|
||||||
|
|
||||||
/// Stack slots.
|
/// Stack slots.
|
||||||
/// The combination of these two determines stack usage per function
|
/// The combination of these two determines stack usage per function
|
||||||
@@ -38,6 +46,9 @@ impl Default for Config {
|
|||||||
block_signature_params: 0..=16,
|
block_signature_params: 0..=16,
|
||||||
jump_tables_per_function: 0..=4,
|
jump_tables_per_function: 0..=4,
|
||||||
jump_table_entries: 0..=16,
|
jump_table_entries: 0..=16,
|
||||||
|
switch_cases: 0..=64,
|
||||||
|
// Ranges smaller than 2 don't make sense.
|
||||||
|
switch_max_range_size: 2..=32,
|
||||||
static_stack_slots_per_function: 0..=8,
|
static_stack_slots_per_function: 0..=8,
|
||||||
static_stack_slot_size: 0..=128,
|
static_stack_slot_size: 0..=128,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ use cranelift::codegen::ir::{
|
|||||||
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value,
|
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value,
|
||||||
};
|
};
|
||||||
use cranelift::codegen::isa::CallConv;
|
use cranelift::codegen::isa::CallConv;
|
||||||
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
|
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
|
||||||
use cranelift::prelude::{
|
use cranelift::prelude::{
|
||||||
EntityRef, InstBuilder, IntCC, JumpTableData, StackSlotData, StackSlotKind,
|
EntityRef, InstBuilder, IntCC, JumpTableData, StackSlotData, StackSlotKind,
|
||||||
};
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
type BlockSignature = Vec<Type>;
|
type BlockSignature = Vec<Type>;
|
||||||
@@ -477,6 +478,51 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_switch(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||||
|
let _type = *self.u.choose(
|
||||||
|
&[
|
||||||
|
I8, I16, I32, I64, // TODO: I128
|
||||||
|
][..],
|
||||||
|
)?;
|
||||||
|
let switch_var = self.get_variable_of_type(_type)?;
|
||||||
|
let switch_val = builder.use_var(switch_var);
|
||||||
|
|
||||||
|
let valid_blocks = self.generate_valid_jumptable_target_blocks();
|
||||||
|
let default_block = *self.u.choose(&valid_blocks[..])?;
|
||||||
|
|
||||||
|
// Build this into a HashMap since we cannot have duplicate entries.
|
||||||
|
let mut entries = HashMap::new();
|
||||||
|
for _ in 0..self.param(&self.config.switch_cases)? {
|
||||||
|
// The Switch API only allows for entries that are addressable by the index type
|
||||||
|
// so we need to limit the range of values that we generate.
|
||||||
|
let (ty_min, ty_max) = _type.bounds(false);
|
||||||
|
let range_start = self.u.int_in_range(ty_min..=ty_max)?;
|
||||||
|
|
||||||
|
// We can either insert a contiguous range of blocks or a individual block
|
||||||
|
// This is done because the Switch API specializes contiguous ranges.
|
||||||
|
let range_size = if bool::arbitrary(self.u)? {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.param(&self.config.switch_max_range_size)?
|
||||||
|
} as u128;
|
||||||
|
|
||||||
|
// Build the switch entries
|
||||||
|
for i in 0..range_size {
|
||||||
|
let index = range_start.wrapping_add(i) % ty_max;
|
||||||
|
let block = *self.u.choose(&valid_blocks[..])?;
|
||||||
|
entries.insert(index, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut switch = Switch::new();
|
||||||
|
for (entry, block) in entries.into_iter() {
|
||||||
|
switch.set_entry(entry, block);
|
||||||
|
}
|
||||||
|
switch.emit(builder, switch_val, default_block);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// We always need to exit safely out of a block.
|
/// We always need to exit safely out of a block.
|
||||||
/// This either means a jump into another block or a return.
|
/// This either means a jump into another block or a return.
|
||||||
fn finalize_block(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
fn finalize_block(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||||
@@ -487,6 +533,7 @@ where
|
|||||||
Self::generate_br_table,
|
Self::generate_br_table,
|
||||||
Self::generate_jump,
|
Self::generate_jump,
|
||||||
Self::generate_return,
|
Self::generate_return,
|
||||||
|
Self::generate_switch,
|
||||||
][..],
|
][..],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user