cranelift: Generate Store and Loads in fuzzgen (#4824)

This commit is contained in:
Afonso Bordado
2022-09-07 19:00:19 +01:00
committed by GitHub
parent b8b2fadea8
commit e977f6a79d

View File

@@ -2,7 +2,9 @@ use crate::codegen::ir::{ArgumentExtension, ArgumentPurpose};
use crate::config::Config;
use anyhow::Result;
use arbitrary::{Arbitrary, Unstructured};
use cranelift::codegen::ir::immediates::Offset32;
use cranelift::codegen::ir::instructions::InstructionFormat;
use cranelift::codegen::ir::stackslot::StackSize;
use cranelift::codegen::ir::{types::*, FuncRef, LibCall, UserExternalName, UserFuncName};
use cranelift::codegen::ir::{
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value,
@@ -10,7 +12,7 @@ use cranelift::codegen::ir::{
use cranelift::codegen::isa::CallConv;
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
use cranelift::prelude::{
EntityRef, ExtFuncData, FloatCC, InstBuilder, IntCC, JumpTableData, StackSlotData,
EntityRef, ExtFuncData, FloatCC, InstBuilder, IntCC, JumpTableData, MemFlags, StackSlotData,
StackSlotKind,
};
use std::collections::HashMap;
@@ -89,9 +91,8 @@ fn insert_stack_load(
rets: &'static [Type],
) -> Result<()> {
let typevar = rets[0];
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
let slot_size = builder.func.sized_stack_slots[slot].size;
let type_size = typevar.bytes();
let (slot, slot_size) = fgen.stack_slot_with_size(builder, type_size)?;
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
let val = builder.ins().stack_load(typevar, slot, offset);
@@ -109,9 +110,8 @@ fn insert_stack_store(
_rets: &'static [Type],
) -> Result<()> {
let typevar = args[0];
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
let slot_size = builder.func.sized_stack_slots[slot].size;
let type_size = typevar.bytes();
let (slot, slot_size) = fgen.stack_slot_with_size(builder, type_size)?;
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
let arg0 = fgen.get_variable_of_type(typevar)?;
@@ -144,6 +144,7 @@ fn insert_cmp(
let var = fgen.get_variable_of_type(rets[0])?;
builder.def_var(var, res);
Ok(())
}
@@ -161,6 +162,41 @@ fn insert_const(
Ok(())
}
fn insert_load_store(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
opcode: Opcode,
args: &'static [Type],
rets: &'static [Type],
) -> Result<()> {
let ctrl_type = *rets.first().or(args.first()).unwrap();
let type_size = ctrl_type.bytes();
let (address, offset) = fgen.generate_load_store_address(builder, type_size)?;
// TODO: More advanced MemFlags
let flags = MemFlags::new();
// The variable being loaded or stored into
let var = fgen.get_variable_of_type(ctrl_type)?;
if opcode.can_store() {
let val = builder.use_var(var);
builder
.ins()
.Store(opcode, ctrl_type, flags, offset, val, address);
} else {
let (inst, dfg) = builder
.ins()
.Load(opcode, ctrl_type, flags, offset, address);
let new_val = dfg.first_result(inst);
builder.def_var(var, new_val);
}
Ok(())
}
type OpcodeInserter = fn(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
@@ -448,6 +484,49 @@ const OPCODE_SIGNATURES: &'static [(
(Opcode::StackLoad, &[], &[I32], insert_stack_load),
(Opcode::StackLoad, &[], &[I64], insert_stack_load),
(Opcode::StackLoad, &[], &[I128], insert_stack_load),
// Loads
(Opcode::Load, &[], &[I8], insert_load_store),
(Opcode::Load, &[], &[I16], insert_load_store),
(Opcode::Load, &[], &[I32], insert_load_store),
(Opcode::Load, &[], &[I64], insert_load_store),
(Opcode::Load, &[], &[I128], insert_load_store),
(Opcode::Load, &[], &[F32], insert_load_store),
(Opcode::Load, &[], &[F64], insert_load_store),
// Special Loads
(Opcode::Uload8, &[], &[I16], insert_load_store),
(Opcode::Uload8, &[], &[I32], insert_load_store),
(Opcode::Uload8, &[], &[I64], insert_load_store),
(Opcode::Uload16, &[], &[I32], insert_load_store),
(Opcode::Uload16, &[], &[I64], insert_load_store),
(Opcode::Uload32, &[], &[I64], insert_load_store),
(Opcode::Sload8, &[], &[I16], insert_load_store),
(Opcode::Sload8, &[], &[I32], insert_load_store),
(Opcode::Sload8, &[], &[I64], insert_load_store),
(Opcode::Sload16, &[], &[I32], insert_load_store),
(Opcode::Sload16, &[], &[I64], insert_load_store),
(Opcode::Sload32, &[], &[I64], insert_load_store),
// TODO: Unimplemented in the interpreter
// Opcode::Uload8x8
// Opcode::Sload8x8
// Opcode::Uload16x4
// Opcode::Sload16x4
// Opcode::Uload32x2
// Opcode::Sload32x2
// Stores
(Opcode::Store, &[I8], &[], insert_load_store),
(Opcode::Store, &[I16], &[], insert_load_store),
(Opcode::Store, &[I32], &[], insert_load_store),
(Opcode::Store, &[I64], &[], insert_load_store),
(Opcode::Store, &[I128], &[], insert_load_store),
(Opcode::Store, &[F32], &[], insert_load_store),
(Opcode::Store, &[F64], &[], insert_load_store),
// Special Stores
(Opcode::Istore8, &[I16], &[], insert_load_store),
(Opcode::Istore8, &[I32], &[], insert_load_store),
(Opcode::Istore8, &[I64], &[], insert_load_store),
(Opcode::Istore16, &[I32], &[], insert_load_store),
(Opcode::Istore16, &[I64], &[], insert_load_store),
(Opcode::Istore32, &[I64], &[], insert_load_store),
// Integer Consts
(Opcode::Iconst, &[], &[I8], insert_const),
(Opcode::Iconst, &[], &[I16], insert_const),
@@ -567,17 +646,57 @@ where
}
/// Finds a stack slot with size of at least n bytes
fn stack_slot_with_size(&mut self, builder: &mut FunctionBuilder, n: u32) -> Result<StackSlot> {
fn stack_slot_with_size(
&mut self,
builder: &mut FunctionBuilder,
n: u32,
) -> Result<(StackSlot, StackSize)> {
let opts: Vec<_> = self
.static_stack_slots
.iter()
.filter(|ss| builder.func.sized_stack_slots[**ss].size >= n)
.map(|ss| *ss)
.map(|ss| (*ss, builder.func.sized_stack_slots[*ss].size))
.filter(|(_, size)| *size >= n)
.collect();
Ok(*self.u.choose(&opts[..])?)
}
/// Generates an address that should allow for a store or a load.
///
/// Addresses aren't generated like other values. They are never stored in variables so that
/// we don't run the risk of returning them from a function, which would make the fuzzer
/// complain since they are different from the interpreter to the backend.
///
/// The address is not guaranteed to be valid, but there's a chance that it is.
///
/// `min_size`: Controls the amount of space that the address should have.This is not
/// guaranteed to be respected
fn generate_load_store_address(
&mut self,
builder: &mut FunctionBuilder,
min_size: u32,
) -> Result<(Value, Offset32)> {
// TODO: Currently our only source of addresses is stack_addr, but we should
// add heap_addr, global_value, symbol_value eventually
let (addr, available_size) = {
let (ss, slot_size) = self.stack_slot_with_size(builder, min_size)?;
let max_offset = slot_size.saturating_sub(min_size);
let offset = self.u.int_in_range(0..=max_offset)? as i32;
let base_addr = builder.ins().stack_addr(I64, ss, offset);
let available_size = (slot_size as i32).saturating_sub(offset);
(base_addr, available_size)
};
// TODO: Insert a bunch of amode opcodes here to modify the address!
// Now that we have an address and a size, we just choose a random offset to return to the
// caller. Try to preserve min_size bytes.
let max_offset = available_size.saturating_sub(min_size as i32);
let offset = self.u.int_in_range(0..=max_offset)? as i32;
Ok((addr, offset.into()))
}
/// Creates a new var
fn create_var(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Variable> {
let id = self.vars.len();