Skip to main content

solar_codegen/lower/
storage.rs

1use super::Lowerer;
2use crate::mir::{FunctionBuilder, ValueId};
3use alloy_primitives::U256;
4use solar_interface::Span;
5use solar_sema::{
6    hir,
7    hir::ElementaryType,
8    ty::{Ty, TyKind},
9};
10
11/// Storage position for a state variable.
12#[derive(Clone, Copy, Debug)]
13pub(super) struct StorageLocation {
14    pub(super) slot: u64,
15    pub(super) offset: u8,
16    pub(super) size: u8,
17}
18
19impl StorageLocation {
20    const WORD_SIZE: u8 = 32;
21
22    const fn full_word(slot: u64) -> Self {
23        Self { slot, offset: 0, size: Self::WORD_SIZE }
24    }
25
26    const fn is_packed(self) -> bool {
27        self.offset != 0 || self.size != Self::WORD_SIZE
28    }
29}
30
31impl<'gcx> Lowerer<'gcx> {
32    /// Allocates the storage location for a state variable.
33    pub(super) fn allocate_storage_location(
34        &mut self,
35        ty: Ty<'gcx>,
36        span: Span,
37    ) -> StorageLocation {
38        if let Some(size) = self.packed_storage_size(ty)
39            && size < StorageLocation::WORD_SIZE
40        {
41            if self.next_storage_offset + size > StorageLocation::WORD_SIZE {
42                self.next_storage_slot += 1;
43                self.next_storage_offset = 0;
44            }
45            let location = StorageLocation {
46                slot: self.next_storage_slot,
47                offset: self.next_storage_offset,
48                size,
49            };
50            self.next_storage_offset += size;
51            if self.next_storage_offset == StorageLocation::WORD_SIZE {
52                self.next_storage_slot += 1;
53                self.next_storage_offset = 0;
54            }
55            return location;
56        }
57
58        if self.next_storage_offset != 0 {
59            self.next_storage_slot += 1;
60            self.next_storage_offset = 0;
61        }
62
63        let slot = self.next_storage_slot;
64        let num_slots = self.calculate_storage_slots_for_ty(ty, span);
65        self.next_storage_slot += num_slots;
66        StorageLocation::full_word(slot)
67    }
68
69    /// Returns the byte width for scalar types that this lowering can safely pack.
70    fn packed_storage_size(&self, ty: Ty<'gcx>) -> Option<u8> {
71        match ty.peel_refs().kind {
72            TyKind::Elementary(ElementaryType::Bool) => Some(1),
73            TyKind::Udvt(inner, _) => self.packed_storage_size(inner),
74            _ => None,
75        }
76    }
77
78    /// Calculates the number of storage slots needed for a type.
79    pub(super) fn calculate_storage_slots_for_ty(&self, ty: Ty<'gcx>, span: Span) -> u64 {
80        match ty.peel_refs().kind {
81            TyKind::Struct(struct_id) => {
82                let mut total = 0u64;
83                for &field_ty in self.gcx.struct_field_types(struct_id) {
84                    total += self.calculate_storage_slots_for_ty(field_ty, span);
85                }
86                total.max(1)
87            }
88            // Fixed-size arrays occupy one slot per element (no packing),
89            // starting at the base slot. Dynamic arrays keep one length slot.
90            TyKind::Array(elem, len) => {
91                let elem_slots = self.calculate_storage_slots_for_ty(elem, span);
92                match u64::try_from(len).ok().and_then(|len| len.checked_mul(elem_slots)) {
93                    Some(slots) => slots.max(1),
94                    None => {
95                        self.gcx
96                            .dcx()
97                            .err("fixed-size storage arrays this large are not supported")
98                            .span(span)
99                            .emit();
100                        1
101                    }
102                }
103            }
104            _ => 1,
105        }
106    }
107
108    pub(super) fn load_storage_location_at_slot(
109        &self,
110        builder: &mut FunctionBuilder<'_>,
111        location: StorageLocation,
112        slot: ValueId,
113    ) -> ValueId {
114        let word = builder.sload(slot);
115        if !location.is_packed() {
116            return word;
117        }
118
119        let shifted = if location.offset == 0 {
120            word
121        } else {
122            let shift = builder.imm_u64(u64::from(location.offset) * 8);
123            builder.shr(shift, word)
124        };
125        let mask = Self::packed_storage_mask(location.size);
126        let mask = builder.imm_u256(mask);
127        builder.and(shifted, mask)
128    }
129
130    pub(super) fn store_storage_location(
131        &self,
132        builder: &mut FunctionBuilder<'_>,
133        location: StorageLocation,
134        value: ValueId,
135    ) {
136        let slot = builder.imm_u64(location.slot);
137        if !location.is_packed() {
138            builder.sstore(slot, value);
139            return;
140        }
141
142        let shift_bits = usize::from(location.offset) * 8;
143        let field_mask = Self::packed_storage_mask(location.size);
144        let shifted_mask = field_mask << shift_bits;
145        let keep_mask = builder.imm_u256(!shifted_mask);
146        let value_mask = builder.imm_u256(field_mask);
147
148        let word = builder.sload(slot);
149        let cleared = builder.and(word, keep_mask);
150        let masked = builder.and(value, value_mask);
151        let shifted = if location.offset == 0 {
152            masked
153        } else {
154            let shift = builder.imm_u64(shift_bits as u64);
155            builder.shl(shift, masked)
156        };
157        let updated = builder.or(cleared, shifted);
158        builder.sstore(slot, updated);
159    }
160
161    fn packed_storage_mask(size: u8) -> U256 {
162        if size >= StorageLocation::WORD_SIZE {
163            U256::MAX
164        } else {
165            (U256::from(1) << (usize::from(size) * 8)) - U256::from(1)
166        }
167    }
168
169    /// Gets the storage slot offset for a struct field.
170    pub fn get_struct_field_slot_offset(
171        &mut self,
172        struct_id: hir::StructId,
173        field_index: usize,
174    ) -> u64 {
175        if let Some(&offset) = self.struct_field_offsets.get(&(struct_id, field_index)) {
176            return offset;
177        }
178
179        let mut offset = 0u64;
180        for (i, &field_ty) in self.gcx.struct_field_types(struct_id).iter().enumerate() {
181            if i == field_index {
182                break;
183            }
184            offset += self.calculate_storage_slots_for_ty(field_ty, Span::DUMMY);
185        }
186
187        self.struct_field_offsets.insert((struct_id, field_index), offset);
188        offset
189    }
190
191    /// Calculates the number of 32-byte memory words needed for a type (flattened for structs).
192    pub fn calculate_memory_words_for_ty(&self, ty: Ty<'gcx>) -> u64 {
193        match ty.peel_refs().kind {
194            TyKind::Struct(struct_id) => {
195                let mut total = 0u64;
196                for &field_ty in self.gcx.struct_field_types(struct_id) {
197                    total += self.calculate_memory_words_for_ty(field_ty);
198                }
199                total.max(1)
200            }
201            _ => 1,
202        }
203    }
204
205    /// Gets the memory byte offset for a struct field.
206    pub fn get_struct_field_memory_offset(
207        &mut self,
208        struct_id: hir::StructId,
209        field_index: usize,
210    ) -> u64 {
211        if let Some(&offset) = self.struct_field_memory_offsets.get(&(struct_id, field_index)) {
212            return offset;
213        }
214
215        let offset = (field_index as u64) * 32;
216
217        self.struct_field_memory_offsets.insert((struct_id, field_index), offset);
218        offset
219    }
220
221    /// Recursively copies a struct from storage to memory.
222    /// Handles nested structs by flattening them into contiguous memory.
223    /// Returns the next memory offset after all fields are copied.
224    pub fn copy_storage_to_memory(
225        &mut self,
226        builder: &mut FunctionBuilder<'_>,
227        struct_id: hir::StructId,
228        base_slot: u64,
229        mem_ptr: ValueId,
230        mem_offset: u64,
231    ) -> u64 {
232        let mut current_slot_offset = 0u64;
233        let mut current_mem_offset = mem_offset;
234
235        let field_tys = self.gcx.struct_field_types(struct_id).to_vec();
236        for field_ty in field_tys {
237            if let TyKind::Struct(inner_struct_id) = field_ty.peel_refs().kind {
238                current_mem_offset = self.copy_storage_to_memory(
239                    builder,
240                    inner_struct_id,
241                    base_slot + current_slot_offset,
242                    mem_ptr,
243                    current_mem_offset,
244                );
245                current_slot_offset += self.calculate_storage_slots_for_ty(field_ty, Span::DUMMY);
246            } else {
247                let slot = base_slot + current_slot_offset;
248                let slot_val = builder.imm_u64(slot);
249                let field_val = builder.sload(slot_val);
250
251                if current_mem_offset == 0 {
252                    builder.mstore(mem_ptr, field_val);
253                } else {
254                    let offset_val = builder.imm_u64(current_mem_offset);
255                    let field_addr = builder.add(mem_ptr, offset_val);
256                    builder.mstore(field_addr, field_val);
257                }
258
259                current_slot_offset += 1;
260                current_mem_offset += 32;
261            }
262        }
263
264        current_mem_offset
265    }
266
267    /// Recursively copies a struct from memory to storage.
268    /// Handles nested structs by reading from flattened memory layout.
269    /// Returns the next memory offset after all fields are read.
270    pub fn copy_memory_to_storage(
271        &mut self,
272        builder: &mut FunctionBuilder<'_>,
273        struct_id: hir::StructId,
274        base_slot: u64,
275        mem_ptr: ValueId,
276        mem_offset: u64,
277    ) -> u64 {
278        let mut current_slot_offset = 0u64;
279        let mut current_mem_offset = mem_offset;
280
281        let field_tys = self.gcx.struct_field_types(struct_id).to_vec();
282        for field_ty in field_tys {
283            if let TyKind::Struct(inner_struct_id) = field_ty.peel_refs().kind {
284                current_mem_offset = self.copy_memory_to_storage(
285                    builder,
286                    inner_struct_id,
287                    base_slot + current_slot_offset,
288                    mem_ptr,
289                    current_mem_offset,
290                );
291                current_slot_offset += self.calculate_storage_slots_for_ty(field_ty, Span::DUMMY);
292            } else {
293                let slot = base_slot + current_slot_offset;
294                let slot_val = builder.imm_u64(slot);
295
296                let field_val = if current_mem_offset == 0 {
297                    builder.mload(mem_ptr)
298                } else {
299                    let offset_val = builder.imm_u64(current_mem_offset);
300                    let field_addr = builder.add(mem_ptr, offset_val);
301                    builder.mload(field_addr)
302                };
303
304                builder.sstore(slot_val, field_val);
305
306                current_slot_offset += 1;
307                current_mem_offset += 32;
308            }
309        }
310
311        current_mem_offset
312    }
313}