diff --git a/sway-core/src/asm_generation/evm/evm_asm_builder.rs b/sway-core/src/asm_generation/evm/evm_asm_builder.rs index 5a9cd73e870..516938665ac 100644 --- a/sway-core/src/asm_generation/evm/evm_asm_builder.rs +++ b/sway-core/src/asm_generation/evm/evm_asm_builder.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc}; use crate::{ asm_generation::{ - asm_builder::AsmBuilder, from_ir::StateAccessType, fuel::data_section::DataSection, + asm_builder::AsmBuilder, from_ir::StateAccessType, fuel::data_section::{DataSection, FinalDataSection, PackedDataSection}, instruction_set::InstructionSet, FinalizedAsm, ProgramABI, ProgramKind, }, asm_lang::Label, @@ -729,9 +729,7 @@ struct EvmFinalProgram { impl EvmFinalProgram { fn finalize(self) -> FinalizedAsm { FinalizedAsm { - data_section: DataSection { - ..Default::default() - }, + data_section: FinalDataSection::default(), program_section: InstructionSet::Evm { ops: self.ops }, program_kind: ProgramKind::Script, entries: vec![], diff --git a/sway-core/src/asm_generation/finalized_asm.rs b/sway-core/src/asm_generation/finalized_asm.rs index 0533f10069d..2eef892968d 100644 --- a/sway-core/src/asm_generation/finalized_asm.rs +++ b/sway-core/src/asm_generation/finalized_asm.rs @@ -1,3 +1,4 @@ +use super::fuel::data_section::{FinalDataSection, PackedDataSection}; use super::instruction_set::InstructionSet; use super::{ fuel::{checks, data_section::DataSection}, @@ -40,7 +41,7 @@ pub struct DataSectionInformation { /// applied to it #[derive(Clone)] pub struct FinalizedAsm { - pub data_section: DataSection, + pub data_section: FinalDataSection, pub program_section: InstructionSet, pub program_kind: ProgramKind, pub entries: Vec, @@ -69,7 +70,7 @@ pub struct CompiledBytecode { impl FinalizedAsm { pub(crate) fn to_bytecode_mut( - &mut self, + self, handler: &Handler, source_map: &mut SourceMap, source_engine: &SourceEngine, @@ -115,7 +116,7 @@ impl fmt::Display for FinalizedAsm { fn to_bytecode_mut( ops: &[AllocatedOp], - data_section: &mut DataSection, + data_section: &mut PackedDataSection, source_map: &mut SourceMap, source_engine: &SourceEngine, build_config: &BuildConfig, @@ -314,11 +315,11 @@ fn to_bytecode_mut( print!("{}{:#010x} ", " ".repeat(indentation), offset); match &pair.value { - Datum::Byte(w) => println!(".byte i{w}, as hex {w:02X}"), - Datum::Word(w) => { - println!(".word i{w}, as hex be bytes ({:02X?})", w.to_be_bytes()) - } - Datum::ByteArray(bs) => { + Datum::U8(v) => println!(".byte i{}, as hex {:02X}", v, v), + Datum::U16(v) => println!(".quarterword i{}, as hex {:02X?}", v, v.to_be_bytes()), + Datum::U32(v) => println!(".halfword i{}, as hex {:02X?}", v, v.to_be_bytes()), + Datum::U64(v) => println!(".word i{}, as hex {:02X?}", v, v.to_be_bytes()), + Datum::ByRef(bs) => { print!(".bytes as hex ({bs:02X?}), len i{}, as ascii \"", bs.len()); for b in bs { @@ -333,21 +334,6 @@ fn to_bytecode_mut( } println!("\""); } - Datum::Slice(bs) => { - print!(".slice as hex ({bs:02X?}), len i{}, as ascii \"", bs.len()); - - for b in bs { - print!( - "{}", - if *b == b' ' || b.is_ascii_graphic() { - *b as char - } else { - '.' - } - ); - } - println!("\""); - } Datum::Collection(els) => { println!(".collection"); for e in els { @@ -368,22 +354,7 @@ fn to_bytecode_mut( assert_eq!(half_word_ix * 4, offset_to_data_section_in_bytes as usize); assert_eq!(bytecode.len(), offset_to_data_section_in_bytes as usize); - let num_nonconfigurables = data_section.non_configurables.len(); - let named_data_section_entries_offsets = data_section - .configurables - .iter() - .enumerate() - .map(|(id, entry)| { - let EntryName::Configurable(name) = &entry.name else { - panic!("Non-configurable in configurables part of datasection"); - }; - ( - name.clone(), - offset_to_data_section_in_bytes - + data_section.absolute_idx_to_offset(id + num_nonconfigurables) as u64, - ) - }) - .collect::>(); + let named_data_section_entries_offsets = data_section.named_offsets(); let mut data_section = data_section.serialize_to_bytes(); bytecode.append(&mut data_section); diff --git a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs index 1d34571552b..0b32ec49555 100644 --- a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs @@ -10,7 +10,7 @@ use crate::{ use super::{ abstract_instruction_set::RealizedAbstractInstructionSet, compiler_constants as consts, - data_section::{DataSection, Entry}, + data_section::{DataSection, Entry, PackedDataSection}, }; use fuel_vm::fuel_asm::Imm12; @@ -199,7 +199,7 @@ impl AllocatedAbstractInstructionSet { /// labels in the organizational ops pub(crate) fn realize_labels( mut self, - data_section: &mut DataSection, + data_section: &mut PackedDataSection, ) -> Result<(RealizedAbstractInstructionSet, LabeledBlocks), crate::CompileError> { let label_offsets = self.resolve_labels(data_section, 0)?; let mut curr_offset = 0; @@ -377,7 +377,6 @@ impl AllocatedAbstractInstructionSet { let data_id = data_section.insert_data_value(Entry::new_word( offset, EntryName::NonConfigurable, - None, )); realized_ops.push(RealizedOp { opcode: AllocatedOpcode::LoadDataId(r1, data_id), @@ -402,7 +401,7 @@ impl AllocatedAbstractInstructionSet { fn resolve_labels( &mut self, - data_section: &mut DataSection, + data_section: &mut PackedDataSection, iter_count: usize, ) -> Result { // Iteratively resolve the label offsets. @@ -428,7 +427,7 @@ impl AllocatedAbstractInstructionSet { )); } - let (remap_needed, label_offsets) = self.map_label_offsets(data_section); + let (remap_needed, label_offsets) = self.map_label_offsets(); if !remap_needed || !self.rewrite_far_jumps(&label_offsets, data_section) { // We didn't need to make any changes to the ops, so the labels are now correct. @@ -439,8 +438,57 @@ impl AllocatedAbstractInstructionSet { } } + // Maximum amount of concrete (4 byte) opcodes that can be emitted in a single instruction. + fn max_instruction_size(op: &AllocatedAbstractOp) -> u64 { + use ControlFlowOp::*; + match op.opcode { + Either::Right(Label(_)) => 0, + + // Data section referencing can take up to 2 instructions + Either::Left(AllocatedOpcode::LoadDataId(_, _) | AllocatedOpcode::AddrDataId(_, _))=> { + 2 + } + + // cfei 0 and cfsi 0 are omitted from asm emission, don't count them for offsets + Either::Left(AllocatedOpcode::CFEI(ref op)) + | Either::Left(AllocatedOpcode::CFSI(ref op)) + if op.value() == 0 => + { + 0 + } + + // Another special case for the blob opcode, used for testing. + Either::Left(AllocatedOpcode::BLOB(ref count)) => count.value() as u64, + + // These ops will end up being exactly one op, so the cur_offset goes up one. + Either::Right(Jump(..) | JumpIfNotZero(..) | Call(..) | LoadLabel(..)) + | Either::Left(_) => 1, + + // We use three instructions to save the absolute address for return. + // SUB r1 $pc $is + // SRLI r1 r1 2 / DIVI r1 r1 4 + // ADDI $r1 $r1 offset + Either::Right(SaveRetAddr(..)) => 3, + + Either::Right(Comment) => 0, + + Either::Right(DataSectionOffsetPlaceholder) => { + // If the placeholder is 32 bits, this is 1. if 64, this should be 2. We use LW + // to load the data, which loads a whole word, so for now this is 2. + 2 + } + + Either::Right(ConfigurablesOffsetPlaceholder) => 2, + + Either::Right(PushAll(_)) | Either::Right(PopAll(_)) => unreachable!( + "fix me, pushall and popall don't really belong in control flow ops \ + since they're not about control flow" + ), + } + } + // Instruction size in units of 32b. - fn instruction_size(op: &AllocatedAbstractOp, data_section: &DataSection) -> u64 { + fn instruction_size(op: &AllocatedAbstractOp, data_section: &PackedDataSection) -> u64 { use ControlFlowOp::*; match op.opcode { Either::Right(Label(_)) => 0, @@ -504,7 +552,7 @@ impl AllocatedAbstractInstructionSet { } } - fn map_label_offsets(&self, data_section: &DataSection) -> (bool, LabeledBlocks) { + fn map_label_offsets(&self) -> (bool, LabeledBlocks) { let mut labelled_blocks = LabeledBlocks::new(); let mut cur_offset = 0; let mut cur_basic_block = None; @@ -540,8 +588,9 @@ impl AllocatedAbstractInstructionSet { jnz_labels.insert((cur_offset, lab)); } - // Update the offset. - cur_offset += Self::instruction_size(op, data_section); + // Update the maximum offset. We're pessimistic here, so if the all labels + // can still be placed this will definitely work. + cur_offset += Self::max_instruction_size(op); } // Don't forget the final block. @@ -571,7 +620,7 @@ impl AllocatedAbstractInstructionSet { fn rewrite_far_jumps( &mut self, label_offsets: &LabeledBlocks, - data_section: &DataSection, + data_section: &PackedDataSection, ) -> bool { let min_ops = self.ops.len(); let mut modified = false; diff --git a/sway-core/src/asm_generation/fuel/data_section.rs b/sway-core/src/asm_generation/fuel/data_section.rs index 9e9ba081ed5..cacb51e46fa 100644 --- a/sway-core/src/asm_generation/fuel/data_section.rs +++ b/sway-core/src/asm_generation/fuel/data_section.rs @@ -1,9 +1,9 @@ use rustc_hash::FxHashMap; use sway_ir::{ - size_bytes_round_up_to_word_alignment, ConstantContent, ConstantValue, Context, Padding, + size_bytes_round_up_to_word_alignment, ConstantContent, ConstantValue, Context, }; -use std::{fmt, iter::repeat}; +use std::{collections::BTreeMap, fmt}; #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub enum EntryName { @@ -20,37 +20,41 @@ impl fmt::Display for EntryName { } } -// An entry in the data section. It's important for the size to be correct, especially for unions +// An entry in the data section. It's important for the size to be correct, especially for unions // where the size could be larger than the represented value. #[derive(Clone, Debug, serde::Serialize)] pub struct Entry { pub value: Datum, - pub padding: Padding, pub name: EntryName, } #[derive(Clone, Debug, serde::Serialize)] pub enum Datum { - Byte(u8), - Word(u64), - ByteArray(Vec), - Slice(Vec), + /// A single byte, loaded into a register. + U8(u8), + /// A quarterword, loaded into a register. + U16(u16), + /// A halfword, loaded into a register. + U32(u32), + /// A word, loaded into a register. + U64(u64), + /// Data behind a pointer. + ByRef(Vec), + /// Collection of entries. Collection(Vec), } impl Entry { - pub(crate) fn new_byte(value: u8, name: EntryName, padding: Option) -> Entry { + pub(crate) fn new_byte(value: u8, name: EntryName) -> Entry { Entry { - value: Datum::Byte(value), - padding: padding.unwrap_or(Padding::default_for_u8(value)), + value: Datum::U8(value), name, } } - pub(crate) fn new_word(value: u64, name: EntryName, padding: Option) -> Entry { + pub(crate) fn new_word(value: u64, name: EntryName) -> Entry { Entry { - value: Datum::Word(value), - padding: padding.unwrap_or(Padding::default_for_u64(value)), + value: Datum::U64(value), name, } } @@ -58,19 +62,9 @@ impl Entry { pub(crate) fn new_byte_array( bytes: Vec, name: EntryName, - padding: Option, ) -> Entry { Entry { - padding: padding.unwrap_or(Padding::default_for_byte_array(&bytes)), - value: Datum::ByteArray(bytes), - name, - } - } - - pub(crate) fn new_slice(bytes: Vec, name: EntryName, padding: Option) -> Entry { - Entry { - padding: padding.unwrap_or(Padding::default_for_byte_array(&bytes)), - value: Datum::Slice(bytes), + value: Datum::ByRef(bytes), name, } } @@ -78,12 +72,8 @@ impl Entry { pub(crate) fn new_collection( elements: Vec, name: EntryName, - padding: Option, ) -> Entry { Entry { - padding: padding.unwrap_or(Padding::default_for_aggregate( - elements.iter().map(|el| el.padding.target_size()).sum(), - )), value: Datum::Collection(elements), name, } @@ -93,64 +83,76 @@ impl Entry { context: &Context, constant: &ConstantContent, name: EntryName, - padding: Option, ) -> Entry { // We need a special handling in case of enums. if constant.ty.is_enum(context) { - let (tag, value) = constant - .enum_tag_and_value_with_paddings(context) - .expect("Constant is an enum."); - - let tag_entry = Entry::from_constant(context, tag.0, EntryName::NonConfigurable, tag.1); + let elems = match &constant.value { + ConstantValue::Struct(elems) if elems.len() == 2 => elems, + _ => unreachable!("enums are represented as structs with 2 elements"), + }; + let tag = &elems[0]; + let value = &elems[1]; + + let tag_entry = Entry::from_constant(context, tag, EntryName::NonConfigurable); let value_entry = - Entry::from_constant(context, value.0, EntryName::NonConfigurable, value.1); + Entry::from_constant(context, value, EntryName::NonConfigurable); - return Entry::new_collection(vec![tag_entry, value_entry], name, padding); + return Entry::new_collection(vec![tag_entry, value_entry], name); } // Not an enum, no more special handling required. match &constant.value { - ConstantValue::Undef | ConstantValue::Unit => Entry::new_byte(0, name, padding), - ConstantValue::Bool(value) => Entry::new_byte(u8::from(*value), name, padding), + ConstantValue::Undef | ConstantValue::Unit => Entry::new_byte(0, name), + ConstantValue::Bool(value) => Entry::new_byte(u8::from(*value), name), ConstantValue::Uint(value) => { if constant.ty.is_uint8(context) { - Entry::new_byte(*value as u8, name, padding) + Entry { + value: Datum::U8(*value as u8), + name, + } + } else if constant.ty.is_uint16(context) { + Entry { + value: Datum::U16(*value as u16), + name, + } + } else if constant.ty.is_uint32(context) { + Entry { + value: Datum::U32(*value as u32), + name, + } } else { - Entry::new_word(*value, name, padding) + Entry { + value: Datum::U64(*value as u64), + name, + } } } ConstantValue::U256(value) => { - Entry::new_byte_array(value.to_be_bytes().to_vec(), name, padding) + Entry::new_byte_array(value.to_be_bytes().to_vec(), name) } ConstantValue::B256(value) => { - Entry::new_byte_array(value.to_be_bytes().to_vec(), name, padding) + Entry::new_byte_array(value.to_be_bytes().to_vec(), name) } - ConstantValue::String(bytes) => Entry::new_byte_array(bytes.clone(), name, padding), - ConstantValue::Array(_) => Entry::new_collection( - constant - .array_elements_with_padding(context) - .expect("Constant is an array.") + ConstantValue::String(bytes) => Entry::new_byte_array(bytes.clone(), name), + ConstantValue::Array(elements) => Entry::new_collection( + elements .into_iter() - .map(|(elem, padding)| { - Entry::from_constant(context, elem, EntryName::NonConfigurable, padding) + .map(|elem| { + Entry::from_constant(context, elem, EntryName::NonConfigurable) }) .collect(), name, - padding, ), - ConstantValue::Struct(_) => Entry::new_collection( - constant - .struct_fields_with_padding(context) - .expect("Constant is a struct.") + ConstantValue::Struct(fields) => Entry::new_collection( + fields .into_iter() - .map(|(elem, padding)| { - Entry::from_constant(context, elem, EntryName::NonConfigurable, padding) + .map(|elem| { + Entry::from_constant(context, elem, EntryName::NonConfigurable) }) .collect(), name, - padding, ), - ConstantValue::RawUntypedSlice(bytes) => Entry::new_slice(bytes.clone(), name, padding), + ConstantValue::RawUntypedSlice(bytes) => Entry::new_byte_array(bytes.clone(), name), ConstantValue::Reference(_) => { todo!("Constant references are currently not supported.") } @@ -160,43 +162,42 @@ impl Entry { } } - /// Converts a literal to a big-endian representation. This is padded to words. + /// Converts a literal to a big-endian representation. No padding is applied. pub(crate) fn to_bytes(&self) -> Vec { - // Get the big-endian byte representation of the basic value. - let bytes = match &self.value { - Datum::Byte(value) => vec![*value], - Datum::Word(value) => value.to_be_bytes().to_vec(), - Datum::ByteArray(bytes) | Datum::Slice(bytes) if bytes.len() % 8 == 0 => bytes.clone(), - Datum::ByteArray(bytes) | Datum::Slice(bytes) => bytes - .iter() - .chain([0; 8].iter()) - .copied() - .take((bytes.len() + 7) & 0xfffffff8_usize) - .collect(), + match &self.value { + Datum::U8(v) => v.to_be_bytes().to_vec(), + Datum::U16(v) => v.to_be_bytes().to_vec(), + Datum::U32(v) => v.to_be_bytes().to_vec(), + Datum::U64(v) => v.to_be_bytes().to_vec(), + Datum::ByRef(bytes) => bytes.clone(), Datum::Collection(items) => items.iter().flat_map(|el| el.to_bytes()).collect(), - }; - - let final_padding = self.padding.target_size().saturating_sub(bytes.len()); - match self.padding { - Padding::Left { .. } => [repeat(0u8).take(final_padding).collect(), bytes].concat(), - Padding::Right { .. } => [bytes, repeat(0u8).take(final_padding).collect()].concat(), } } pub(crate) fn has_copy_type(&self) -> bool { - matches!(self.value, Datum::Word(_) | Datum::Byte(_)) + matches!(self.value, Datum::U8(_) | Datum::U16(_) | Datum::U32(_) | Datum::U64(_)) } - pub(crate) fn is_byte(&self) -> bool { - matches!(self.value, Datum::Byte(_)) + /// Size of the entry in bytes. + pub(crate) fn size(&self) -> usize { + match &self.value { + Datum::U8(_) => 1, + Datum::U16(_) => 2, + Datum::U32(_) => 4, + Datum::U64(_) => 8, + Datum::ByRef(bytes) => bytes.len(), + Datum::Collection(items) => items.iter().map(|el| el.size()).sum(), + } } pub(crate) fn equiv(&self, entry: &Entry) -> bool { fn equiv_data(lhs: &Datum, rhs: &Datum) -> bool { match (lhs, rhs) { - (Datum::Byte(l), Datum::Byte(r)) => l == r, - (Datum::Word(l), Datum::Word(r)) => l == r, - (Datum::ByteArray(l), Datum::ByteArray(r)) => l == r, + (Datum::U8(l), Datum::U8(r)) => l == r, + (Datum::U16(l), Datum::U16(r)) => l == r, + (Datum::U32(l), Datum::U32(r)) => l == r, + (Datum::U64(l), Datum::U64(r)) => l == r, + (Datum::ByRef(l), Datum::ByRef(r)) => l == r, (Datum::Collection(l), Datum::Collection(r)) => { l.len() == r.len() && l.iter() @@ -215,7 +216,7 @@ impl Entry { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum DataIdEntryKind { NonConfigurable, Configurable, @@ -231,7 +232,7 @@ impl fmt::Display for DataIdEntryKind { } /// An address which refers to a value in the data section of the asm. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub(crate) struct DataId { pub(crate) idx: u32, pub(crate) kind: DataIdEntryKind, @@ -243,7 +244,8 @@ impl fmt::Display for DataId { } } -/// The data to be put in the data section of the asm +/// The data to be put in the data section of the asm. +/// After all data has been inserted, an immutable [FinalDataSection] is created. #[derive(Default, Clone, Debug)] pub struct DataSection { pub non_configurables: Vec, @@ -252,27 +254,6 @@ pub struct DataSection { } impl DataSection { - /// Get the number of entries - pub fn num_entries(&self) -> usize { - self.non_configurables.len() + self.configurables.len() - } - - /// Iterate over all entries, non-configurables followed by configurables - pub fn iter_all_entries(&self) -> impl Iterator + '_ { - self.non_configurables - .iter() - .chain(self.configurables.iter()) - .cloned() - } - - /// Get the absolute index of an id - fn absolute_idx(&self, id: &DataId) -> usize { - match id.kind { - DataIdEntryKind::NonConfigurable => id.idx as usize, - DataIdEntryKind::Configurable => id.idx as usize + self.non_configurables.len(), - } - } - /// Get entry at id fn get(&self, id: &DataId) -> Option<&Entry> { match id.kind { @@ -281,74 +262,16 @@ impl DataSection { } } - /// Given a [DataId], calculate the offset _from the beginning of the data section_ to the data - /// in bytes. - pub(crate) fn data_id_to_offset(&self, id: &DataId) -> usize { - let idx = self.absolute_idx(id); - self.absolute_idx_to_offset(idx) - } - - /// Given an absolute index, calculate the offset _from the beginning of the data section_ to the data - /// in bytes. - pub(crate) fn absolute_idx_to_offset(&self, idx: usize) -> usize { - self.iter_all_entries().take(idx).fold(0, |offset, entry| { - //entries must be word aligned - size_bytes_round_up_to_word_alignment!(offset + entry.to_bytes().len()) - }) - } - - pub(crate) fn serialize_to_bytes(&self) -> Vec { - // not the exact right capacity but serves as a lower bound - let mut buf = Vec::with_capacity(self.num_entries()); - for entry in self.iter_all_entries() { - buf.append(&mut entry.to_bytes()); - - //entries must be word aligned - let aligned_len = size_bytes_round_up_to_word_alignment!(buf.len()); - buf.extend(vec![0u8; aligned_len - buf.len()]); - } - buf - } - /// Returns whether a specific [DataId] value has a copy type (fits in a register). pub(crate) fn has_copy_type(&self, id: &DataId) -> Option { self.get(id).map(|entry| entry.has_copy_type()) } - /// Returns whether a specific [DataId] value is a byte entry. - pub(crate) fn is_byte(&self, id: &DataId) -> Option { - self.get(id).map(|entry| entry.is_byte()) - } - - /// When generating code, sometimes a hard-coded data pointer is needed to reference - /// static values that have a length longer than one word. - /// This method appends pointers to the end of the data section (thus, not altering the data - /// offsets of previous data). - /// `pointer_value` is in _bytes_ and refers to the offset from instruction start or - /// relative to the current (load) instruction. - pub(crate) fn append_pointer(&mut self, pointer_value: u64) -> DataId { - // The 'pointer' is just a literal 64 bit address. - let data_id = self.insert_data_value(Entry::new_word( - pointer_value, - EntryName::NonConfigurable, - None, - )); - self.pointer_id.insert(pointer_value, data_id.clone()); - data_id - } - - /// Get the [DataId] for a pointer, if it exists. - /// The pointer must've been inserted with append_pointer. - pub(crate) fn data_id_of_pointer(&self, pointer_value: u64) -> Option { - self.pointer_id.get(&pointer_value).cloned() - } - /// Given any data in the form of a [Literal] (using this type mainly because it includes type /// information and debug spans), insert it into the data section and return its handle as /// [DataId]. pub(crate) fn insert_data_value(&mut self, new_entry: Entry) -> DataId { // if there is an identical data value, use the same id - let (value_pairs, kind) = match new_entry.name { EntryName::NonConfigurable => ( &mut self.non_configurables, @@ -372,30 +295,132 @@ impl DataSection { } } - // If the stored data is Datum::Word, return the inner value. - pub(crate) fn get_data_word(&self, data_id: &DataId) -> Option { - let value_pairs = match data_id.kind { - DataIdEntryKind::NonConfigurable => &self.non_configurables, - DataIdEntryKind::Configurable => &self.configurables, - }; - value_pairs.get(data_id.idx as usize).and_then(|entry| { - if let Datum::Word(w) = entry.value { - Some(w) - } else { - None - } - }) + /// When a load from data section is realized and targets a (register-placeable) copy type, + /// this is the value that will be loaded into the register. + /// For non-copy types, returns `None` instead. + pub(crate) fn get_reg_value(&self, data_id: DataId) -> Option { + let entry = self.get(&data_id)?; + match &entry.value { + Datum::U8(v) => Some(*v as u64), + Datum::U16(v) => Some(*v as u64), + Datum::U32(v) => Some(*v as u64), + Datum::U64(v) => Some(*v), + _ => None, + } + } + + pub(crate) fn pack(&self, optimization: crate::OptLevel) -> PackedDataSection { + PackedDataSection::new(self, optimization) + } +} + +fn display_bytes_for_data_section(bs: &Vec, prefix: &str) -> String { + let mut hex_str = String::new(); + let mut chr_str = String::new(); + for b in bs { + hex_str.push_str(format!("{b:02x} ").as_str()); + chr_str.push(if *b == b' ' || b.is_ascii_graphic() { + *b as char + } else { + '.' + }); } + format!("{prefix}[{}] {hex_str} {chr_str}", bs.len()) +} + + +/// Data section packed into it's penultimate form, +/// ready for label serialization. Still allows pointer insertion, +/// but no other modifications. +#[derive(Default, Clone, Debug)] +pub struct PackedDataSection { + non_configurables: Vec, + configurables: Vec, + pointer_id: FxHashMap, } -impl fmt::Display for DataSection { +impl PackedDataSection { + pub(crate) fn new( + data_section: &DataSection, + _optimization: crate::OptLevel, + ) -> PackedDataSection { + // TODO: optimize + + let mut packed = PackedDataSection::default(); + packed.non_configurables = data_section + .non_configurables + .iter() + .map(|entry| entry.clone()) + .collect(); + packed.configurables = data_section + .configurables + .iter() + .map(|entry| entry.clone()) + .collect(); + packed.pointer_id = data_section.pointer_id.clone(); + packed + } + + pub(crate) fn finalize(self) -> FinalDataSection { + FinalDataSection { non_configurables: self.non_configurables, configurables: self.configurables } + } + + /// When generating code, sometimes a hard-coded data pointer is needed to reference + /// static values that have a length longer than one word. + /// This method appends pointers to the end of the data section (thus, not altering the data + /// offsets of previous data). + /// `pointer_value` is in _bytes_ and refers to the offset from instruction start or + /// relative to the current (load) instruction. + pub(crate) fn append_pointer(&mut self, pointer_value: u64) -> DataId { + todo!(); + // // The 'pointer' is just a literal 64 bit address. + // let data_id = self.insert_data_value(Entry::new_word( + // pointer_value, + // EntryName::NonConfigurable, + // )); + // self.pointer_id.insert(pointer_value, data_id.clone()); + // data_id + } + + /// Get the [DataId] for a pointer, if it exists. + /// The pointer must've been inserted with append_pointer. + pub(crate) fn data_id_of_pointer(&self, pointer_value: u64) -> Option { + self.pointer_id.get(&pointer_value).cloned() + } + + /// Return offsets to named configurables. + pub(crate) fn named_offsets(&self, offset_to_data_section_in_bytes: u64) -> BTreeMap { + // self.configurables.iter().enumerate() + todo!(); + } +} + +/// Data section with all data laid out. +#[derive(Default, Clone, Debug)] +pub struct FinalDataSection { + pub non_configurables: Vec, + pub configurables: Vec, +} + +impl FinalDataSection { + /// Iterate over all entries, non-configurables followed by configurables + pub fn iter_all_entries(&self) -> impl Iterator + '_ { + self.non_configurables + .iter() + .chain(self.configurables.iter()) + .cloned() + } +} + +impl fmt::Display for FinalDataSection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn display_entry(datum: &Datum) -> String { match datum { - Datum::Byte(w) => format!(".byte {w}"), - Datum::Word(w) => format!(".word {w}"), - Datum::ByteArray(bs) => display_bytes_for_data_section(bs, ".bytes"), - Datum::Slice(bs) => display_bytes_for_data_section(bs, ".slice"), + Datum::U8(v) => format!(".byte {v}"), + Datum::U16(v) => format!(".quarterword {v}"), + Datum::U32(v) => format!(".word {v}"), + Datum::U64(v) => format!(".half {v}"), + Datum::ByRef(bs) => display_bytes_for_data_section(bs, ".bytes"), Datum::Collection(els) => format!( ".collection {{ {} }}", els.iter() @@ -421,17 +446,3 @@ impl fmt::Display for DataSection { write!(f, ".data:\n{data_buf}") } } - -fn display_bytes_for_data_section(bs: &Vec, prefix: &str) -> String { - let mut hex_str = String::new(); - let mut chr_str = String::new(); - for b in bs { - hex_str.push_str(format!("{b:02x} ").as_str()); - chr_str.push(if *b == b' ' || b.is_ascii_graphic() { - *b as char - } else { - '.' - }); - } - format!("{prefix}[{}] {hex_str} {chr_str}", bs.len()) -} diff --git a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs index 59cba133cb2..1a06d2f3dc8 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -104,7 +104,6 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> { self.context, constant.get_content(self.context), EntryName::Configurable(name.clone()), - None, ); let dataid = self.data_section.insert_data_value(entry); self.configurable_v0_data_id.insert(name.clone(), dataid); @@ -125,7 +124,6 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> { let dataid = self.data_section.insert_data_value(Entry::new_byte_array( encoded_bytes.clone(), EntryName::Configurable(name.clone()), - None, )); self.before_entries.push(Op { @@ -277,7 +275,7 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> { } let final_program = allocated_program - .into_final_program() + .into_final_program(build_config.map(|c| c.optimization_level).unwrap_or_default()) .map_err(|e| handler.emit_err(e))?; if build_config @@ -1273,7 +1271,6 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.context, constant.get_content(self.context), EntryName::NonConfigurable, - None, ); let data_id = self.data_section.insert_data_value(entry); @@ -2134,7 +2131,6 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.context, constant.get_content(self.context), config_name, - None, ); let data_id = self.data_section.insert_data_value(entry); @@ -2258,7 +2254,6 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { let data_id = self.data_section.insert_data_value(Entry::new_word( imm, EntryName::NonConfigurable, - None, )); self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::LoadDataId(reg.clone(), data_id)), diff --git a/sway-core/src/asm_generation/fuel/functions.rs b/sway-core/src/asm_generation/fuel/functions.rs index a811cfd81ef..ff14cce2217 100644 --- a/sway-core/src/asm_generation/fuel/functions.rs +++ b/sway-core/src/asm_generation/fuel/functions.rs @@ -850,7 +850,6 @@ impl FuelAsmBuilder<'_, '_> { self.context, constant.get_content(self.context), EntryName::NonConfigurable, - None, )); self.ptr_map.insert(*ptr, Storage::Data(data_id)); } @@ -881,7 +880,6 @@ impl FuelAsmBuilder<'_, '_> { self.context, constant.get_content(self.context), EntryName::NonConfigurable, - None, )); init_mut_vars.push(InitMutVars { diff --git a/sway-core/src/asm_generation/fuel/optimizations.rs b/sway-core/src/asm_generation/fuel/optimizations.rs index ac012a36bfc..9a10553ae83 100644 --- a/sway-core/src/asm_generation/fuel/optimizations.rs +++ b/sway-core/src/asm_generation/fuel/optimizations.rs @@ -131,7 +131,7 @@ impl AbstractInstructionSet { } } VirtualOp::LoadDataId(dest, data_id) => { - if let Some(c) = data_section.get_data_word(data_id) { + if let Some(c) = data_section.get_reg_value(*data_id) { reg_contents.insert(dest.clone(), RegContents::Constant(c)); } else { reg_contents.remove(dest); diff --git a/sway-core/src/asm_generation/fuel/programs/abstract.rs b/sway-core/src/asm_generation/fuel/programs/abstract.rs index 50748ede023..63dc63f7756 100644 --- a/sway-core/src/asm_generation/fuel/programs/abstract.rs +++ b/sway-core/src/asm_generation/fuel/programs/abstract.rs @@ -302,7 +302,6 @@ impl AbstractProgram { let data_label = self.data_section.insert_data_value(Entry::new_word( u32::from_be_bytes(selector) as u64, EntryName::NonConfigurable, - None, )); // Load the data into a register for comparison. diff --git a/sway-core/src/asm_generation/fuel/programs/allocated.rs b/sway-core/src/asm_generation/fuel/programs/allocated.rs index a9f69de7f34..ffb99a161f6 100644 --- a/sway-core/src/asm_generation/fuel/programs/allocated.rs +++ b/sway-core/src/asm_generation/fuel/programs/allocated.rs @@ -9,7 +9,7 @@ use crate::{ ProgramKind, }, asm_lang::Label, - decl_engine::DeclRefFunction, + decl_engine::DeclRefFunction, OptLevel, }; /// An [AllocatedProgram] represents code which has allocated registers but still has abstract @@ -23,7 +23,7 @@ pub(crate) struct AllocatedProgram { } impl AllocatedProgram { - pub(crate) fn into_final_program(mut self) -> Result { + pub(crate) fn into_final_program(mut self, optimization: OptLevel) -> Result { // Concat the prologue and all the functions together. let abstract_ops = AllocatedAbstractInstructionSet { ops: std::iter::once(self.prologue.ops) @@ -32,8 +32,13 @@ impl AllocatedProgram { .collect(), }; + // Prepare data section for label realization + let mut data_section = self.data_section + .pack(optimization); + + // Realize the labels in the program. let (realized_ops, mut label_offsets) = - abstract_ops.realize_labels(&mut self.data_section)?; + abstract_ops.realize_labels(&mut data_section)?; let ops = realized_ops.allocated_ops(); // Collect the entry point offsets. @@ -51,7 +56,7 @@ impl AllocatedProgram { Ok(FinalProgram { kind: self.kind, - data_section: self.data_section, + data_section: data_section.finalize(), ops, entries, }) diff --git a/sway-core/src/asm_generation/fuel/programs/final.rs b/sway-core/src/asm_generation/fuel/programs/final.rs index 2bc115436ef..38e0ee9fc67 100644 --- a/sway-core/src/asm_generation/fuel/programs/final.rs +++ b/sway-core/src/asm_generation/fuel/programs/final.rs @@ -1,6 +1,6 @@ use crate::{ asm_generation::{ - fuel::data_section::DataSection, instruction_set::InstructionSet, ProgramKind, + fuel::data_section::FinalDataSection, instruction_set::InstructionSet, ProgramKind, }, asm_lang::allocated_ops::AllocatedOp, decl_engine::DeclRefFunction, @@ -12,7 +12,7 @@ use super::{FnName, ImmOffset, SelectorOpt}; /// A [FinalProgram] represents code which may be serialized to VM bytecode. pub(crate) struct FinalProgram { pub(crate) kind: ProgramKind, - pub(crate) data_section: DataSection, + pub(crate) data_section: FinalDataSection, pub(crate) ops: Vec, pub(crate) entries: Vec<(SelectorOpt, ImmOffset, FnName, Option)>, } diff --git a/sway-core/src/asm_lang/allocated_ops.rs b/sway-core/src/asm_lang/allocated_ops.rs index bde88766d33..aac5817fe67 100644 --- a/sway-core/src/asm_lang/allocated_ops.rs +++ b/sway-core/src/asm_lang/allocated_ops.rs @@ -13,7 +13,7 @@ use super::*; use crate::{ asm_generation::fuel::{ compiler_constants::DATA_SECTION_REGISTER, - data_section::{DataId, DataSection}, + data_section::{DataId, DataSection, PackedDataSection}, }, fuel_prelude::fuel_asm::{self, op}, }; @@ -605,7 +605,7 @@ impl AllocatedOp { &self, offset_to_data_section: u64, offset_from_instr_start: u64, - data_section: &DataSection, + data_section: &PackedDataSection, ) -> FuelAsmData { use AllocatedOpcode::*; FuelAsmData::Instructions(vec![match &self.opcode { @@ -852,90 +852,85 @@ fn addr_of( /// Converts a virtual load word instruction which uses data labels into one which uses /// actual bytewise offsets for use in bytecode. +/// Copy-types are loaded directly into the register. For non-copy types, the register +/// will instead contain a pointer to the value in the data section. /// Returns one op if the type is less than one word big, but two ops if it has to construct /// a pointer and add it to $is. fn realize_load( dest: &AllocatedRegister, data_id: &DataId, - data_section: &DataSection, + data_section: &PackedDataSection, offset_to_data_section: u64, offset_from_instr_start: u64, ) -> Vec { - // if this data is larger than a word, instead of loading the data directly - // into the register, we want to load a pointer to the data into the register - // this appends onto the data section and mutates it by adding the pointer as a literal - let has_copy_type = data_section.has_copy_type(data_id).expect( - "Internal miscalculation in data section -- data id did not match up to any actual data", - ); - - let is_byte = data_section.is_byte(data_id).expect( - "Internal miscalculation in data section -- data id did not match up to any actual data", - ); - - // all data is word-aligned right now, and `offset_to_id` returns the offset in bytes - let offset_bytes = data_section.data_id_to_offset(data_id) as u64; - assert!( - offset_bytes % 8 == 0, - "Internal miscalculation in data section -- data offset is not aligned to a word", - ); - let offset_words = offset_bytes / 8; - - let imm = VirtualImmediate12::new( - if is_byte { offset_bytes } else { offset_words }, - Span::new(" ".into(), 0, 0, None).unwrap(), - ); - let offset = match imm { - Ok(value) => value, - Err(_) => panic!( - "Unable to offset into the data section more than 2^12 bits. \ - Unsupported data section length: {} words.", - offset_words - ), - }; - - if !has_copy_type { - // load the pointer itself into the register. `offset_to_data_section` is in bytes. - // The -4 is because $pc is added in the *next* instruction. - let pointer_offset_from_current_instr = - offset_to_data_section - offset_from_instr_start + offset_bytes - 4; - - // insert the pointer as bytes as a new data section entry at the end of the data - let data_id_for_pointer = data_section - .data_id_of_pointer(pointer_offset_from_current_instr) - .expect("Pointer offset must be in data_section"); - - // now load the pointer we just created into the `dest`ination - let mut buf = Vec::with_capacity(2); - buf.append(&mut realize_load( - dest, - &data_id_for_pointer, - data_section, - offset_to_data_section, - offset_from_instr_start, - )); - // add $pc to the pointer since it is relative to the current instruction. - buf.push( - fuel_asm::op::ADD::new( - dest.to_reg_id(), - dest.to_reg_id(), - ConstantRegister::ProgramCounter.to_reg_id(), - ) - .into(), - ); - buf - } else if is_byte { - vec![fuel_asm::op::LB::new( - dest.to_reg_id(), - fuel_asm::RegId::new(DATA_SECTION_REGISTER), - offset.value().into(), - ) - .into()] - } else { - vec![fuel_asm::op::LW::new( - dest.to_reg_id(), - fuel_asm::RegId::new(DATA_SECTION_REGISTER), - offset.value().into(), - ) - .into()] - } + // let offset_bytes = data_section.data_id_to_offset(data_id); + + // let has_copy_type = data_section.has_copy_type(data_id).expect( + // "Internal miscalculation in data section -- data id did not match up to any actual data", + // ); + + todo!(); + + // let size_bytes = data_section.size(data_id).expect( + // "Internal miscalculation in data section -- data id did not match up to any actual data", + // ); + + // let imm = VirtualImmediate12::new( + // if is_byte { offset_bytes } else { offset_words }, + // Span::new(" ".into(), 0, 0, None).unwrap(), + // ); + // let offset = match imm { + // Ok(value) => value, + // Err(_) => panic!( + // "Unable to offset into the data section more than 2^12 bits. \ + // Unsupported data section length: {} words.", + // offset_words + // ), + // }; + + // if !has_copy_type { + // // load the pointer itself into the register. `offset_to_data_section` is in bytes. + // // The -4 is because $pc is added in the *next* instruction. + // let pointer_offset_from_current_instr = + // offset_to_data_section - offset_from_instr_start + offset_bytes - 4; + + // // insert the pointer as bytes as a new data section entry at the end of the data + // let data_id_for_pointer = data_section + // .data_id_of_pointer(pointer_offset_from_current_instr) + // .expect("Pointer offset must be in data_section"); + + // // now load the pointer we just created into the `dest`ination + // let mut buf = Vec::with_capacity(2); + // buf.append(&mut realize_load( + // dest, + // &data_id_for_pointer, + // data_section, + // offset_to_data_section, + // offset_from_instr_start, + // )); + // // add $pc to the pointer since it is relative to the current instruction. + // buf.push( + // fuel_asm::op::ADD::new( + // dest.to_reg_id(), + // dest.to_reg_id(), + // ConstantRegister::ProgramCounter.to_reg_id(), + // ) + // .into(), + // ); + // buf + // } else if is_byte { + // vec![fuel_asm::op::LB::new( + // dest.to_reg_id(), + // fuel_asm::RegId::new(DATA_SECTION_REGISTER), + // offset.value().into(), + // ) + // .into()] + // } else { + // vec![fuel_asm::op::LW::new( + // dest.to_reg_id(), + // fuel_asm::RegId::new(DATA_SECTION_REGISTER), + // offset.value().into(), + // ) + // .into()] + // } } diff --git a/sway-core/src/build_config.rs b/sway-core/src/build_config.rs index a87591e7764..5887e5b5d4d 100644 --- a/sway-core/src/build_config.rs +++ b/sway-core/src/build_config.rs @@ -40,8 +40,10 @@ impl BuildTarget { #[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum OptLevel { + /// Debug #[default] Opt0 = 0, + /// Release Opt1 = 1, } diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index e70e212d1df..70bc441e8e9 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -1222,7 +1222,7 @@ pub fn compile_to_bytecode( )?; asm_to_bytecode( handler, - &mut asm_res, + asm_res, source_map, engines.se(), build_config, @@ -1254,7 +1254,7 @@ pub fn set_bytecode_configurables_offset( /// Given the assembly (opcodes), compile to [CompiledBytecode], containing the asm in bytecode form. pub fn asm_to_bytecode( handler: &Handler, - asm: &mut CompiledAsm, + asm: CompiledAsm, source_map: &mut SourceMap, source_engine: &SourceEngine, build_config: &BuildConfig, diff --git a/sway-ir/src/constant.rs b/sway-ir/src/constant.rs index 6b5bf62550c..da11e1ce29e 100644 --- a/sway-ir/src/constant.rs +++ b/sway-ir/src/constant.rs @@ -2,7 +2,7 @@ use std::hash::{Hash, Hasher}; -use crate::{context::Context, irtype::Type, pretty::DebugWithContext, value::Value, Padding}; +use crate::{context::Context, irtype::Type, pretty::DebugWithContext, value::Value}; use rustc_hash::FxHasher; use sway_types::u256::U256; @@ -68,11 +68,6 @@ pub enum ConstantValue { RawUntypedSlice(Vec), } -/// A [Constant] with its required [Padding]. -/// If the [Padding] is `None` the default [Padding] for the -/// [Constant] type is expected. -type ConstantWithPadding<'a> = (&'a ConstantContent, Option); - impl ConstantContent { pub fn new_unit(context: &Context) -> Self { ConstantContent { @@ -195,116 +190,6 @@ impl ConstantContent { Value::new_constant(context, new_const) } - /// Returns the tag and the value of an enum constant if `self` is an enum constant, - /// otherwise `None`. - fn extract_enum_tag_and_value( - &self, - context: &Context, - ) -> Option<(&ConstantContent, &ConstantContent)> { - if !self.ty.is_enum(context) { - return None; - } - - let elems = match &self.value { - ConstantValue::Struct(elems) if elems.len() == 2 => elems, - _ => return None, // This should never be the case. If we have an enum, it is a struct with exactly two elements. - }; - - Some((&elems[0], &elems[1])) - } - - /// Returns enum tag and value as [Constant]s, together with their [Padding]s, - /// if `self` is an enum [Constant], otherwise `None`. - pub fn enum_tag_and_value_with_paddings( - &self, - context: &Context, - ) -> Option<(ConstantWithPadding, ConstantWithPadding)> { - if !self.ty.is_enum(context) { - return None; - } - - let tag_and_value_with_paddings = self - .elements_of_aggregate_with_padding(context) - .expect("Enums are aggregates."); - - debug_assert!(tag_and_value_with_paddings.len() == 2, "In case of enums, `elements_of_aggregate_with_padding` must return exactly two elements, the tag and the value."); - - let tag = tag_and_value_with_paddings[0].clone(); - let value = tag_and_value_with_paddings[1].clone(); - - Some((tag, value)) - } - - /// Returns elements of an array with the expected padding for each array element - /// if `self` is an array [Constant], otherwise `None`. - pub fn array_elements_with_padding( - &self, - context: &Context, - ) -> Option> { - if !self.ty.is_array(context) { - return None; - } - - self.elements_of_aggregate_with_padding(context) - } - - /// Returns fields of a struct with the expected padding for each field - /// if `self` is a struct [Constant], otherwise `None`. - pub fn struct_fields_with_padding( - &self, - context: &Context, - ) -> Option> { - if !self.ty.is_struct(context) { - return None; - } - - self.elements_of_aggregate_with_padding(context) - } - - /// Returns elements of an aggregate constant with the expected padding for each element - /// if `self` is an aggregate (struct, enum, or array), otherwise `None`. - /// If the returned [Padding] is `None` the default [Padding] for the type - /// is expected. - /// If the aggregate constant is an enum, the returned [Vec] has exactly two elements, - /// the first being the tag and the second the value of the enum variant. - fn elements_of_aggregate_with_padding( - &self, - context: &Context, - ) -> Option)>> { - // We need a special handling in case of enums. - if let Some((tag, value)) = self.extract_enum_tag_and_value(context) { - let tag_with_padding = (tag, None); - - // Enum variants are left padded to the word boundary, and the size - // of each variant is the size of the union. - // We know we have an enum here, means exactly two fields in the struct - // second of which is the union. - let target_size = self.ty.get_field_types(context)[1] - .size(context) - .in_bytes_aligned() as usize; - - let value_with_padding = (value, Some(Padding::Left { target_size })); - - return Some(vec![tag_with_padding, value_with_padding]); - } - - match &self.value { - // Individual array elements do not have additional padding. - ConstantValue::Array(elems) => Some(elems.iter().map(|el| (el, None)).collect()), - // Each struct field is right padded to the word boundary. - ConstantValue::Struct(elems) => Some( - elems - .iter() - .map(|el| { - let target_size = el.ty.size(context).in_bytes_aligned() as usize; - (el, Some(Padding::Right { target_size })) - }) - .collect(), - ), - _ => None, - } - } - /// Compare two Constant values. Can't impl PartialOrder because of context. pub fn eq(&self, context: &Context, other: &Self) -> bool { self.ty.eq(context, &other.ty) diff --git a/sway-ir/src/irtype.rs b/sway-ir/src/irtype.rs index 40165fa91a4..57c5085a3d2 100644 --- a/sway-ir/src/irtype.rs +++ b/sway-ir/src/irtype.rs @@ -259,6 +259,11 @@ impl Type { matches!(*self.get_content(context), TypeContent::Uint(8)) } + /// Is u16 type + pub fn is_uint16(&self, context: &Context) -> bool { + matches!(*self.get_content(context), TypeContent::Uint(16)) + } + /// Is u32 type pub fn is_uint32(&self, context: &Context) -> bool { matches!(*self.get_content(context), TypeContent::Uint(32)) @@ -637,54 +642,6 @@ impl TypeSize { } } -/// Provides information about padding expected when laying values in memory. -/// Padding depends on the type of the value, but also on the embedding of -/// the value in aggregates. E.g., in an array of `u8`, each `u8` is "padded" -/// to its size of one byte while as a struct field, it will be right padded -/// to 8 bytes. -#[derive(Clone, Debug, serde::Serialize)] -pub enum Padding { - Left { target_size: usize }, - Right { target_size: usize }, -} - -impl Padding { - /// Returns the default [Padding] for `u8`. - pub fn default_for_u8(_value: u8) -> Self { - // Dummy _value is used only to ensure correct usage at the call site. - Self::Right { target_size: 1 } - } - - /// Returns the default [Padding] for `u64`. - pub fn default_for_u64(_value: u64) -> Self { - // Dummy _value is used only to ensure correct usage at the call site. - Self::Right { target_size: 8 } - } - - /// Returns the default [Padding] for a byte array. - pub fn default_for_byte_array(value: &[u8]) -> Self { - Self::Right { - target_size: value.len(), - } - } - - /// Returns the default [Padding] for an aggregate. - /// `aggregate_size` is the overall size of the aggregate in bytes. - pub fn default_for_aggregate(aggregate_size: usize) -> Self { - Self::Right { - target_size: aggregate_size, - } - } - - /// The target size in bytes. - pub fn target_size(&self) -> usize { - use Padding::*; - match self { - Left { target_size } | Right { target_size } => *target_size, - } - } -} - #[cfg(test)] mod tests { pub use super::*;