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, iter::repeat};
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 { .. } => [repeat(0u8).take(final_padding).collect(), bytes].concat(),
182            Padding::Right { .. } => [bytes, repeat(0u8).take(final_padding).collect()].concat(),
183        }
184    }
185
186    pub(crate) fn has_copy_type(&self) -> bool {
187        matches!(self.value, Datum::Word(_) | Datum::Byte(_))
188    }
189
190    pub(crate) fn is_byte(&self) -> bool {
191        matches!(self.value, Datum::Byte(_))
192    }
193
194    pub(crate) fn equiv(&self, entry: &Entry) -> bool {
195        fn equiv_data(lhs: &Datum, rhs: &Datum) -> bool {
196            match (lhs, rhs) {
197                (Datum::Byte(l), Datum::Byte(r)) => l == r,
198                (Datum::Word(l), Datum::Word(r)) => l == r,
199                (Datum::ByteArray(l), Datum::ByteArray(r)) => l == r,
200                (Datum::Collection(l), Datum::Collection(r)) => {
201                    l.len() == r.len()
202                        && l.iter()
203                            .zip(r.iter())
204                            .all(|(l, r)| equiv_data(&l.value, &r.value))
205                }
206                _ => false,
207            }
208        }
209
210        // If this corresponds to a configuration-time constants, then the entry names will be
211        // available (i.e. `Some(..)`) and they must be the same before we can merge the two
212        // entries. Otherwise, `self.name` and `entry.name` will be `None` in which case we're also
213        // allowed to merge the two entries (if their values are equivalent of course).
214        equiv_data(&self.value, &entry.value) && self.name == entry.name
215    }
216}
217
218#[derive(Clone, Debug)]
219pub enum DataIdEntryKind {
220    NonConfigurable,
221    Configurable,
222}
223
224impl fmt::Display for DataIdEntryKind {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self {
227            DataIdEntryKind::NonConfigurable => write!(f, "NonConfigurable"),
228            DataIdEntryKind::Configurable => write!(f, "Configurable"),
229        }
230    }
231}
232
233/// An address which refers to a value in the data section of the asm.
234#[derive(Clone, Debug)]
235pub(crate) struct DataId {
236    pub(crate) idx: u32,
237    pub(crate) kind: DataIdEntryKind,
238}
239
240impl fmt::Display for DataId {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        write!(f, "data_{}_{}", self.kind, self.idx)
243    }
244}
245
246/// The data to be put in the data section of the asm
247#[derive(Default, Clone, Debug)]
248pub struct DataSection {
249    pub non_configurables: Vec<Entry>,
250    pub configurables: Vec<Entry>,
251    pub(crate) pointer_id: FxHashMap<u64, DataId>,
252}
253
254impl DataSection {
255    /// Get the number of entries
256    pub fn num_entries(&self) -> usize {
257        self.non_configurables.len() + self.configurables.len()
258    }
259
260    /// Iterate over all entries, non-configurables followed by configurables
261    pub fn iter_all_entries(&self) -> impl Iterator<Item = Entry> + '_ {
262        self.non_configurables
263            .iter()
264            .chain(self.configurables.iter())
265            .cloned()
266    }
267
268    /// Get the absolute index of an id
269    fn absolute_idx(&self, id: &DataId) -> usize {
270        match id.kind {
271            DataIdEntryKind::NonConfigurable => id.idx as usize,
272            DataIdEntryKind::Configurable => id.idx as usize + self.non_configurables.len(),
273        }
274    }
275
276    /// Get entry at id
277    fn get(&self, id: &DataId) -> Option<&Entry> {
278        match id.kind {
279            DataIdEntryKind::NonConfigurable => self.non_configurables.get(id.idx as usize),
280            DataIdEntryKind::Configurable => self.configurables.get(id.idx as usize),
281        }
282    }
283
284    /// Given a [DataId], calculate the offset _from the beginning of the data section_ to the data
285    /// in bytes.
286    pub(crate) fn data_id_to_offset(&self, id: &DataId) -> usize {
287        let idx = self.absolute_idx(id);
288        self.absolute_idx_to_offset(idx)
289    }
290
291    /// Given an absolute index, calculate the offset _from the beginning of the data section_ to the data
292    /// in bytes.
293    pub(crate) fn absolute_idx_to_offset(&self, idx: usize) -> usize {
294        self.iter_all_entries().take(idx).fold(0, |offset, entry| {
295            //entries must be word aligned
296            size_bytes_round_up_to_word_alignment!(offset + entry.to_bytes().len())
297        })
298    }
299
300    pub(crate) fn serialize_to_bytes(&self) -> Vec<u8> {
301        // not the exact right capacity but serves as a lower bound
302        let mut buf = Vec::with_capacity(self.num_entries());
303        for entry in self.iter_all_entries() {
304            buf.append(&mut entry.to_bytes());
305
306            //entries must be word aligned
307            let aligned_len = size_bytes_round_up_to_word_alignment!(buf.len());
308            buf.extend(vec![0u8; aligned_len - buf.len()]);
309        }
310        buf
311    }
312
313    /// Returns whether a specific [DataId] value has a copy type (fits in a register).
314    pub(crate) fn has_copy_type(&self, id: &DataId) -> Option<bool> {
315        self.get(id).map(|entry| entry.has_copy_type())
316    }
317
318    /// Returns whether a specific [DataId] value is a byte entry.
319    pub(crate) fn is_byte(&self, id: &DataId) -> Option<bool> {
320        self.get(id).map(|entry| entry.is_byte())
321    }
322
323    /// When generating code, sometimes a hard-coded data pointer is needed to reference
324    /// static values that have a length longer than one word.
325    /// This method appends pointers to the end of the data section (thus, not altering the data
326    /// offsets of previous data).
327    /// `pointer_value` is in _bytes_ and refers to the offset from instruction start or
328    /// relative to the current (load) instruction.
329    pub(crate) fn append_pointer(&mut self, pointer_value: u64) -> DataId {
330        // The 'pointer' is just a literal 64 bit address.
331        let data_id = self.insert_data_value(Entry::new_word(
332            pointer_value,
333            EntryName::NonConfigurable,
334            None,
335        ));
336        self.pointer_id.insert(pointer_value, data_id.clone());
337        data_id
338    }
339
340    /// Get the [DataId] for a pointer, if it exists.
341    /// The pointer must've been inserted with append_pointer.
342    pub(crate) fn data_id_of_pointer(&self, pointer_value: u64) -> Option<DataId> {
343        self.pointer_id.get(&pointer_value).cloned()
344    }
345
346    /// Given any data in the form of a [Literal] (using this type mainly because it includes type
347    /// information and debug spans), insert it into the data section and return its handle as
348    /// [DataId].
349    pub(crate) fn insert_data_value(&mut self, new_entry: Entry) -> DataId {
350        // if there is an identical data value, use the same id
351
352        let (value_pairs, kind) = match new_entry.name {
353            EntryName::NonConfigurable => (
354                &mut self.non_configurables,
355                DataIdEntryKind::NonConfigurable,
356            ),
357            EntryName::Configurable(_) => (&mut self.configurables, DataIdEntryKind::Configurable),
358        };
359        match value_pairs.iter().position(|entry| entry.equiv(&new_entry)) {
360            Some(num) => DataId {
361                idx: num as u32,
362                kind,
363            },
364            None => {
365                value_pairs.push(new_entry);
366                // the index of the data section where the value is stored
367                DataId {
368                    idx: (value_pairs.len() - 1) as u32,
369                    kind,
370                }
371            }
372        }
373    }
374
375    // If the stored data is Datum::Word, return the inner value.
376    pub(crate) fn get_data_word(&self, data_id: &DataId) -> Option<u64> {
377        let value_pairs = match data_id.kind {
378            DataIdEntryKind::NonConfigurable => &self.non_configurables,
379            DataIdEntryKind::Configurable => &self.configurables,
380        };
381        value_pairs.get(data_id.idx as usize).and_then(|entry| {
382            if let Datum::Word(w) = entry.value {
383                Some(w)
384            } else {
385                None
386            }
387        })
388    }
389}
390
391impl fmt::Display for DataSection {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        fn display_entry(datum: &Datum) -> String {
394            match datum {
395                Datum::Byte(w) => format!(".byte {w}"),
396                Datum::Word(w) => format!(".word {w}"),
397                Datum::ByteArray(bs) => display_bytes_for_data_section(bs, ".bytes"),
398                Datum::Slice(bs) => display_bytes_for_data_section(bs, ".slice"),
399                Datum::Collection(els) => format!(
400                    ".collection {{ {} }}",
401                    els.iter()
402                        .map(|el| display_entry(&el.value))
403                        .collect::<Vec<_>>()
404                        .join(", ")
405                ),
406            }
407        }
408
409        use std::fmt::Write;
410        let mut data_buf = String::new();
411        for (ix, entry) in self.iter_all_entries().enumerate() {
412            writeln!(
413                data_buf,
414                "data_{}_{} {}",
415                entry.name,
416                ix,
417                display_entry(&entry.value)
418            )?;
419        }
420
421        write!(f, ".data:\n{data_buf}")
422    }
423}
424
425fn display_bytes_for_data_section(bs: &Vec<u8>, prefix: &str) -> String {
426    let mut hex_str = String::new();
427    let mut chr_str = String::new();
428    for b in bs {
429        hex_str.push_str(format!("{b:02x} ").as_str());
430        chr_str.push(if *b == b' ' || b.is_ascii_graphic() {
431            *b as char
432        } else {
433            '.'
434        });
435    }
436    format!("{prefix}[{}] {hex_str} {chr_str}", bs.len())
437}