use rustc_hash::FxHashMap as HashMap;
use std::sync::OnceLock;
use super::binop::IntBinop;
use super::mnemonic::{Args, MnemonicKind};
use crate::{
types::{TypeId, TypeManager},
value::{BodyView, InstructionId, LocalValueId, QCodeView, ValueId, ValueRef},
};
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IntrinsicId(usize);
impl IntrinsicId {
pub fn from_name(name: &str) -> Option<Self> {
registry().by_name.get(name).copied()
}
pub fn name(self) -> &'static str {
self.desc().name()
}
pub fn desc(self) -> &'static dyn Intrinsic {
registry().descs[self.0]
}
}
impl serde::Serialize for IntrinsicId {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.name())
}
}
impl<'de> serde::Deserialize<'de> for IntrinsicId {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let name = <std::borrow::Cow<'de, str>>::deserialize(d)?;
IntrinsicId::from_name(&name)
.ok_or_else(|| serde::de::Error::custom(format!("unknown intrinsic `{name}`")))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RootOp {
IntBinop(IntBinop),
}
pub trait Intrinsic: Sync {
fn name(&self) -> &'static str;
fn arity(&self) -> usize;
fn result_type(&self, types: &TypeManager, args: &[TypeId]) -> TypeId;
fn eval(&self, args: &[(u128, usize)], out_size: usize) -> Option<u128>;
fn root_op(&self) -> Option<RootOp> {
None
}
fn recognize(&self, _view: BodyView<'_, '_>, _at: InstructionId) -> Option<Vec<ValueId>> {
None
}
fn simplify(
&self,
_view: BodyView<'_, '_>,
_id: IntrinsicId,
_out_size: usize,
_args: &[ValueId],
) -> Option<Simplified> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Simplified {
Value(ValueId),
Expression(super::Mnemonic),
}
pub struct IntrinsicRegistration(pub &'static dyn Intrinsic);
inventory::collect!(IntrinsicRegistration);
struct Registry {
descs: Vec<&'static dyn Intrinsic>,
by_name: HashMap<&'static str, IntrinsicId>,
by_root: HashMap<RootOp, Vec<IntrinsicId>>,
}
fn registry() -> &'static Registry {
static REG: OnceLock<Registry> = OnceLock::new();
REG.get_or_init(|| {
let mut descs: Vec<&'static dyn Intrinsic> = inventory::iter::<IntrinsicRegistration>()
.map(|r| r.0)
.collect();
descs.sort_by_key(|d| d.name());
let mut by_name = HashMap::default();
let mut by_root: HashMap<RootOp, Vec<IntrinsicId>> = HashMap::default();
for (idx, desc) in descs.iter().enumerate() {
let id = IntrinsicId(idx);
let prev = by_name.insert(desc.name(), id);
assert!(
prev.is_none(),
"duplicate intrinsic registration: {}",
desc.name()
);
if let Some(root) = desc.root_op() {
by_root.entry(root).or_default().push(id);
}
}
Registry {
descs,
by_name,
by_root,
}
})
}
pub fn recognizers_for(root: RootOp) -> &'static [IntrinsicId] {
registry()
.by_root
.get(&root)
.map(Vec::as_slice)
.unwrap_or(&[])
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct IntrinsicApp {
pub id: IntrinsicId,
pub args: Vec<LocalValueId>,
}
impl MnemonicKind for IntrinsicApp {
fn opcode(&self) -> &'static str {
self.id.name()
}
fn args(&self) -> Args {
SmallVec::from_vec(self.args.clone())
}
}
#[macro_export]
macro_rules! register_intrinsic {
($def:expr $(,)?) => {
inventory::submit! {
$crate::value::insn::IntrinsicRegistration(&$def)
}
};
}
pub(crate) fn mask_for(bytes: usize) -> u128 {
let bits = (bytes * 8).min(128);
if bits == 0 {
0
} else if bits == 128 {
u128::MAX
} else {
(1u128 << bits) - 1
}
}
pub(crate) fn const_u64<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
v: ValueId,
) -> Option<u64> {
match ValueRef::from_view(view, v) {
ValueRef::Literal(lit) => {
let ValueId::Literal(id) = v else {
return None;
};
if view.shared().values.literals[id].symbolic.is_some() {
return None;
}
Some(lit.value())
}
_ => None,
}
}
pub(crate) fn as_int_binop<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
v: ValueId,
want: IntBinop,
) -> Option<(ValueId, ValueId)> {
use super::{Binary, Binop, Mnemonic};
let ValueId::Instruction(id) = v else {
return None;
};
match view.instruction(id).mnemonic() {
Mnemonic::Binop(Binary {
lhs,
rhs,
op: Binop::Int(op),
}) if *op == want => Some((lhs.qualify(id.func), rhs.qualify(id.func))),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intrinsic_id_serializes_by_name() {
let config = bincode::config::standard();
let rol = IntrinsicId::from_name("rol").unwrap();
let bytes = bincode::serde::encode_to_vec(rol, config).unwrap();
let name: String = bincode::serde::decode_from_slice(&bytes, config).unwrap().0;
assert_eq!(name, "rol");
let (back, _): (IntrinsicId, usize) =
bincode::serde::decode_from_slice(&bytes, config).unwrap();
assert_eq!(back, rol);
let bad = bincode::serde::encode_to_vec("nope", config).unwrap();
assert!(bincode::serde::decode_from_slice::<IntrinsicId, _>(&bad, config).is_err());
}
}