Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
} 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.
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