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,
|
fmt,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
num::NonZeroU32,
|
num::{NonZeroU32, NonZeroU128},
|
||||||
ops::{ControlFlow, IndexMut},
|
ops::{ControlFlow, IndexMut},
|
||||||
path::Path,
|
path::Path,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
|
@ -109,6 +109,10 @@ declare_intrinsics! {
|
||||||
LifetimeEnd,
|
LifetimeEnd,
|
||||||
#[name = "llvm.memset"]
|
#[name = "llvm.memset"]
|
||||||
Memset,
|
Memset,
|
||||||
|
#[name = "llvm.ctpop"]
|
||||||
|
CtPop,
|
||||||
|
#[name = "llvm.cttz"]
|
||||||
|
Cttz,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! declare_known_functions {
|
macro_rules! declare_known_functions {
|
||||||
|
|
@ -147,6 +151,10 @@ declare_known_functions! {
|
||||||
SysConf,
|
SysConf,
|
||||||
#[name = c"getauxval"]
|
#[name = c"getauxval"]
|
||||||
GetAuxVal,
|
GetAuxVal,
|
||||||
|
#[name = c"pthread_atfork"]
|
||||||
|
PThreadAtFork,
|
||||||
|
#[name = c"pthread_mutex_init"]
|
||||||
|
PThreadMutexInit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -197,15 +205,36 @@ impl TargetLayout {
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
let Some(size) = retval
|
retval.round_size_up_to_multiple_of_align();
|
||||||
.size
|
|
||||||
.checked_next_multiple_of(retval.align.get() as u64)
|
|
||||||
else {
|
|
||||||
panic!("struct too big");
|
|
||||||
};
|
|
||||||
retval.size = size;
|
|
||||||
retval
|
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 {
|
trait TargetType {
|
||||||
|
|
@ -557,12 +586,14 @@ impl<Struct: TargetStruct, Field: TargetType, const INDEX: usize, const OFFSET:
|
||||||
trait TargetStruct: TargetType {
|
trait TargetStruct: TargetType {
|
||||||
const FIELD_LAYOUTS: &[TargetLayout];
|
const FIELD_LAYOUTS: &[TargetLayout];
|
||||||
const FIELD_OFFSETS: &[u64];
|
const FIELD_OFFSETS: &[u64];
|
||||||
|
const MIN_ALIGN: NonZeroU32;
|
||||||
|
const MIN_SIZE: u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! declare_target_struct {
|
macro_rules! declare_target_struct {
|
||||||
(
|
(
|
||||||
#[field_ints = $FieldInts:ident, value = $Value:ident]
|
#[field_ints = $FieldInts:ident, value = $Value:ident]
|
||||||
#[repr(C)]
|
#[repr(C$(, align($min_align:literal))?$(, min_size($min_size:literal))?)]
|
||||||
struct $Struct:ident {
|
struct $Struct:ident {
|
||||||
$($field:ident: $field_ty:ty,)*
|
$($field:ident: $field_ty:ty,)*
|
||||||
}
|
}
|
||||||
|
|
@ -604,9 +635,17 @@ macro_rules! declare_target_struct {
|
||||||
TargetLayout::struct_layout(Self::FIELD_LAYOUTS, &mut offsets);
|
TargetLayout::struct_layout(Self::FIELD_LAYOUTS, &mut offsets);
|
||||||
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 {
|
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;
|
type Value = $Value;
|
||||||
fn llvm_type<'ctx>(parser: &Parser<'ctx>) -> AnyTypeEnum<'ctx> {
|
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()
|
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> {
|
fn get_called_value<'ctx>(instruction: InstructionValue<'ctx>) -> AnyValueEnum<'ctx> {
|
||||||
match instruction.get_opcode() {
|
match instruction.get_opcode() {
|
||||||
InstructionOpcode::Call | InstructionOpcode::Invoke => {}
|
InstructionOpcode::Call | InstructionOpcode::Invoke => {}
|
||||||
|
|
@ -1693,6 +1756,20 @@ impl<'ctx> State<'ctx> {
|
||||||
}
|
}
|
||||||
Ok(())
|
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}"),
|
Intrinsic::Unknown(_) => todo!("{function_value}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1800,6 +1877,49 @@ impl<'ctx> State<'ctx> {
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
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(
|
fn run_call_known_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
function_value: FunctionValue<'ctx>,
|
function_value: FunctionValue<'ctx>,
|
||||||
|
|
@ -1810,6 +1930,8 @@ impl<'ctx> State<'ctx> {
|
||||||
KnownFunction::ClockGetTime => self.run_call_clock_gettime(),
|
KnownFunction::ClockGetTime => self.run_call_clock_gettime(),
|
||||||
KnownFunction::SysConf => self.run_call_sysconf(),
|
KnownFunction::SysConf => self.run_call_sysconf(),
|
||||||
KnownFunction::GetAuxVal => self.run_call_getauxval(),
|
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>> {
|
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)
|
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(
|
fn get_next_states(
|
||||||
mut self: Rc<Self>,
|
mut self: Rc<Self>,
|
||||||
new_states: &mut Vec<Rc<Self>>,
|
new_states: &mut Vec<Rc<Self>>,
|
||||||
|
|
@ -2231,7 +2398,40 @@ impl<'ctx> State<'ctx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InstructionOpcode::SDiv => todo!("{}", this.next_instruction),
|
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 => {
|
InstructionOpcode::SExt | InstructionOpcode::Trunc | InstructionOpcode::ZExt => {
|
||||||
let value = this
|
let value = this
|
||||||
.next_instruction
|
.next_instruction
|
||||||
|
|
@ -2303,7 +2503,10 @@ impl<'ctx> State<'ctx> {
|
||||||
InstructionOpcode::UserOp1 => todo!("{}", this.next_instruction),
|
InstructionOpcode::UserOp1 => todo!("{}", this.next_instruction),
|
||||||
InstructionOpcode::UserOp2 => todo!("{}", this.next_instruction),
|
InstructionOpcode::UserOp2 => todo!("{}", this.next_instruction),
|
||||||
InstructionOpcode::VAArg => 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(
|
fn run_states(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue