diff --git a/cranelift/fuzzgen/src/config.rs b/cranelift/fuzzgen/src/config.rs index 2217ced1a9..0ea0ae1408 100644 --- a/cranelift/fuzzgen/src/config.rs +++ b/cranelift/fuzzgen/src/config.rs @@ -16,8 +16,16 @@ pub struct Config { /// This value does not apply to block0 which takes the function params /// and is thus governed by `signature_params` pub block_signature_params: RangeInclusive, + /// 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, pub jump_table_entries: RangeInclusive, + /// 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, + pub switch_max_range_size: RangeInclusive, /// Stack slots. /// The combination of these two determines stack usage per function @@ -38,6 +46,9 @@ impl Default for Config { block_signature_params: 0..=16, jump_tables_per_function: 0..=4, 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_slot_size: 0..=128, } diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index 089ea20326..13a10367a6 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -7,10 +7,11 @@ use cranelift::codegen::ir::{ AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value, }; use cranelift::codegen::isa::CallConv; -use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; +use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable}; use cranelift::prelude::{ EntityRef, InstBuilder, IntCC, JumpTableData, StackSlotData, StackSlotKind, }; +use std::collections::HashMap; use std::ops::RangeInclusive; type BlockSignature = Vec; @@ -477,6 +478,51 @@ where 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. /// This either means a jump into another block or a return. fn finalize_block(&mut self, builder: &mut FunctionBuilder) -> Result<()> { @@ -487,6 +533,7 @@ where Self::generate_br_table, Self::generate_jump, Self::generate_return, + Self::generate_switch, ][..], )?;