Skip to content
Open
78 changes: 62 additions & 16 deletions sway-core/src/asm_generation/finalized_asm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::instruction_set::InstructionSet;
use super::{
fuel::{checks, data_section::DataSection},
fuel::{checks, compiler_constants::TWELVE_BITS, data_section::DataSection},
ProgramABI, ProgramKind,
};
use crate::asm_generation::fuel::data_section::{Datum, Entry, EntryName};
Expand Down Expand Up @@ -120,14 +120,41 @@ fn to_bytecode_mut(
source_engine: &SourceEngine,
build_config: &BuildConfig,
) -> CompiledBytecode {
fn op_size_in_bytes(data_section: &DataSection, item: &AllocatedOp) -> u64 {
// Snapshot non-configurable size before pointer pre-insertion mutates the data section.
// Pointers are appended to non_configurables, so their offsets are relative to this.
let base_non_config_size = data_section.non_configurables_size_in_bytes() as u64;

// Count non-copy loads to compute worst-case pointer offset (last pointer inserted).
let num_non_copy_loads = ops
.iter()
.filter(|op| {
matches!(&op.opcode, AllocatedInstruction::LoadDataId(_, label)
if !data_section.has_copy_type(label).unwrap_or(true))
})
.count() as u64;
let worst_pointer_word_offset =
(base_non_config_size + num_non_copy_loads.saturating_sub(1) * 8) / 8;

fn op_size_in_bytes(
data_section: &DataSection,
worst_pointer_word_offset: u64,
item: &AllocatedOp,
) -> u64 {
match &item.opcode {
AllocatedInstruction::LoadDataId(_reg, data_label)
if !data_section
AllocatedInstruction::LoadDataId(_reg, data_label) => {
let has_copy_type = data_section
.has_copy_type(data_label)
.expect("data label references non existent data -- internal error") =>
{
8
.expect("data label references non existent data -- internal error");
if has_copy_type {
let offset_bytes = data_section.data_id_to_offset(data_label) as u64;
let is_byte = data_section.is_byte(data_label).unwrap();
let imm_value = if is_byte { offset_bytes } else { offset_bytes / 8 };
if imm_value > TWELVE_BITS { 12 } else { 4 }
Comment thread
Dnreikronos marked this conversation as resolved.
Outdated
} else {
// Use worst-case pointer offset: if any pointer might exceed 12 bits,
// all non-copy loads use the large form for consistent sizing.
if worst_pointer_word_offset > TWELVE_BITS { 16 } else { 8 }
}
Comment thread
Dnreikronos marked this conversation as resolved.
}
AllocatedInstruction::AddrDataId(_, _data_id) => 8,
AllocatedInstruction::ConfigurablesOffsetPlaceholder => 8,
Expand All @@ -140,9 +167,9 @@ fn to_bytecode_mut(

// Some instructions may be omitted or expanded into multiple instructions, so we compute,
// using `op_size_in_bytes`, exactly how many ops will be generated to calculate the offset.
let mut offset_to_data_section_in_bytes = ops
.iter()
.fold(0, |acc, item| acc + op_size_in_bytes(data_section, item));
let mut offset_to_data_section_in_bytes = ops.iter().fold(0, |acc, item| {
acc + op_size_in_bytes(data_section, worst_pointer_word_offset, item)
});

// A noop is inserted in ASM generation if required, to word-align the data section.
let mut ops_padded = Vec::new();
Expand All @@ -160,6 +187,7 @@ fn to_bytecode_mut(
&ops_padded
};

let mut num_pointers_inserted: u64 = 0;
let mut offset_from_instr_start = 0;
for op in ops.iter() {
match &op.opcode {
Expand All @@ -173,14 +201,31 @@ fn to_bytecode_mut(
// so that when we take addresses of configurables, that address doesn't change
// later on if a non-configurable is added to the data-section.
let offset_bytes = data_section.data_id_to_offset(data_label) as u64;
// The -4 is because $pc is added in the *next* instruction.
let pointer_offset_from_current_instr =
offset_to_data_section_in_bytes - offset_from_instr_start + offset_bytes - 4;
data_section.append_pointer(pointer_offset_from_current_instr);

// The pointer entry will be appended at the end of non_configurables.
// Predict its word offset to determine the inner load instruction count.
let pointer_word_offset =
(base_non_config_size + num_pointers_inserted * 8) / 8;
let inner_instr_bytes =
if pointer_word_offset > TWELVE_BITS { 12 } else { 4 };

// $pc is read by the ADD instruction that follows the inner load.
// The correction accounts for the inner load's instruction count.
let pointer_offset_from_current_instr = offset_to_data_section_in_bytes
- offset_from_instr_start
+ offset_bytes
- inner_instr_bytes;
data_section.append_pointer(
pointer_offset_from_current_instr,
data_label,
offset_from_instr_start,
);
num_pointers_inserted += 1;
}
_ => (),
}
offset_from_instr_start += op_size_in_bytes(data_section, op);
offset_from_instr_start +=
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
op_size_in_bytes(data_section, worst_pointer_word_offset, op);
}

let mut bytecode = Vec::with_capacity(offset_to_data_section_in_bytes as usize);
Expand All @@ -205,7 +250,8 @@ fn to_bytecode_mut(
offset_from_instr_start,
data_section,
);
offset_from_instr_start += op_size_in_bytes(data_section, op);
offset_from_instr_start +=
op_size_in_bytes(data_section, worst_pointer_word_offset, op);

match fuel_op {
FuelAsmData::DatasectionOffset(data) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,14 +264,17 @@ impl AllocatedAbstractInstructionSet {
far_jump_sizes: &FxHashMap<usize, u64>,
) -> Result<(RealizedAbstractInstructionSet, LabeledBlocks), crate::CompileError> {
let label_offsets = self.resolve_labels(data_section);
let worst_ptr_offset = Self::worst_pointer_word_offset(&self.ops, data_section);
let mut curr_offset = 0;

let mut realized_ops = vec![];
for (op_idx, op) in self.ops.iter().enumerate() {
let op_size = far_jump_sizes
.get(&op_idx)
.copied()
.unwrap_or_else(|| Self::instruction_size_not_far_jump(op, data_section));
.unwrap_or_else(|| {
Self::instruction_size_not_far_jump(op, data_section, worst_ptr_offset)
});
let AllocatedAbstractOp {
opcode,
comment,
Expand Down Expand Up @@ -394,10 +397,11 @@ impl AllocatedAbstractInstructionSet {
match op.opcode {
Either::Right(Label(_)) => 0,

// Loads from data section may take up to 2 instructions
// Loads from data section may take up to 4 instructions
// (MOVI + ADD + LW/LB for copy-type, plus ADD $pc for non-copy)
Either::Left(
AllocatedInstruction::LoadDataId(_, _) | AllocatedInstruction::AddrDataId(_, _),
) => 2,
) => 4,

// cfei 0 and cfsi 0 are omitted from asm emission, don't count them for offsets
Either::Left(AllocatedInstruction::CFEI(ref op))
Expand Down Expand Up @@ -435,24 +439,49 @@ impl AllocatedAbstractInstructionSet {
}
}

// Actual size of an instruction.
// Note that this return incorrect values for far jumps, they must be handled separately.
// The return value is in concrete instructions, i.e. units of 4 bytes.
fn instruction_size_not_far_jump(op: &AllocatedAbstractOp, data_section: &DataSection) -> u64 {
fn worst_pointer_word_offset(
ops: &[AllocatedAbstractOp],
data_section: &DataSection,
) -> u64 {
let base = data_section.non_configurables_size_in_bytes() as u64;
let num_non_copy: u64 = ops
.iter()
.filter(|op| {
matches!(
&op.opcode,
either::Either::Left(AllocatedInstruction::LoadDataId(_, ref id))
if !data_section.has_copy_type(id).unwrap_or(true)
)
})
.count() as u64;
(base + num_non_copy.saturating_sub(1) * 8) / 8
}

// Actual size of an instruction (units of 4 bytes).
// Incorrect for far jumps — those are handled separately.
// Must be called before pointer pre-insertion in to_bytecode_mut;
// non-copy size estimates depend on non_configurables_size_in_bytes() being pointer-free.
fn instruction_size_not_far_jump(
op: &AllocatedAbstractOp,
data_section: &DataSection,
worst_pointer_word_offset: u64,
) -> u64 {
use ControlFlowOp::*;
match op.opcode {
Either::Right(Label(_)) => 0,

// A special case for LoadDataId which may be 1 or 2 ops, depending on the source size.
Either::Left(AllocatedInstruction::LoadDataId(_, ref 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",
);
if has_copy_type {
1
let offset_bytes = data_section.data_id_to_offset(data_id) as u64;
let is_byte = data_section.is_byte(data_id).unwrap();
let imm_value = if is_byte { offset_bytes } else { offset_bytes / 8 };
if imm_value > consts::TWELVE_BITS { 3 } else { 1 }
} else {
2
if worst_pointer_word_offset > consts::TWELVE_BITS { 4 } else { 2 }
}
}

Expand Down Expand Up @@ -593,6 +622,7 @@ impl AllocatedAbstractInstructionSet {
data_section: &DataSection,
far_jump_sizes: &FxHashMap<usize, u64>,
) -> LabeledBlocks {
let worst_ptr_offset = Self::worst_pointer_word_offset(&self.ops, data_section);
let mut labelled_blocks = LabeledBlocks::new();
let mut cur_offset = 0;
let mut cur_basic_block = None;
Expand All @@ -614,7 +644,9 @@ impl AllocatedAbstractInstructionSet {
let op_size = far_jump_sizes
.get(&op_idx)
.copied()
.unwrap_or_else(|| Self::instruction_size_not_far_jump(op, data_section));
.unwrap_or_else(|| {
Self::instruction_size_not_far_jump(op, data_section, worst_ptr_offset)
});
cur_offset += op_size;
}

Expand Down
33 changes: 24 additions & 9 deletions sway-core/src/asm_generation/fuel/data_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ impl Entry {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum DataIdEntryKind {
NonConfigurable,
Configurable,
Expand All @@ -235,7 +235,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, Debug, PartialEq, Eq, Hash)]
pub(crate) struct DataId {
pub(crate) idx: u32,
pub(crate) kind: DataIdEntryKind,
Expand All @@ -252,7 +252,7 @@ impl fmt::Display for DataId {
pub struct DataSection {
pub non_configurables: Vec<Entry>,
pub configurables: Vec<Entry>,
pub(crate) pointer_id: FxHashMap<u64, DataId>,
pub(crate) source_to_pointer: FxHashMap<(DataId, u64), DataId>,
}

impl DataSection {
Expand Down Expand Up @@ -301,6 +301,10 @@ impl DataSection {
})
}

pub(crate) fn non_configurables_size_in_bytes(&self) -> usize {
self.absolute_idx_to_offset(self.non_configurables.len())
}

pub(crate) fn serialize_to_bytes(&self) -> Vec<u8> {
// not the exact right capacity but serves as a lower bound
let mut buf = Vec::with_capacity(self.num_entries());
Expand Down Expand Up @@ -330,21 +334,32 @@ impl DataSection {
/// 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 {
pub(crate) fn append_pointer(
&mut self,
pointer_value: u64,
source_data_id: &DataId,
load_site_offset: 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());
self.source_to_pointer
.insert((source_data_id.clone(), load_site_offset), 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<DataId> {
self.pointer_id.get(&pointer_value).cloned()
/// Get the pointer [DataId] for a non-copy load at a specific instruction offset.
pub(crate) fn pointer_for_load_site(
&self,
source_data_id: &DataId,
load_site_offset: u64,
) -> Option<DataId> {
self.source_to_pointer
.get(&(source_data_id.clone(), load_site_offset))
.cloned()
}

/// Given any data in the form of a [Literal] (using this type mainly because it includes type
Expand Down
Loading