diff --git a/src/lib.rs b/src/lib.rs index 72b0398..91d3581 100644 --- a/src/lib.rs +++ b/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(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, function: FunctionValue<'ctx>) -> Result> { @@ -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, + ) -> 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) -> 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) -> 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, new_states: &mut Vec>, @@ -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(