implement more instructions and functions

This commit is contained in:
Jacob Lifshay 2026-04-03 19:07:28 -07:00
parent f64b5d7120
commit 2fa256098d
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ

View file

@ -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(