implement more instructions and functions
This commit is contained in:
parent
f64b5d7120
commit
2fa256098d
1 changed files with 215 additions and 12 deletions
227
src/lib.rs
227
src/lib.rs
|
|
@ -30,7 +30,7 @@ use std::{
|
|||
fmt,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
num::{NonZeroU32, NonZeroU128},
|
||||
ops::{ControlFlow, IndexMut},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
|
|
@ -109,6 +109,10 @@ declare_intrinsics! {
|
|||
LifetimeEnd,
|
||||
#[name = "llvm.memset"]
|
||||
Memset,
|
||||
#[name = "llvm.ctpop"]
|
||||
CtPop,
|
||||
#[name = "llvm.cttz"]
|
||||
Cttz,
|
||||
}
|
||||
|
||||
macro_rules! declare_known_functions {
|
||||
|
|
@ -147,6 +151,10 @@ declare_known_functions! {
|
|||
SysConf,
|
||||
#[name = c"getauxval"]
|
||||
GetAuxVal,
|
||||
#[name = c"pthread_atfork"]
|
||||
PThreadAtFork,
|
||||
#[name = c"pthread_mutex_init"]
|
||||
PThreadMutexInit,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
|
|
@ -197,15 +205,36 @@ impl TargetLayout {
|
|||
}
|
||||
i += 1;
|
||||
}
|
||||
let Some(size) = retval
|
||||
.size
|
||||
.checked_next_multiple_of(retval.align.get() as u64)
|
||||
else {
|
||||
panic!("struct too big");
|
||||
};
|
||||
retval.size = size;
|
||||
retval.round_size_up_to_multiple_of_align();
|
||||
retval
|
||||
}
|
||||
#[track_caller]
|
||||
const fn round_size_up_to_multiple_of_align(&mut self) {
|
||||
let Some(size) = self.size.checked_next_multiple_of(self.align.get() as u64) else {
|
||||
panic!("type too big");
|
||||
};
|
||||
self.size = size;
|
||||
}
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
const fn with_min_align(mut self, min_align: NonZeroU32) -> Self {
|
||||
if self.align.get() >= min_align.get() {
|
||||
return self;
|
||||
}
|
||||
self.align = min_align;
|
||||
self.round_size_up_to_multiple_of_align();
|
||||
self
|
||||
}
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
const fn with_min_size(mut self, min_size: u64) -> Self {
|
||||
if self.size >= min_size {
|
||||
return self;
|
||||
}
|
||||
self.size = min_size;
|
||||
self.round_size_up_to_multiple_of_align();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
trait TargetType {
|
||||
|
|
@ -557,12 +586,14 @@ impl<Struct: TargetStruct, Field: TargetType, const INDEX: usize, const OFFSET:
|
|||
trait TargetStruct: TargetType {
|
||||
const FIELD_LAYOUTS: &[TargetLayout];
|
||||
const FIELD_OFFSETS: &[u64];
|
||||
const MIN_ALIGN: NonZeroU32;
|
||||
const MIN_SIZE: u64;
|
||||
}
|
||||
|
||||
macro_rules! declare_target_struct {
|
||||
(
|
||||
#[field_ints = $FieldInts:ident, value = $Value:ident]
|
||||
#[repr(C)]
|
||||
#[repr(C$(, align($min_align:literal))?$(, min_size($min_size:literal))?)]
|
||||
struct $Struct:ident {
|
||||
$($field:ident: $field_ty:ty,)*
|
||||
}
|
||||
|
|
@ -604,9 +635,17 @@ macro_rules! declare_target_struct {
|
|||
TargetLayout::struct_layout(Self::FIELD_LAYOUTS, &mut offsets);
|
||||
offsets
|
||||
};
|
||||
const MIN_ALIGN: NonZeroU32 = {
|
||||
NonZeroU32::new(($($min_align,)? 1,).0).unwrap()
|
||||
};
|
||||
const MIN_SIZE: u64 = {
|
||||
($($min_size,)? 0,).0
|
||||
};
|
||||
}
|
||||
impl TargetType for $Struct {
|
||||
const LAYOUT: TargetLayout = TargetLayout::struct_layout(Self::FIELD_LAYOUTS, &mut []);
|
||||
const LAYOUT: TargetLayout = TargetLayout::struct_layout(Self::FIELD_LAYOUTS, &mut [])
|
||||
.with_min_align(Self::MIN_ALIGN)
|
||||
.with_min_size(Self::MIN_SIZE);
|
||||
type Value = $Value;
|
||||
fn llvm_type<'ctx>(parser: &Parser<'ctx>) -> AnyTypeEnum<'ctx> {
|
||||
parser.context.struct_type(&[$(<$field_ty as TargetType>::llvm_basic_type(parser).expect("invalid field type")),*], false).into()
|
||||
|
|
@ -703,6 +742,30 @@ declare_target_struct! {
|
|||
}
|
||||
}
|
||||
|
||||
declare_target_struct! {
|
||||
#[field_ints = TargetPThreadListTFieldInts, value = TargetPThreadListTValue]
|
||||
#[repr(C, align(8), min_size(24))]
|
||||
struct TargetPThreadListT {
|
||||
prev: *mut TargetPThreadListT,
|
||||
next: *mut TargetPThreadListT,
|
||||
}
|
||||
}
|
||||
|
||||
declare_target_struct! {
|
||||
#[field_ints = TargetPThreadMutexTFieldInts, value = TargetPThreadMutexTValue]
|
||||
#[repr(C, align(8), min_size(24))]
|
||||
struct TargetPThreadMutexT {
|
||||
lock: i32,
|
||||
count: u32,
|
||||
owner: i32,
|
||||
nusers: u32,
|
||||
kind: i32,
|
||||
spins: i16,
|
||||
elision: i16,
|
||||
list: TargetPThreadListT,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_called_value<'ctx>(instruction: InstructionValue<'ctx>) -> AnyValueEnum<'ctx> {
|
||||
match instruction.get_opcode() {
|
||||
InstructionOpcode::Call | InstructionOpcode::Invoke => {}
|
||||
|
|
@ -1693,6 +1756,20 @@ impl<'ctx> State<'ctx> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
Intrinsic::CtPop => {
|
||||
self.int_un_op_unsigned(|_, src| Ok(src.count_ones() as u128))?;
|
||||
Ok(())
|
||||
}
|
||||
Intrinsic::Cttz => {
|
||||
self.int_un_op_unsigned(|bit_width, src| {
|
||||
Ok(if let Some(src) = NonZeroU128::new(src) {
|
||||
src.trailing_zeros()
|
||||
} else {
|
||||
bit_width
|
||||
} as u128)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
Intrinsic::Unknown(_) => todo!("{function_value}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -1800,6 +1877,49 @@ impl<'ctx> State<'ctx> {
|
|||
)?;
|
||||
Ok(())
|
||||
}
|
||||
fn run_call_pthread_atfork(&mut self) -> Result<()> {
|
||||
// we don't implement forking, so ignore the passed-in function pointers and just return 0
|
||||
self.insert_local_value(
|
||||
self.next_instruction.as_any_value_enum(),
|
||||
Value::ConstantInt(0 as u128),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
fn run_call_pthread_mutex_init(&mut self) -> Result<()> {
|
||||
let [mutex, mutexattr] = self.get_call_arg_values()?;
|
||||
let Value::Pointer(mutex) = mutex else {
|
||||
bail!("pthread_mutex_init `mutex` argument must be a pointer");
|
||||
};
|
||||
let Value::Pointer(mutexattr) = mutexattr else {
|
||||
bail!("pthread_mutex_init `mutexattr` argument must be a pointer");
|
||||
};
|
||||
if mutexattr.base.address() != Some(0) {
|
||||
todo!("pthread_mutex_init with non-null `mutexattr`");
|
||||
};
|
||||
let null_ptr = Pointer {
|
||||
base: self.global.null_ptr_base.clone(),
|
||||
offset: 0,
|
||||
};
|
||||
let init = TargetPThreadMutexTValue {
|
||||
lock: 0,
|
||||
count: 0,
|
||||
owner: 0,
|
||||
nusers: 0,
|
||||
kind: 0,
|
||||
spins: 0,
|
||||
elision: 0,
|
||||
list: TargetPThreadListTValue {
|
||||
prev: null_ptr.clone(),
|
||||
next: null_ptr,
|
||||
},
|
||||
};
|
||||
TargetPThreadMutexT::store(self, mutex, init)?;
|
||||
self.insert_local_value(
|
||||
self.next_instruction.as_any_value_enum(),
|
||||
Value::ConstantInt(0),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
fn run_call_known_function(
|
||||
&mut self,
|
||||
function_value: FunctionValue<'ctx>,
|
||||
|
|
@ -1810,6 +1930,8 @@ impl<'ctx> State<'ctx> {
|
|||
KnownFunction::ClockGetTime => self.run_call_clock_gettime(),
|
||||
KnownFunction::SysConf => self.run_call_sysconf(),
|
||||
KnownFunction::GetAuxVal => self.run_call_getauxval(),
|
||||
KnownFunction::PThreadAtFork => self.run_call_pthread_atfork(),
|
||||
KnownFunction::PThreadMutexInit => self.run_call_pthread_mutex_init(),
|
||||
}
|
||||
}
|
||||
fn run_normal_call(mut self: Rc<Self>, function: FunctionValue<'ctx>) -> Result<Rc<Self>> {
|
||||
|
|
@ -1920,6 +2042,51 @@ impl<'ctx> State<'ctx> {
|
|||
Ok(op(bit_width, lhs, rhs)? as u128)
|
||||
})
|
||||
}
|
||||
fn int_un_op_unmasked_inputs(
|
||||
&mut self,
|
||||
op: impl FnOnce(u32, u128) -> Result<u128>,
|
||||
) -> Result<()> {
|
||||
let src = self
|
||||
.next_instruction
|
||||
.get_operand(0)
|
||||
.expect("known to have src operand")
|
||||
.unwrap_value();
|
||||
let bit_width = src.get_type().into_int_type().get_bit_width();
|
||||
let src = self.get_value(src)?;
|
||||
let Value::ConstantInt(src) = src else {
|
||||
todo!("{src:?}");
|
||||
};
|
||||
let mut retval = op(bit_width, src)?;
|
||||
let dest_bit_width = self
|
||||
.next_instruction
|
||||
.as_any_value_enum()
|
||||
.into_int_value()
|
||||
.get_type()
|
||||
.get_bit_width();
|
||||
retval &= 1u128.unbounded_shl(dest_bit_width).wrapping_sub(1);
|
||||
self.insert_local_value(
|
||||
self.next_instruction.as_any_value_enum(),
|
||||
Value::ConstantInt(retval),
|
||||
)
|
||||
}
|
||||
fn int_un_op_unsigned(&mut self, op: impl FnOnce(u32, u128) -> Result<u128>) -> Result<()> {
|
||||
self.int_un_op_unmasked_inputs(|bit_width, src| {
|
||||
let shift = u128::BITS
|
||||
.checked_sub(bit_width)
|
||||
.expect("bit width too big");
|
||||
let src = (src << shift) >> shift;
|
||||
op(bit_width, src)
|
||||
})
|
||||
}
|
||||
fn int_un_op_signed(&mut self, op: impl FnOnce(u32, i128) -> Result<i128>) -> Result<()> {
|
||||
self.int_un_op_unmasked_inputs(|bit_width, src| {
|
||||
let shift = i128::BITS
|
||||
.checked_sub(bit_width)
|
||||
.expect("bit width too big");
|
||||
let src = ((src as i128) << shift) >> shift;
|
||||
Ok(op(bit_width, src)? as u128)
|
||||
})
|
||||
}
|
||||
fn get_next_states(
|
||||
mut self: Rc<Self>,
|
||||
new_states: &mut Vec<Rc<Self>>,
|
||||
|
|
@ -2231,7 +2398,40 @@ impl<'ctx> State<'ctx> {
|
|||
}
|
||||
}
|
||||
InstructionOpcode::SDiv => todo!("{}", this.next_instruction),
|
||||
InstructionOpcode::Select => todo!("{}", this.next_instruction),
|
||||
InstructionOpcode::Select => {
|
||||
let cond = this
|
||||
.next_instruction
|
||||
.get_operand(0)
|
||||
.expect("known to have condition operand")
|
||||
.unwrap_value();
|
||||
let cond = this.get_value(cond)?;
|
||||
let true_value = this
|
||||
.next_instruction
|
||||
.get_operand(1)
|
||||
.expect("known to have true_value operand")
|
||||
.unwrap_value();
|
||||
let true_value = this.get_value(true_value)?;
|
||||
let false_value = this
|
||||
.next_instruction
|
||||
.get_operand(2)
|
||||
.expect("known to have false_value operand")
|
||||
.unwrap_value();
|
||||
let false_value = this.get_value(false_value)?;
|
||||
let value = if let Value::ConstantInt(cond) = cond {
|
||||
if cond & 1 != 0 {
|
||||
true_value
|
||||
} else {
|
||||
false_value
|
||||
}
|
||||
} else {
|
||||
todo!(
|
||||
"unimplemented select condition: {cond:?}\n{}",
|
||||
this.next_instruction
|
||||
)
|
||||
};
|
||||
this.insert_local_value(this.next_instruction.as_any_value_enum(), value)?;
|
||||
return_after_non_term!();
|
||||
}
|
||||
InstructionOpcode::SExt | InstructionOpcode::Trunc | InstructionOpcode::ZExt => {
|
||||
let value = this
|
||||
.next_instruction
|
||||
|
|
@ -2303,7 +2503,10 @@ impl<'ctx> State<'ctx> {
|
|||
InstructionOpcode::UserOp1 => todo!("{}", this.next_instruction),
|
||||
InstructionOpcode::UserOp2 => todo!("{}", this.next_instruction),
|
||||
InstructionOpcode::VAArg => todo!("{}", this.next_instruction),
|
||||
InstructionOpcode::Xor => todo!("{}", this.next_instruction),
|
||||
InstructionOpcode::Xor => {
|
||||
this.int_bin_op_unsigned(|_, lhs, rhs| Ok(lhs ^ rhs))?;
|
||||
return_after_non_term!();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn run_states(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue