tempo_precompiles_macros/
packing.rs1use alloy::primitives::U256;
7use proc_macro2::TokenStream;
8use quote::{format_ident, quote};
9use syn::{Ident, Type};
10
11use crate::{FieldInfo, FieldKind};
12
13pub(crate) struct PackingConstants(String);
15
16impl PackingConstants {
17 pub(crate) fn new(name: &Ident) -> Self {
19 Self(const_name(name))
20 }
21
22 pub(crate) fn slot(&self) -> Ident {
24 format_ident!("{}", &self.0)
25 }
26
27 pub(crate) fn location(&self) -> Ident {
29 let span = proc_macro2::Span::call_site();
30 Ident::new(&format!("{}_LOC", self.0), span)
31 }
32
33 pub(crate) fn offset(&self) -> Ident {
35 let span = proc_macro2::Span::call_site();
36 Ident::new(&format!("{}_OFFSET", self.0), span)
37 }
38
39 pub(crate) fn into_tuple(self) -> (Ident, Ident) {
41 (self.slot(), self.offset())
42 }
43}
44
45pub(crate) fn const_name(name: &Ident) -> String {
47 name.to_string().to_uppercase()
48}
49
50#[derive(Debug, Clone)]
52pub(crate) enum SlotAssignment {
53 Manual(U256),
55 Auto {
57 base_slot: U256,
59 },
60}
61
62impl SlotAssignment {
63 pub(crate) fn ref_slot(&self) -> &U256 {
64 match self {
65 Self::Manual(slot) => slot,
66 Self::Auto { base_slot } => base_slot,
67 }
68 }
69}
70
71#[derive(Debug)]
73pub(crate) struct LayoutField<'a> {
74 pub name: &'a Ident,
76 pub ty: &'a Type,
78 pub kind: FieldKind<'a>,
80 pub assigned_slot: SlotAssignment,
82}
83
84pub(crate) fn allocate_slots(fields: &[FieldInfo]) -> syn::Result<Vec<LayoutField<'_>>> {
94 let mut result = Vec::with_capacity(fields.len());
95 let mut current_base_slot = U256::ZERO;
96
97 for field in fields.iter() {
98 let kind = classify_field_type(&field.ty)?;
99
100 let assigned_slot = if let Some(explicit) = field.slot {
102 SlotAssignment::Manual(explicit)
103 } else if let Some(new_base) = field.base_slot {
104 current_base_slot = new_base;
106 SlotAssignment::Auto {
107 base_slot: new_base,
108 }
109 } else {
110 SlotAssignment::Auto {
111 base_slot: current_base_slot,
112 }
113 };
114
115 result.push(LayoutField {
116 name: &field.name,
117 ty: &field.ty,
118 kind,
119 assigned_slot,
120 });
121 }
122
123 Ok(result)
124}
125
126pub(crate) fn gen_constants_from_ir(fields: &[LayoutField<'_>], gen_location: bool) -> TokenStream {
132 let mut constants = TokenStream::new();
133 let mut current_base_slot: Option<&LayoutField<'_>> = None;
134
135 for field in fields {
136 let ty = field.ty;
137 let consts = PackingConstants::new(field.name);
138 let (loc_const, (slot_const, offset_const)) = (consts.location(), consts.into_tuple());
139
140 let bytes_expr = quote! { <#ty as crate::storage::StorableType>::BYTES };
142
143 let (slot_expr, offset_expr) = match &field.assigned_slot {
145 SlotAssignment::Manual(manual_slot) => {
147 let hex_value = format!("{manual_slot}_U256");
148 let slot_lit = syn::LitInt::new(&hex_value, proc_macro2::Span::call_site());
149 let slot_expr = quote! { ::alloy::primitives::uint!(#slot_lit) };
150 (slot_expr, quote! { 0 })
151 }
152 SlotAssignment::Auto { base_slot, .. } => {
154 let output = if let Some(current_base) = current_base_slot {
155 if current_base.assigned_slot.ref_slot() == field.assigned_slot.ref_slot() {
157 let (prev_slot, prev_offset) =
158 PackingConstants::new(current_base.name).into_tuple();
159 gen_slot_packing_logic(
160 current_base.ty,
161 field.ty,
162 quote! { #prev_slot },
163 quote! { #prev_offset },
164 )
165 }
166 else {
168 let limbs = *base_slot.as_limbs();
169 (
170 quote! { ::alloy::primitives::U256::from_limbs([#(#limbs),*]) },
171 quote! { 0 },
172 )
173 }
174 }
175 else {
177 (quote! { ::alloy::primitives::U256::ZERO }, quote! { 0 })
178 };
179 current_base_slot = Some(field);
181 output
182 }
183 };
184
185 constants.extend(quote! {
187 pub const #slot_const: ::alloy::primitives::U256 = #slot_expr;
188 pub const #offset_const: usize = #offset_expr;
189 });
190
191 if gen_location {
195 constants.extend(quote! {
196 pub const #loc_const: crate::storage::packing::FieldLocation =
197 crate::storage::packing::FieldLocation::new(#slot_const.as_limbs()[0] as usize, #offset_const, #bytes_expr);
198 });
199 }
200
201 #[cfg(debug_assertions)]
203 {
204 let bytes_const = format_ident!("{slot_const}_BYTES");
205 constants.extend(quote! { pub const #bytes_const: usize = #bytes_expr; });
206 }
207 }
208
209 constants
210}
211
212pub(crate) fn classify_field_type(ty: &Type) -> syn::Result<FieldKind<'_>> {
218 use crate::utils::extract_mapping_types;
219
220 if let Some((key_ty, value_ty)) = extract_mapping_types(ty) {
222 return Ok(FieldKind::Mapping {
223 key: key_ty,
224 value: value_ty,
225 });
226 }
227
228 Ok(FieldKind::Direct(ty))
230}
231
232pub(crate) fn get_neighbor_slot_refs<T, F>(
236 idx: usize,
237 fields: &[T],
238 packing: &Ident,
239 get_name: F,
240) -> (Option<TokenStream>, Option<TokenStream>)
241where
242 F: Fn(&T) -> &Ident,
243{
244 let prev_slot_ref = if idx > 0 {
245 let prev_name = get_name(&fields[idx - 1]);
246 let prev_slot = PackingConstants::new(prev_name).location();
247 Some(quote! { #packing::#prev_slot.offset_slots })
248 } else {
249 None
250 };
251
252 let next_slot_ref = if idx + 1 < fields.len() {
253 let next_name = get_name(&fields[idx + 1]);
254 let next_slot = PackingConstants::new(next_name).location();
255 Some(quote! { #packing::#next_slot.offset_slots })
256 } else {
257 None
258 };
259
260 (prev_slot_ref, next_slot_ref)
261}
262
263pub(crate) fn gen_slot_packing_logic(
270 prev_ty: &Type,
271 curr_ty: &Type,
272 prev_slot_expr: TokenStream,
273 prev_offset_expr: TokenStream,
274) -> (TokenStream, TokenStream) {
275 let prev_layout_slots = quote! {
277 ::alloy::primitives::U256::from_limbs([<#prev_ty as crate::storage::StorableType>::SLOTS as u64, 0, 0, 0])
278 };
279
280 let can_pack_expr = quote! {
282 #prev_offset_expr
283 + <#prev_ty as crate::storage::StorableType>::BYTES
284 + <#curr_ty as crate::storage::StorableType>::BYTES <= 32
285 };
286
287 let slot_expr = quote! {{
288 if #can_pack_expr { #prev_slot_expr } else { #prev_slot_expr.checked_add(#prev_layout_slots).expect("slot overflow") }
289 }};
290
291 let offset_expr = quote! {{
292 if #can_pack_expr { #prev_offset_expr + <#prev_ty as crate::storage::StorableType>::BYTES } else { 0 }
293 }};
294
295 (slot_expr, offset_expr)
296}
297
298pub(crate) fn gen_layout_ctx_expr(
305 ty: &Type,
306 is_manual_slot: bool,
307 slot_const_ref: TokenStream,
308 offset_const_ref: TokenStream,
309 prev_slot_const_ref: Option<TokenStream>,
310 next_slot_const_ref: Option<TokenStream>,
311) -> TokenStream {
312 if !is_manual_slot && (prev_slot_const_ref.is_some() || next_slot_const_ref.is_some()) {
313 let prev_check = prev_slot_const_ref.map(|prev| quote! { #slot_const_ref == #prev });
315 let next_check = next_slot_const_ref.map(|next| quote! { #slot_const_ref == #next });
316
317 let shares_slot_check = match (prev_check, next_check) {
318 (Some(prev), Some(next)) => quote! { (#prev || #next) },
319 (Some(prev), None) => prev,
320 (None, Some(next)) => next,
321 (None, None) => unreachable!(),
322 };
323
324 quote! {
325 {
326 if #shares_slot_check && <#ty as crate::storage::StorableType>::IS_PACKABLE {
327 crate::storage::LayoutCtx::packed(#offset_const_ref)
328 } else {
329 crate::storage::LayoutCtx::FULL
330 }
331 }
332 }
333 } else {
334 quote! { crate::storage::LayoutCtx::FULL }
335 }
336}
337
338pub(crate) fn gen_layout_ctx_expr_inefficient(
346 ty: &Type,
347 is_manual_slot: bool,
348 _slot_const_ref: TokenStream,
349 offset_const_ref: TokenStream,
350 _prev_slot_const_ref: Option<TokenStream>,
351 _next_slot_const_ref: Option<TokenStream>,
352) -> TokenStream {
353 if !is_manual_slot {
354 quote! {
355 if <#ty as crate::storage::StorableType>::IS_PACKABLE {
356 crate::storage::LayoutCtx::packed(#offset_const_ref)
357 } else {
358 crate::storage::LayoutCtx::FULL
359 }
360 }
361 } else {
362 quote! { crate::storage::LayoutCtx::FULL }
363 }
364}
365
366pub(crate) fn gen_collision_check_fn(
372 idx: usize,
373 field: &LayoutField<'_>,
374 all_fields: &[LayoutField<'_>],
375) -> Option<(Ident, TokenStream)> {
376 fn gen_slot_count_expr(ty: &Type) -> TokenStream {
377 quote! { ::alloy::primitives::U256::from_limbs([<#ty as crate::storage::StorableType>::SLOTS as u64, 0, 0, 0]) }
378 }
379
380 if let SlotAssignment::Manual(_) = field.assigned_slot {
382 let field_name = field.name;
383 let check_fn_name = format_ident!("__check_collision_{}", field_name);
384 let slot_const = PackingConstants::new(field.name).slot();
385
386 let mut checks = TokenStream::new();
387
388 for (other_idx, other_field) in all_fields.iter().enumerate() {
390 if other_idx == idx {
391 continue;
392 }
393
394 let other_slot_const = PackingConstants::new(other_field.name).slot();
395 let other_name = other_field.name;
396
397 let current_count_expr = gen_slot_count_expr(field.ty);
399 let other_count_expr = gen_slot_count_expr(other_field.ty);
400
401 checks.extend(quote! {
403 {
404 let slot = #slot_const;
405 let slot_end = slot + #current_count_expr;
406 let other_slot = #other_slot_const;
407 let other_end = other_slot + #other_count_expr;
408
409 let no_overlap = slot_end.le(&other_slot) || other_end.le(&slot);
410 debug_assert!(
411 no_overlap,
412 "Storage slot collision: field `{}` (slot {:?}) overlaps with field `{}` (slot {:?})",
413 stringify!(#field_name),
414 slot,
415 stringify!(#other_name),
416 other_slot
417 );
418 }
419 });
420 }
421
422 let check_fn = quote! {
423 #[cfg(debug_assertions)]
424 #[inline(always)]
425 fn #check_fn_name() {
426 #checks
427 }
428 };
429
430 Some((check_fn_name, check_fn))
431 } else {
432 None
433 }
434}