sway_core/asm_generation/fuel/
data_section.rs

1use rustc_hash::FxHashMap;
2use sway_ir::{
3    size_bytes_round_up_to_word_alignment, ConstantContent, ConstantValue, Context, Padding,
4};
5
6use std::fmt;
7
8#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
9pub enum EntryName {
10    NonConfigurable,
11    Configurable(String),
12}
13
14impl fmt::Display for EntryName {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match self {
17            EntryName::NonConfigurable => write!(f, "NonConfigurable"),
18            EntryName::Configurable(name) => write!(f, "<Configurable, {name}>"),
19        }
20    }
21}
22
23// An entry in the data section.  It's important for the size to be correct, especially for unions
24// where the size could be larger than the represented value.
25#[derive(Clone, Debug, serde::Serialize)]
26pub struct Entry {
27    pub value: Datum,
28    pub padding: Padding,
29    pub name: EntryName,
30}
31
32#[derive(Clone, Debug, serde::Serialize)]
33pub enum Datum {
34    Byte(u8),
35    Word(u64),
36    ByteArray(Vec<u8>),
37    Slice(Vec<u8>),
38    Collection(Vec<Entry>),
39}
40
41impl Entry {
42    pub(crate) fn new_byte(value: u8, name: EntryName, padding: Option<Padding>) -> Entry {
43        Entry {
44            value: Datum::Byte(value),
45            padding: padding.unwrap_or(Padding::default_for_u8(value)),
46            name,
47        }
48    }
49
50    pub(crate) fn new_word(value: u64, name: EntryName, padding: Option<Padding>) -> Entry {
51        Entry {
52            value: Datum::Word(value),
53            padding: padding.unwrap_or(Padding::default_for_u64(value)),
54            name,
55        }
56    }
57
58    pub(crate) fn new_byte_array(
59        bytes: Vec<u8>,
60        name: EntryName,
61        padding: Option<Padding>,
62    ) -> Entry {
63        Entry {
64            padding: padding.unwrap_or(Padding::default_for_byte_array(&bytes)),
65            value: Datum::ByteArray(bytes),
66            name,
67        }
68    }
69
70    pub(crate) fn new_slice(bytes: Vec<u8>, name: EntryName, padding: Option<Padding>) -> Entry {
71        Entry {
72            padding: padding.unwrap_or(Padding::default_for_byte_array(&bytes)),
73            value: Datum::Slice(bytes),
74            name,
75        }
76    }
77
78    pub(crate) fn new_collection(
79        elements: Vec<Entry>,
80        name: EntryName,
81        padding: Option<Padding>,
82    ) -> Entry {
83        Entry {
84            padding: padding.unwrap_or(Padding::default_for_aggregate(
85                elements.iter().map(|el| el.padding.target_size()).sum(),
86            )),
87            value: Datum::Collection(elements),
88            name,
89        }
90    }
91
92    pub(crate) fn from_constant(
93        context: &Context,
94        constant: &ConstantContent,
95        name: EntryName,
96        padding: Option<Padding>,
97    ) -> Entry {
98        // We need a special handling in case of enums.
99        if constant.ty.is_enum(context) {
100            let (tag, value) = constant
101                .enum_tag_and_value_with_paddings(context)
102                .expect("Constant is an enum.");
103
104            let tag_entry = Entry::from_constant(context, tag.0, EntryName::NonConfigurable, tag.1);
105            let value_entry =
106                Entry::from_constant(context, value.0, EntryName::NonConfigurable, value.1);
107
108            return Entry::new_collection(vec![tag_entry, value_entry], name, padding);
109        }
110
111        // Not an enum, no more special handling required.
112        match &constant.value {
113            ConstantValue::Undef | ConstantValue::Unit => Entry::new_byte(0, name, padding),
114            ConstantValue::Bool(value) => Entry::new_byte(u8::from(*value), name, padding),
115            ConstantValue::Uint(value) => {
116                if constant.ty.is_uint8(context) {
117                    Entry::new_byte(*value as u8, name, padding)
118                } else {
119                    Entry::new_word(*value, name, padding)
120                }
121            }
122            ConstantValue::U256(value) => {
123                Entry::new_byte_array(value.to_be_bytes().to_vec(), name, padding)
124            }
125            ConstantValue::B256(value) => {
126                Entry::new_byte_array(value.to_be_bytes().to_vec(), name, padding)
127            }
128            ConstantValue::String(bytes) => Entry::new_byte_array(bytes.clone(), name, padding),
129            ConstantValue::Array(_) => Entry::new_collection(
130                constant
131                    .array_elements_with_padding(context)
132                    .expect("Constant is an array.")
133                    .into_iter()
134                    .map(|(elem, padding)| {
135                        Entry::from_constant(context, elem, EntryName::NonConfigurable, padding)
136                    })
137                    .collect(),
138                name,
139                padding,
140            ),
141            ConstantValue::Struct(_) => Entry::new_collection(
142                constant
143                    .struct_fields_with_padding(context)
144                    .expect("Constant is a struct.")
145                    .into_iter()
146                    .map(|(elem, padding)| {
147                        Entry::from_constant(context, elem, EntryName::NonConfigurable, padding)
148                    })
149                    .collect(),
150                name,
151                padding,
152            ),
153            ConstantValue::RawUntypedSlice(bytes) => Entry::new_slice(bytes.clone(), name, padding),
154            ConstantValue::Reference(_) => {
155                todo!("Constant references are currently not supported.")
156            }
157            ConstantValue::Slice(_) => {
158                todo!("Constant slices are currently not supported.")
159            }
160        }
161    }
162
163    /// Converts a literal to a big-endian representation. This is padded to words.
164    pub(crate) fn to_bytes(&self) -> Vec<u8> {
165        // Get the big-endian byte representation of the basic value.
166        let bytes = match &self.value {
167            Datum::Byte(value) => vec![*value],
168            Datum::Word(value) => value.to_be_bytes().to_vec(),
169            Datum::ByteArray(bytes) | Datum::Slice(bytes) if bytes.len() % 8 == 0 => bytes.clone(),
170            Datum::ByteArray(bytes) | Datum::Slice(bytes) => bytes
171                .iter()
172                .chain([0; 8].iter())
173                .copied()
174                .take((bytes.len() + 7) & 0xfffffff8_usize)
175                .collect(),
176            Datum::Collection(items) => items.iter().flat_map(|el| el.to_bytes()).collect(),
177        };
178
179        let final_padding = self.padding.target_size().saturating_sub(bytes.len());
180        match self.padding {
181            Padding::Left { .. } => {
182                [std::iter::repeat_n(0u8, final_padding).collect(), bytes].concat()
183            }
184            Padding::Right { .. } => {
185                [bytes, std::iter::repeat_n(0u8, final_padding).collect()].concat()
186            }
187        }
188    }
189
190    pub(crate) fn has_copy_type(&self) -> bool {
191        matches!(self.value, Datum::Word(_) | Datum::Byte(_))
192    }
193
194    pub(crate) fn is_byte(&self) -> bool {
195        matches!(self.value, Datum::Byte(_))
196    }
197
198    pub(crate) fn equiv(&self, entry: &Entry) -> bool {
199        fn equiv_data(lhs: &Datum, rhs: &Datum) -> bool {
200            match (lhs, rhs) {
201                (Datum::Byte(l), Datum::Byte(r)) => l == r,
202                (Datum::Word(l), Datum::Word(r)) => l == r,
203                (Datum::ByteArray(l), Datum::ByteArray(r)) => l == r,
204                (Datum::Collection(l), Datum::Collection(r)) => {
205                    l.len() == r.len()
206                        && l.iter()
207                            .zip(r.iter())
208                            .all(|(l, r)| equiv_data(&l.value, &r.value))
209                }
210                _ => false,
211            }
212        }
213
214        // If this corresponds to a configuration-time constants, then the entry names will be
215        // available (i.e. `Some(..)`) and they must be the same before we can merge the two
216        // entries. Otherwise, `self.name` and `entry.name` will be `None` in which case we're also
217        // allowed to merge the two entries (if their values are equivalent of course).
218        equiv_data(&self.value, &entry.value) && self.name == entry.name
219    }
220}
221
222#[derive(Clone, Debug)]
223pub enum DataIdEntryKind {
224    NonConfigurable,
225    Configurable,
226}
227
228impl fmt::Display for DataIdEntryKind {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        match self {
231            DataIdEntryKind::NonConfigurable => write!(f, "NonConfigurable"),
232            DataIdEntryKind::Configurable => write!(f, "Configurable"),
233        }
234    }
235}
236
237/// An address which refers to a value in the data section of the asm.
238#[derive(Clone, Debug)]
239pub(crate) struct DataId {
240    pub(crate) idx: u32,
241    pub(crate) kind: DataIdEntryKind,
242}
243
244impl fmt::Display for DataId {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        write!(f, "data_{}_{}", self.kind, self.idx)
247    }
248}
249
250/// The data to be put in the data section of the asm
251#[derive(Default, Clone, Debug)]
252pub struct DataSection {
253    pub non_configurables: Vec<Entry>,
254    pub configurables: Vec<Entry>,
255    pub(crate) pointer_id: FxHashMap<u64, DataId>,
256}
257
258impl DataSection {
259    /// Get the number of entries
260    pub fn num_entries(&self) -> usize {
261        self.non_configurables.len() + self.configurables.len()
262    }
263
264    /// Iterate over all entries, non-configurables followed by configurables
265    pub fn iter_all_entries(&self) -> impl Iterator<Item = Entry> + '_ {
266        self.non_configurables
267            .iter()
268            .chain(self.configurables.iter())
269            .cloned()
270    }
271
272    /// Get the absolute index of an id
273    fn absolute_idx(&self, id: &DataId) -> usize {
274        match id.kind {
275            DataIdEntryKind::NonConfigurable => id.idx as usize,
276            DataIdEntryKind::Configurable => id.idx as usize + self.non_configurables.len(),
277        }
278    }
279
280    /// Get entry at id
281    fn get(&self, id: &DataId) -> Option<&Entry> {
282        match id.kind {
283            DataIdEntryKind::NonConfigurable => self.non_configurables.get(id.idx as usize),
284            DataIdEntryKind::Configurable => self.configurables.get(id.idx as usize),
285        }
286    }
287
288    /// Given a [DataId], calculate the offset _from the beginning of the data section_ to the data
289    /// in bytes.
290    pub(crate) fn data_id_to_offset(&self, id: &DataId) -> usize {
291        let idx = self.absolute_idx(id);
292        self.absolute_idx_to_offset(idx)
293    }
294
295    /// Given an absolute index, calculate the offset _from the beginning of the data section_ to the data
296    /// in bytes.
297    pub(crate) fn absolute_idx_to_offset(&self, idx: usize) -> usize {
298        self.iter_all_entries().take(idx).fold(0, |offset, entry| {
299            //entries must be word aligned
300            size_bytes_round_up_to_word_alignment!(offset + entry.to_bytes().len())
301        })
302    }
303
304    pub(crate) fn serialize_to_bytes(&self) -> Vec<u8> {
305        // not the exact right capacity but serves as a lower bound
306        let mut buf = Vec::with_capacity(self.num_entries());
307        for entry in self.iter_all_entries() {
308            buf.append(&mut entry.to_bytes());
309
310            //entries must be word aligned
311            let aligned_len = size_bytes_round_up_to_word_alignment!(buf.len());
312            buf.extend(vec![0u8; aligned_len - buf.len()]);
313        }
314        buf
315    }
316
317    /// Returns whether a specific [DataId] value has a copy type (fits in a register).
318    pub(crate) fn has_copy_type(&self, id: &DataId) -> Option<bool> {
319        self.get(id).map(|entry| entry.has_copy_type())
320    }
321
322    /// Returns whether a specific [DataId] value is a byte entry.
323    pub(crate) fn is_byte(&self, id: &DataId) -> Option<bool> {
324        self.get(id).map(|entry| entry.is_byte())
325    }
326
327    /// When generating code, sometimes a hard-coded data pointer is needed to reference
328    /// static values that have a length longer than one word.
329    /// This method appends pointers to the end of the data section (thus, not altering the data
330    /// offsets of previous data).
331    /// `pointer_value` is in _bytes_ and refers to the offset from instruction start or
332    /// relative to the current (load) instruction.
333    pub(crate) fn append_pointer(&mut self, pointer_value: u64) -> DataId {
334        // The 'pointer' is just a literal 64 bit address.
335        let data_id = self.insert_data_value(Entry::new_word(
336            pointer_value,
337            EntryName::NonConfigurable,
338            None,
339        ));
340        self.pointer_id.insert(pointer_value, data_id.clone());
341        data_id
342    }
343
344    /// Get the [DataId] for a pointer, if it exists.
345    /// The pointer must've been inserted with append_pointer.
346    pub(crate) fn data_id_of_pointer(&self, pointer_value: u64) -> Option<DataId> {
347        self.pointer_id.get(&pointer_value).cloned()
348    }
349
350    /// Given any data in the form of a [Literal] (using this type mainly because it includes type
351    /// information and debug spans), insert it into the data section and return its handle as
352    /// [DataId].
353    pub(crate) fn insert_data_value(&mut self, new_entry: Entry) -> DataId {
354        // if there is an identical data value, use the same id
355
356        let (value_pairs, kind) = match new_entry.name {
357            EntryName::NonConfigurable => (
358                &mut self.non_configurables,
359                DataIdEntryKind::NonConfigurable,
360            ),
361            EntryName::Configurable(_) => (&mut self.configurables, DataIdEntryKind::Configurable),
362        };
363        match value_pairs.iter().position(|entry| entry.equiv(&new_entry)) {
364            Some(num) => DataId {
365                idx: num as u32,
366                kind,
367            },
368            None => {
369                value_pairs.push(new_entry);
370                // the index of the data section where the value is stored
371                DataId {
372                    idx: (value_pairs.len() - 1) as u32,
373                    kind,
374                }
375            }
376        }
377    }
378
379    // If the stored data is Datum::Word, return the inner value.
380    pub(crate) fn get_data_word(&self, data_id: &DataId) -> Option<u64> {
381        let value_pairs = match data_id.kind {
382            DataIdEntryKind::NonConfigurable => &self.non_configurables,
383            DataIdEntryKind::Configurable => &self.configurables,
384        };
385        value_pairs.get(data_id.idx as usize).and_then(|entry| {
386            if let Datum::Word(w) = entry.value {
387                Some(w)
388            } else {
389                None
390            }
391        })
392    }
393}
394
395impl fmt::Display for DataSection {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        fn display_entry(datum: &Datum) -> String {
398            match datum {
399                Datum::Byte(w) => format!(".byte {w}"),
400                Datum::Word(w) => format!(".word {w}"),
401                Datum::ByteArray(bs) => display_bytes_for_data_section(bs, ".bytes"),
402                Datum::Slice(bs) => display_bytes_for_data_section(bs, ".slice"),
403                Datum::Collection(els) => format!(
404                    ".collection {{ {} }}",
405                    els.iter()
406                        .map(|el| display_entry(&el.value))
407                        .collect::<Vec<_>>()
408                        .join(", ")
409                ),
410            }
411        }
412
413        use std::fmt::Write;
414        let mut data_buf = String::new();
415        for (ix, entry) in self.iter_all_entries().enumerate() {
416            writeln!(
417                data_buf,
418                "data_{}_{} {}",
419                entry.name,
420                ix,
421                display_entry(&entry.value)
422            )?;
423        }
424
425        write!(f, ".data:\n{data_buf}")
426    }
427}
428
429fn display_bytes_for_data_section(bs: &Vec<u8>, prefix: &str) -> String {
430    let mut hex_str = String::new();
431    let mut chr_str = String::new();
432    for b in bs {
433        hex_str.push_str(format!("{b:02x} ").as_str());
434        chr_str.push(if *b == b' ' || b.is_ascii_graphic() {
435            *b as char
436        } else {
437            '.'
438        });
439    }
440    format!("{prefix}[{}] {hex_str} {chr_str}", bs.len())
441}