Skip to content
Draft
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
12 changes: 10 additions & 2 deletions forc-test/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,21 @@ fn find_jump_instruction_index(bytecode: &[u8]) -> usize {
// This will be `__entry` for script/predicate/contract using encoding v1;
// `main` for script/predicate using encoding v0;
// or the first function for libraries
// MOVE R59 $sp ;; [26, 236, 80, 0]
let a = vm::fuel_asm::op::move_(59, fuel_asm::RegId::SP).to_bytes();
// MOVE $locals_base $sp ;; [26, <locals_base>, 80, 0]
//
// The destination register is the compiler-reserved `$locals_base`, whose number
// is an internal compiler detail that can change; read it from sway-core instead
// of hardcoding it (it was hardcoded to `59` previously, which broke when the
// reserved-register chain shifted).
let a =
vm::fuel_asm::op::move_(sway_core::LOCALS_BASE_REGISTER, fuel_asm::RegId::SP).to_bytes();

// for contracts using encoding v0
// search the first `lw $r0 $fp i73`
// which is the start of the fn selector
// LW $writable $fp 0x49 ;; [93, 64, 96, 73]
// (`WRITABLE`/`$fp` are VM registers and `73` is a frame offset immediate, neither
// of which depend on the compiler's reserved-register layout.)
let b = vm::fuel_asm::op::lw(fuel_asm::RegId::WRITABLE, fuel_asm::RegId::FP, 73).to_bytes();

bytecode
Expand Down
34 changes: 18 additions & 16 deletions sway-core/src/asm_generation/fuel/compiler_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@ pub(crate) const FLAGS_REGISTER: u8 = 0xF;
/// This is the number of registers reserved by the compiler. Adjust this number if a new
/// reservation must be made.
/// So far, the compiler-reserved registers are:
/// 1. DATA_SECTION_BEGIN - the offset to the read only data section.
/// 2. RETURN_ADDRESS - where a function must return to.
/// 3. RETURN_VALUE - the value returned by a _function_ call.
/// 4. SCRATCH - used for certain operations which need a register temporarily, such as JMP.
/// 5. LOCALS_BASE - base register for stack locals.
/// 6. ARGS - for passing arguments to function calls.
/// 1. CONFIGURABLE_SECTION_BEGIN - the offset to the configurable section.
/// 2. DATA_SECTION_BEGIN - the offset to the read only data section.
/// 3. RETURN_ADDRESS - where a function must return to.
/// 4. RETURN_VALUE - the value returned by a _function_ call.
/// 5. SCRATCH - used for certain operations which need a register temporarily, such as JMP.
/// 6. LOCALS_BASE - base register for stack locals.
/// 7. ARGS - for passing arguments to function calls.
pub(crate) const DATA_SECTION_REGISTER: u8 = NUM_TOTAL_REGISTERS - 1;
pub(crate) const RETURN_ADDRESS_REGISTER: u8 = NUM_TOTAL_REGISTERS - 2;
pub(crate) const RETURN_VALUE_REGISTER: u8 = NUM_TOTAL_REGISTERS - 3;
pub(crate) const SCRATCH_REGISTER: u8 = NUM_TOTAL_REGISTERS - 4;
pub(crate) const LOCALS_BASE: u8 = NUM_TOTAL_REGISTERS - 5;
pub(crate) const CONFIGURABLE_SECTION_REGISTER: u8 = NUM_TOTAL_REGISTERS - 2;
pub(crate) const RETURN_ADDRESS_REGISTER: u8 = NUM_TOTAL_REGISTERS - 3;
pub(crate) const RETURN_VALUE_REGISTER: u8 = NUM_TOTAL_REGISTERS - 4;
pub(crate) const SCRATCH_REGISTER: u8 = NUM_TOTAL_REGISTERS - 5;
pub(crate) const LOCALS_BASE: u8 = NUM_TOTAL_REGISTERS - 6;

pub(crate) const NUM_ARG_REGISTERS: u8 = 6;
pub(crate) const ARG_REG0: u8 = NUM_TOTAL_REGISTERS - 6;
pub(crate) const ARG_REG1: u8 = NUM_TOTAL_REGISTERS - 7;
pub(crate) const ARG_REG2: u8 = NUM_TOTAL_REGISTERS - 8;
pub(crate) const ARG_REG3: u8 = NUM_TOTAL_REGISTERS - 9;
pub(crate) const ARG_REG4: u8 = NUM_TOTAL_REGISTERS - 10;
pub(crate) const ARG_REG5: u8 = NUM_TOTAL_REGISTERS - 11;
pub(crate) const ARG_REG0: u8 = NUM_TOTAL_REGISTERS - 7;
pub(crate) const ARG_REG1: u8 = NUM_TOTAL_REGISTERS - 8;
pub(crate) const ARG_REG2: u8 = NUM_TOTAL_REGISTERS - 9;
pub(crate) const ARG_REG3: u8 = NUM_TOTAL_REGISTERS - 10;
pub(crate) const ARG_REG4: u8 = NUM_TOTAL_REGISTERS - 11;
pub(crate) const ARG_REG5: u8 = NUM_TOTAL_REGISTERS - 12;

// Both are inclusive
pub(crate) const UPPER_ALLOCATABLE_REGISTER: u8 = ARG_REG5 - 1;
Expand Down
26 changes: 23 additions & 3 deletions sway-core/src/asm_generation/fuel/data_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ pub enum DataIdEntryKind {
impl fmt::Display for DataIdEntryKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataIdEntryKind::NonConfigurable => write!(f, "NonConfigurable"),
DataIdEntryKind::Configurable => write!(f, "Configurable"),
DataIdEntryKind::NonConfigurable => write!(f, "non_configurable"),
DataIdEntryKind::Configurable => write!(f, "configurable"),
}
}
}
Expand Down Expand Up @@ -292,6 +292,23 @@ impl DataSection {
self.absolute_idx_to_offset(idx)
}

/// Given a configurable [DataId], calculate the offset _from the beginning of the
/// configurable section_ to the data in bytes. This excludes the non-configurables
/// prefix and is meant to be used together with `CONFIGURABLE_SECTION_REGISTER`
/// (`$cs`), which points at the start of the configurable section.
pub(crate) fn configurable_offset_within_section(&self, id: &DataId) -> usize {
debug_assert!(matches!(id.kind, DataIdEntryKind::Configurable));
// `id.idx` indexes into `configurables`; sum the (word-aligned) sizes of the
// entries that precede it.
self.configurables
.iter()
.take(id.idx as usize)
.fold(0, |offset, entry| {
// entries must be word aligned
size_bytes_round_up_to_word_alignment!(offset + entry.to_bytes().len())
})
}

/// 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 {
Expand Down Expand Up @@ -416,7 +433,10 @@ impl fmt::Display for DataSection {
writeln!(
data_buf,
"data_{}_{} {}",
entry.name,
match &entry.name {
EntryName::NonConfigurable => "non_configurable".to_string(),
EntryName::Configurable(name) => format!("configurable_{}", name),
},
ix,
display_entry(&entry.value)
)?;
Expand Down
179 changes: 124 additions & 55 deletions sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use sway_error::{
handler::{ErrorEmitted, Handler},
warning::CompileWarning,
warning::Warning,
OkOrIceInternal,
};
use sway_ir::*;
use sway_types::{span::Span, Spanned};
Expand All @@ -45,8 +46,10 @@ pub struct FuelAsmBuilder<'ir, 'eng> {
// Globals will be allocated at SSP uninitialized (they will not be zeroed)
pub(super) globals_section: GlobalsSection,

// Maps configurable name to data id, only used by encoding v0
pub(super) configurable_v0_data_id: HashMap<String, DataId>,
// Maps a configurable name to its DataId in the data section, used by
// v0 configurables and trivially decodable encoding-v1 configurables.
// Non-trivial v1 configurables are NOT here, because they live in globals instead.
pub(super) trivial_configurable_to_data_id: HashMap<String, DataId>,

// Register sequencer dishes out new registers and labels.
pub(super) reg_seqr: RegisterSequencer,
Expand Down Expand Up @@ -106,7 +109,7 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
None,
);
let dataid = self.data_section.insert_data_value(entry);
self.configurable_v0_data_id.insert(name.clone(), dataid);
self.trivial_configurable_to_data_id.insert(name.clone(), dataid);
}
ConfigContent::V1 {
name,
Expand All @@ -115,56 +118,68 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
decode_fn,
..
} => {
let size_in_bytes = ty.size(self.context).in_bytes();

self.globals_section.insert(name, size_in_bytes);
let global = self.globals_section.get_by_name(name).unwrap();

let (decode_fn_label, _) = self.func_label_map.get(&decode_fn.get()).unwrap();
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 {
opcode: Either::Left(VirtualOp::AddrDataId(
VirtualRegister::Constant(ConstantRegister::FuncArg0),
dataid,
)),
comment: format!("get pointer to configurable {name} default value"),
owning_span: None,
});
if let Some(decode_fn) = decode_fn.get() {
// A v1 configurable with a decode fn is non-trivial
// so allocate a writable global and decode the
// encoded bytes into it at program start.
let size_in_bytes = ty.size(self.context).in_bytes();

self.before_entries.push(Op {
opcode: Either::Left(VirtualOp::ADDI(
VirtualRegister::Constant(ConstantRegister::FuncArg1),
VirtualRegister::Constant(ConstantRegister::Zero),
VirtualImmediate12::new(encoded_bytes.len() as u64),
)),
comment: format!("get length of configurable {name} default value"),
owning_span: None,
});
self.globals_section.insert(name, size_in_bytes);
let global = self.globals_section.get_by_name(name).unwrap();

self.before_entries.push(Op {
opcode: Either::Left(VirtualOp::ADDI(
VirtualRegister::Constant(ConstantRegister::FuncArg2),
VirtualRegister::Constant(ConstantRegister::StackStartPointer),
VirtualImmediate12::new(global.offset_in_bytes),
)),
comment: format!("get pointer to configurable {name} stack address"),
owning_span: None,
});
let (decode_fn_label, _) = self.func_label_map.get(&decode_fn).unwrap();

// call decode
self.before_entries.push(Op {
opcode: Either::Right(crate::asm_lang::ControlFlowOp::Jump {
to: *decode_fn_label,
type_: JumpType::Call,
}),
comment: format!("decode configurable {name}"),
owning_span: None,
});
self.before_entries.push(Op {
opcode: Either::Left(VirtualOp::AddrDataId(
VirtualRegister::Constant(ConstantRegister::FuncArg0),
dataid,
)),
comment: format!("get pointer to configurable {name} default value"),
owning_span: None,
});

self.before_entries.push(Op {
opcode: Either::Left(VirtualOp::ADDI(
VirtualRegister::Constant(ConstantRegister::FuncArg1),
VirtualRegister::Constant(ConstantRegister::Zero),
VirtualImmediate12::new(encoded_bytes.len() as u64),
)),
comment: format!("get length of configurable {name} default value"),
owning_span: None,
});

self.before_entries.push(Op {
opcode: Either::Left(VirtualOp::ADDI(
VirtualRegister::Constant(ConstantRegister::FuncArg2),
VirtualRegister::Constant(ConstantRegister::StackStartPointer),
VirtualImmediate12::new(global.offset_in_bytes),
)),
comment: format!("get pointer to configurable {name} stack address"),
owning_span: None,
});

// call decode
self.before_entries.push(Op {
opcode: Either::Right(crate::asm_lang::ControlFlowOp::Jump {
to: *decode_fn_label,
type_: JumpType::Call,
}),
comment: format!("decode configurable {name}"),
owning_span: None,
});
} else {
// A v1 configurable without a decode fn is trivial,
// so no writable global and no decode call.
// get_config resolves this name to AddrDataId, pointing to
// the encoded bytes directly.
self.trivial_configurable_to_data_id.insert(name.clone(), dataid);
}
}
}
}
Expand Down Expand Up @@ -192,9 +207,18 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
entries,
non_entries,
before_entries: before_entry,
trivial_configurable_to_data_id,
..
} = self;

// The configurable-section register `$cs` only needs to be initialized in
// the prologue when the program actually has a trivially-addressable
// configurable (V0, or a trivially-decodable V1). Non-trivial V1
// configurables are addressed via `$ds` (decode prologue) and `$ssp`
// (`get_config`), and do not populate this map, so they correctly do not
// trigger the `$cs` initialization.
let needs_configurable_register = !trivial_configurable_to_data_id.is_empty();

let opt_level = build_config
.map(|cfg| cfg.optimization_level)
.unwrap_or_default();
Expand Down Expand Up @@ -253,6 +277,7 @@ impl AsmBuilder for FuelAsmBuilder<'_, '_> {
non_entries,
reg_seqr,
context.experimental,
needs_configurable_register,
);

// Compiled dependencies will not have any content and we
Expand Down Expand Up @@ -308,7 +333,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {
program_kind,
data_section,
globals_section: GlobalsSection::default(),
configurable_v0_data_id: HashMap::default(),
trivial_configurable_to_data_id: HashMap::default(),
reg_seqr,
func_label_map: HashMap::new(),
block_label_map: HashMap::new(),
Expand Down Expand Up @@ -1453,16 +1478,60 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> {
});
self.reg_map.insert(*addr_val, addr_reg);
} else {
// Otherwise it is a configurable with encoding v0 and must be at configurable_v0_data_id
let dataid = self.configurable_v0_data_id.get(name).unwrap();
self.cur_bytecode.push(Op {
opcode: either::Either::Left(VirtualOp::AddrDataId(
addr_reg.clone(),
dataid.clone(),
)),
comment: format!("get address of configurable {name}"),
owning_span: self.md_mgr.val_to_span(self.context, *addr_val),
});
// Otherwise it is a configurable without a writable global: either an
// encoding v0 configurable or a trivially decodable encoding-v1
// configurable. Address it through the configurable section register
// (`$cs`), which points at the start of the configurable section, using an
// offset measured from there.
let span = self
.md_mgr
.val_to_span(self.context, *addr_val)
.unwrap_or_else(Span::dummy);
let dataid = self
.trivial_configurable_to_data_id
.get(name)
.ok_or_ice_internal(
"Trivial configurable referenced by get_config was not registered in the data section.",
span.clone(),
)?;
let offset_within_configurables =
self.data_section.configurable_offset_within_section(dataid) as u64;
match VirtualImmediate12::try_new(offset_within_configurables, span.clone()) {
Ok(imm12) => {
// Single op: ADDI addr_reg, $cs, <offset>
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::ADDI(
addr_reg.clone(),
VirtualRegister::Constant(ConstantRegister::ConfigurableSectionStart),
imm12,
)),
comment: format!("get address of configurable {name}"),
owning_span: Some(span),
});
}
Err(_) => {
// Offset doesn't fit in 12 bits: MOVI addr_reg, <offset>;
// ADD addr_reg, addr_reg, $cs
let imm18 =
VirtualImmediate18::try_new(offset_within_configurables, span.clone())?;
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::MOVI(addr_reg.clone(), imm18)),
comment: format!(
"get offset of configurable {name} within configurable section"
),
owning_span: Some(span.clone()),
});
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::ADD(
addr_reg.clone(),
addr_reg.clone(),
VirtualRegister::Constant(ConstantRegister::ConfigurableSectionStart),
)),
comment: format!("get address of configurable {name}"),
owning_span: Some(span),
});
}
}
self.reg_map.insert(*addr_val, addr_reg);
}

Expand Down
Loading
Loading