Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add push/pop instructions #498

Merged
merged 12 commits into from
Aug 8, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Description of the upcoming release here.

- [#486](https://github.com/FuelLabs/fuel-vm/pull/486/): Adds `ed25519` signature verification and `secp256r1` signature recovery to `fuel-crypto`, and corresponding opcodes `ED19` and `ECR1` to `fuel-vm`.

- [#486](https://github.com/FuelLabs/fuel-vm/pull/498): Adds `PSHL`, `PSHH`, `POPH` and `POPL` instructions, which allow cheap push and pop stack operations with multiple registers.

- [#500](https://github.com/FuelLabs/fuel-vm/pull/500) Introduced `ParallelExecutor` trait
and made available async versions of verify and estimate predicates.
Updated tests to test for both parallel and sequential execution.
Expand Down
2 changes: 1 addition & 1 deletion fuel-asm/src/encoding_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn opcode() {

for opcode_int in 0..64 {
let Ok(op) = Opcode::try_from(opcode_int) else {
continue;
continue
};

instructions.push(op.test_construct(r, r, r, r, imm12));
Expand Down
20 changes: 14 additions & 6 deletions fuel-asm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ impl_instructions! {
0x93 CFE cfe [amount: RegId]
"Shrink the current call frame's stack"
0x94 CFS cfs [amount: RegId]
"Push a bitmask-selected set of registers in range 16..40 to the stack."
0x95 PSHL pshl [bitmask: Imm24]
"Push a bitmask-selected set of registers in range 40..64 to the stack."
0x96 PSHH pshh [bitmask: Imm24]
"Pop a bitmask-selected set of registers in range 16..40 to the stack."
0x97 POPL popl [bitmask: Imm24]
"Pop a bitmask-selected set of registers in range 40..64 to the stack."
0x98 POPH poph [bitmask: Imm24]

"Compare 128bit integers"
0xa0 WDCM wdcm [dst: RegId lhs: RegId rhs: RegId flags: Imm06]
Expand Down Expand Up @@ -492,12 +500,12 @@ impl Opcode {
match self {
ADD | AND | DIV | EQ | EXP | GT | LT | MLOG | MROO | MOD | MOVE | MUL
| NOT | OR | SLL | SRL | SUB | XOR | WDCM | WQCM | WDOP | WQOP | WDML
| WQML | WDDV | WQDV | WDMD | WQMD | WDAM | WQAM | WDMM | WQMM | RET
| ALOC | MCL | MCP | MEQ | ECK1 | ECR1 | ED19 | K256 | S256 | NOOP | FLAG
| ADDI | ANDI | DIVI | EXPI | MODI | MULI | MLDV | ORI | SLLI | SRLI
| SUBI | XORI | JNEI | LB | LW | SB | SW | MCPI | MCLI | GM | MOVI | JNZI
| JI | JMP | JNE | JMPF | JMPB | JNZF | JNZB | JNEF | JNEB | CFEI | CFSI
| CFE | CFS | GTF => true,
| WQML | WDDV | WQDV | WDMD | WQMD | WDAM | WQAM | WDMM | WQMM | PSHH
| PSHL | POPH | POPL | RET | ALOC | MCL | MCP | MEQ | ECK1 | ECR1 | ED19
| K256 | S256 | NOOP | FLAG | ADDI | ANDI | DIVI | EXPI | MODI | MULI
| MLDV | ORI | SLLI | SRLI | SUBI | XORI | JNEI | LB | LW | SB | SW
| MCPI | MCLI | GM | MOVI | JNZI | JI | JMP | JNE | JMPF | JMPB | JNZF
| JNZB | JNEF | JNEB | CFEI | CFSI | CFE | CFS | GTF => true,
_ => false,
}
}
Expand Down
4 changes: 2 additions & 2 deletions fuel-crypto/src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ pub fn verify(
message: &Message,
) -> Result<(), Error> {
let Ok(signature) = Signature::from_bytes(&**signature) else {
return Err(Error::InvalidSignature);
return Err(Error::InvalidSignature)
};

let Ok(pub_key) = ed25519_dalek::PublicKey::from_bytes(&**pub_key) else {
return Err(Error::InvalidPublicKey);
return Err(Error::InvalidPublicKey)
};

if pub_key.verify_strict(&**message, &signature).is_ok() {
Expand Down
37 changes: 35 additions & 2 deletions fuel-vm/src/constraints/reg_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ impl<'r> ProgramRegisters<'r> {
// Translate the `a` absolute register index to a program register index.
let a = a.translate();
// Split the array at the first register which is a.
let [i, rest @ ..] = &mut self.0[a..] else { return None };
let [i, rest @ ..] = &mut self.0[a..] else {
return None
};
// Translate the `b` absolute register index to a program register index.
// Subtract 1 because the first register is `a`.
// Subtract `a` registers because we split the array at `a`.
Expand All @@ -303,7 +305,9 @@ impl<'r> ProgramRegisters<'r> {
// Translate the `b` absolute register index to a program register index.
let b = b.translate();
// Split the array at the first register which is b.
let [i, rest @ ..] = &mut self.0[b..] else { return None };
let [i, rest @ ..] = &mut self.0[b..] else {
return None
};
// Translate the `a` absolute register index to a program register index.
// Subtract 1 because the first register is `b`.
// Subtract `b` registers because we split the array at `b`.
Expand Down Expand Up @@ -422,3 +426,32 @@ impl<'a> From<&SystemRegistersRef<'a>> for [Word; VM_REGISTER_SYSTEM_COUNT] {
]
}
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum ProgramRegistersSegment {
/// Registers 16..40
Low,
/// Registers 40..64
High,
}

impl<'r> ProgramRegisters<'r> {
/// Returns the registers corresponding to the segment, always 24 elements.
pub(crate) fn segment(&self, segment: ProgramRegistersSegment) -> &[Word] {
match segment {
ProgramRegistersSegment::Low => &self.0[..24],
ProgramRegistersSegment::High => &self.0[24..],
}
}

/// Returns the registers corresponding to the segment, always 24 elements.
pub(crate) fn segment_mut(
&mut self,
segment: ProgramRegistersSegment,
) -> &mut [Word] {
match segment {
ProgramRegistersSegment::Low => &mut self.0[..24],
ProgramRegistersSegment::High => &mut self.0[24..],
}
}
}
12 changes: 12 additions & 0 deletions fuel-vm/src/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ pub struct GasCostsValues {
pub not: Word,
pub or: Word,
pub ori: Word,
pub poph: Word,
pub popl: Word,
pub pshh: Word,
pub pshl: Word,
#[cfg_attr(feature = "serde", serde(rename = "ret_contract"))]
pub ret: Word,
#[cfg_attr(feature = "serde", serde(rename = "rvrt_contract"))]
Expand Down Expand Up @@ -376,6 +380,10 @@ impl GasCostsValues {
not: 0,
or: 0,
ori: 0,
poph: 0,
popl: 0,
pshh: 0,
pshl: 0,
ret: 0,
rvrt: 0,
s256: 0,
Expand Down Expand Up @@ -485,6 +493,10 @@ impl GasCostsValues {
or: 1,
ori: 1,
ret: 1,
poph: 1,
popl: 1,
pshh: 1,
pshl: 1,
rvrt: 1,
s256: 1,
sb: 1,
Expand Down
4 changes: 4 additions & 0 deletions fuel-vm/src/gas/default_gas_costs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ pub fn default_gas_costs() -> GasCostsValues {
not: 1,
or: 1,
ori: 1,
poph: 2,
popl: 2,
pshh: 2,
pshl: 2,
move_op: 1,
ret: 63,
s256: 4,
Expand Down
25 changes: 25 additions & 0 deletions fuel-vm/src/interpreter/executors/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
constraints::reg_key::ProgramRegistersSegment,
consts::*,
error::{
InterpreterError,
Expand Down Expand Up @@ -625,6 +626,30 @@ where
self.stack_pointer_overflow(Word::overflowing_sub, r!(a))?;
}

Instruction::PSHL(pshl) => {
self.gas_charge(self.gas_costs.pshl)?;
let bitmask = pshl.unpack();
self.push_selected_registers(ProgramRegistersSegment::Low, bitmask)?;
}

Instruction::PSHH(pshh) => {
self.gas_charge(self.gas_costs.pshh)?;
let bitmask = pshh.unpack();
self.push_selected_registers(ProgramRegistersSegment::High, bitmask)?;
}

Instruction::POPL(popl) => {
self.gas_charge(self.gas_costs.popl)?;
let bitmask = popl.unpack();
self.pop_selected_registers(ProgramRegistersSegment::Low, bitmask)?;
}

Instruction::POPH(poph) => {
self.gas_charge(self.gas_costs.poph)?;
let bitmask = poph.unpack();
self.pop_selected_registers(ProgramRegistersSegment::High, bitmask)?;
}

Instruction::LB(lb) => {
self.gas_charge(self.gas_costs.lb)?;
let (a, b, imm) = lb.unpack();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ fn writes_to_ra(opcode: Opcode) -> bool {
Opcode::NOT => true,
Opcode::OR => true,
Opcode::ORI => true,
Opcode::POPH => false,
Opcode::POPL => false,
Opcode::PSHH => false,
Opcode::PSHL => false,
Opcode::SLL => true,
Opcode::SLLI => true,
Opcode::SRL => true,
Expand Down Expand Up @@ -232,6 +236,10 @@ fn writes_to_rb(opcode: Opcode) -> bool {
Opcode::NOT => false,
Opcode::OR => false,
Opcode::ORI => false,
Opcode::POPH => false,
Opcode::POPL => false,
Opcode::PSHH => false,
Opcode::PSHL => false,
Opcode::SLL => false,
Opcode::SLLI => false,
Opcode::SRL => false,
Expand Down
4 changes: 3 additions & 1 deletion fuel-vm/src/interpreter/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ pub(crate) fn set_flag(
pc: RegMut<PC>,
a: Word,
) -> Result<(), RuntimeError> {
let Some(flags) = Flags::from_bits(a) else { return Err(PanicReason::ErrorFlag.into()) };
let Some(flags) = Flags::from_bits(a) else {
return Err(PanicReason::ErrorFlag.into())
};

*flag = flags.bits();

Expand Down
109 changes: 109 additions & 0 deletions fuel-vm/src/interpreter/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
};

use fuel_asm::{
Imm24,
PanicReason,
RegId,
};
Expand All @@ -32,6 +33,9 @@ mod tests;
#[cfg(test)]
mod allocation_tests;

#[cfg(test)]
mod stack_tests;

/// Used to handle `Word` to `usize` conversions for memory addresses,
/// as well as checking that the resulting value is withing the VM ram boundaries.
pub trait ToAddr {
Expand Down Expand Up @@ -189,6 +193,42 @@ where
stack_pointer_overflow(sp, ssp.as_ref(), hp.as_ref(), pc, f, v)
}

pub(crate) fn push_selected_registers(
&mut self,
segment: ProgramRegistersSegment,
bitmask: Imm24,
) -> Result<(), RuntimeError> {
let (SystemRegisters { sp, hp, pc, .. }, program_regs) =
split_registers(&mut self.registers);
push_selected_registers(
&mut self.memory,
sp,
hp.as_ref(),
pc,
&program_regs,
segment,
bitmask,
)
}

pub(crate) fn pop_selected_registers(
&mut self,
segment: ProgramRegistersSegment,
bitmask: Imm24,
) -> Result<(), RuntimeError> {
let (SystemRegisters { sp, ssp, pc, .. }, mut program_regs) =
split_registers(&mut self.registers);
pop_selected_registers(
&self.memory,
sp,
ssp.as_ref(),
pc,
&mut program_regs,
segment,
bitmask,
)
}

pub(crate) fn load_byte(
&mut self,
ra: RegisterId,
Expand Down Expand Up @@ -287,6 +327,75 @@ where
}
}

pub(crate) fn push_selected_registers(
memory: &mut [u8; MEM_SIZE],
mut sp: RegMut<SP>,
hp: Reg<HP>,
pc: RegMut<PC>,
program_regs: &ProgramRegisters,
segment: ProgramRegistersSegment,
bitmask: Imm24,
) -> Result<(), RuntimeError> {
let bitmask = bitmask.to_u32();

// First compute the new stack pointer, as that's the only error condition
let count = bitmask.count_ones();
let stack_range = MemoryRange::new(*sp, (count as u64) * 8)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let stack_range = MemoryRange::new(*sp, (count as u64) * 8)?;
let stack_range = MemoryRange::new(*sp, count * 8)?;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will not work; u32 doesn't implement ToAddr (unless we expand the implementation to cover it). The as-cast here is extending u32 returned by count_ones(), and is complely harmless. I'm replacing it with a .into() cast anyway, so it looks less suspicious.

let new_sp = stack_range.words().end;
if new_sp > *hp {
Dentosal marked this conversation as resolved.
Show resolved Hide resolved
return Err(PanicReason::MemoryOverflow.into())
}

// Write the registers to the stack
let mut it = memory[stack_range.usizes()].chunks_exact_mut(8);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to cross the heap here? Or is it not because we checked that inside of the try_update_stack_pointer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try_update_stack_pointer performs the check

for (i, reg) in program_regs.segment(segment).iter().enumerate() {
if (bitmask & (1 << i)) != 0 {
it.next().unwrap().copy_from_slice(&reg.to_be_bytes());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use expect, please, with a description of why it is impossible?=)

}
}

// Apply changes to system registers
*sp = new_sp;
inc_pc(pc)
}

pub(crate) fn pop_selected_registers(
memory: &[u8; MEM_SIZE],
mut sp: RegMut<SP>,
ssp: Reg<SSP>,
pc: RegMut<PC>,
program_regs: &mut ProgramRegisters,
segment: ProgramRegistersSegment,
bitmask: Imm24,
) -> Result<(), RuntimeError> {
let bitmask = bitmask.to_u32();

// First compute the new stack pointer, as that's the only error condition
let count = bitmask.count_ones();
let size_in_stack = (count as u64) * 8;
Dentosal marked this conversation as resolved.
Show resolved Hide resolved
let new_sp = sp
.checked_sub(size_in_stack)
.ok_or(PanicReason::MemoryOverflow)?;
if new_sp < *ssp {
return Err(PanicReason::MemoryOverflow.into())
Dentosal marked this conversation as resolved.
Show resolved Hide resolved
}
let stack_range = MemoryRange::new(new_sp, size_in_stack)?.usizes();

// Restore registers from the stack
let mut it = memory[stack_range].chunks_exact(8);
for (i, reg) in program_regs.segment_mut(segment).iter_mut().enumerate() {
if (bitmask & (1 << i)) != 0 {
let mut buf = [0u8; 8];
buf.copy_from_slice(it.next().expect("Count mismatch"));
*reg = Word::from_be_bytes(buf);
}
}

// Apply changes to system registers
*sp = new_sp;
inc_pc(pc)
}

pub(crate) fn load_byte(
memory: &[u8; MEM_SIZE],
pc: RegMut<PC>,
Expand Down
Loading