Skip to main content

pcode_types/expression/
ids.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Integer identifier for a local p-code variable.  Strings are never stored;
5/// uniqueness is guaranteed by allocation order within each macro/constructor scope.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct LocalVarId(
8    /// Allocation index within the scope this variable belongs to. Unique
9    /// within one decoded instruction, since each spliced body — a macro
10    /// expansion, a sub-table, a delay slot — is given its own base offset.
11    pub u32,
12);
13
14/// Parse-time interner: maps source name strings to [`LocalVarId`]s.
15/// Only alive during parsing — discarded once the producer has built its macro.
16pub struct LocalVarInterner<'str> {
17    map: HashMap<&'str str, LocalVarId>,
18    count: u32,
19}
20
21impl<'str> Default for LocalVarInterner<'str> {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl<'str> LocalVarInterner<'str> {
28    /// Creates an empty interner.
29    pub fn new() -> Self {
30        Self {
31            map: HashMap::new(),
32            count: 0,
33        }
34    }
35
36    /// Returns the identifier for `name`, allocating one on its first use.
37    pub fn intern(&mut self, name: &'str str) -> LocalVarId {
38        if let Some(&id) = self.map.get(name) {
39            return id;
40        }
41        let id = LocalVarId(self.count);
42        self.count += 1;
43        self.map.insert(name, id);
44        id
45    }
46
47    /// Returns the number of distinct names interned so far.
48    pub fn count(&self) -> u32 {
49        self.count
50    }
51
52    /// Returns an existing identifier without allocating one.
53    pub fn get(&self, name: &str) -> Option<LocalVarId> {
54        self.map.get(name).copied()
55    }
56}
57
58/// The built-in SLEIGH functions — a spec may call these without declaring
59/// them, unlike `define pcodeop` names.
60///
61/// They appear as [`ExpressionTy::FunctionCall`](super::ExpressionTy::FunctionCall)
62/// and every one of them has a direct p-code meaning, so a consumer lowering to
63/// its own IR is expected to implement them rather than treat them as opaque
64/// calls. The two exceptions are noted on their variants.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub enum Builtin {
67    /// `carry(a, b)` — would the unsigned addition `a + b` carry out of the
68    /// top bit? One byte.
69    Carry,
70
71    /// `scarry(a, b)` — would the signed addition `a + b` overflow? One byte.
72    Scarry,
73
74    /// `sborrow(a, b)` — would the signed subtraction `a - b` overflow? One
75    /// byte.
76    Sborrow,
77
78    /// `nan(x)` — is the floating-point value `x` a NaN? One byte.
79    Nan,
80
81    /// `abs(x)` — floating-point absolute value.
82    Abs,
83
84    /// `sqrt(x)` — floating-point square root.
85    Sqrt,
86
87    /// `floor(x)` — round towards negative infinity, staying floating-point.
88    Floor,
89
90    /// `ceil(x)` — round towards positive infinity, staying floating-point.
91    Ceil,
92
93    /// `round(x)` — round to nearest, staying floating-point.
94    Round,
95
96    /// `int2float(x)` — signed integer to floating-point.
97    Int2Float,
98
99    /// `float2float(x)` — change floating-point width, preserving the value.
100    Float2Float,
101
102    /// `trunc(x)` — floating-point to signed integer, truncating towards zero.
103    Trunc,
104
105    /// `zext(x)` — widen, filling with zeroes. The result width comes from the
106    /// context the call sits in, not from the argument.
107    Zext,
108
109    /// `sext(x)` — widen, filling with copies of the sign bit.
110    Sext,
111
112    /// `popcount(x)` — number of set bits.
113    Popcount,
114
115    /// `lzcount(x)` — number of leading zero bits.
116    Lzcount,
117    /// Constant-pool reference. Has no p-code expansion: a consumer must
118    /// resolve it against the binary's constant pool.
119    Cpool,
120    /// Object allocation, the companion of [`Builtin::Cpool`] in
121    /// bytecode-oriented specifications.
122    NewObject,
123}
124
125impl Builtin {
126    /// Every builtin, in declaration order.
127    ///
128    /// The symbol table is seeded from this, so a variant added here is
129    /// automatically callable from a specification.
130    pub const ALL: &'static [Builtin] = &[
131        Builtin::Carry,
132        Builtin::Scarry,
133        Builtin::Sborrow,
134        Builtin::Nan,
135        Builtin::Abs,
136        Builtin::Sqrt,
137        Builtin::Floor,
138        Builtin::Ceil,
139        Builtin::Round,
140        Builtin::Int2Float,
141        Builtin::Float2Float,
142        Builtin::Trunc,
143        Builtin::Zext,
144        Builtin::Sext,
145        Builtin::Popcount,
146        Builtin::Lzcount,
147        Builtin::Cpool,
148        Builtin::NewObject,
149    ];
150
151    /// The name a specification calls this builtin by.
152    pub fn as_str(self) -> &'static str {
153        match self {
154            Builtin::Carry => "carry",
155            Builtin::Scarry => "scarry",
156            Builtin::Sborrow => "sborrow",
157            Builtin::Nan => "nan",
158            Builtin::Abs => "abs",
159            Builtin::Sqrt => "sqrt",
160            Builtin::Floor => "floor",
161            Builtin::Ceil => "ceil",
162            Builtin::Round => "round",
163            Builtin::Int2Float => "int2float",
164            Builtin::Float2Float => "float2float",
165            Builtin::Trunc => "trunc",
166            Builtin::Zext => "zext",
167            Builtin::Sext => "sext",
168            Builtin::Popcount => "popcount",
169            Builtin::Lzcount => "lzcount",
170            Builtin::Cpool => "cpool",
171            Builtin::NewObject => "newobject",
172        }
173    }
174
175    /// Returns the builtin with the SLEIGH source spelling `s`.
176    pub fn from_name(s: &str) -> Option<Self> {
177        Self::ALL.iter().copied().find(|b| b.as_str() == s)
178    }
179}
180
181#[cfg(test)]
182mod builtin_tests {
183    use super::Builtin;
184
185    /// `ALL` seeds the symbol table, so a builtin missing from it is not
186    /// callable from a specification however well `as_str` knows it.
187    #[test]
188    fn every_builtin_round_trips_through_all() {
189        for &builtin in Builtin::ALL {
190            assert_eq!(Builtin::from_name(builtin.as_str()), Some(builtin));
191        }
192        assert_eq!(Builtin::from_name("not_a_builtin"), None);
193        assert_eq!(Builtin::from_name("epsilon"), None);
194        assert_eq!(Builtin::from_name("float2int"), None);
195    }
196}