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}