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

Paged memory allocation #461

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9c62f31
WIP
Dentosal May 24, 2023
9f2263d
WIP
Dentosal May 24, 2023
22641f1
WIP
Dentosal May 24, 2023
dbe3ccb
Clean up memory handling
Dentosal May 26, 2023
0e8ef42
Make tests compile
Dentosal May 29, 2023
9263918
Bugfix: heap allocation was not actually done
Dentosal May 30, 2023
a159d05
fmt
Dentosal May 30, 2023
aba5ab0
Bugfix: infinite loop when allocating the last memory page
Dentosal May 30, 2023
3f9115f
More test fixes
Dentosal May 30, 2023
5d92a8e
clippy
Dentosal May 30, 2023
509b559
Bugfix: MemoryRange::split_at
Dentosal May 30, 2023
20e796b
bugfix: Allocate stack space when doing calls
Dentosal May 30, 2023
6019b6a
vm reset/init function
Dentosal May 30, 2023
943e085
Bug and test fixes
Dentosal May 30, 2023
40ee7cc
Fix log test case
Dentosal May 30, 2023
1cde4a6
All known bugs fixed
Dentosal May 30, 2023
aae1561
Move memory range to own module, add unit tests
Dentosal May 31, 2023
9598e9e
More docs, refactoring
Dentosal May 31, 2023
97d62c8
bugfix: cfs(i) shouldn't allow sp < ssp, was broken in master as well
Dentosal May 31, 2023
1ceecd9
Factor out potentially problematic Word as usize casts
Dentosal May 31, 2023
b153e68
make internal_contract_addr return a typed pointer
Dentosal May 31, 2023
8215227
Actually charge for the allocated memory pages
Dentosal May 31, 2023
42814f9
Add InstructionLocation methods without debug feature
Dentosal Jun 1, 2023
f3587b3
Merge branch 'master' into dento/paged-memory
Dentosal Jun 1, 2023
5a87fb3
clippy
Dentosal Jun 1, 2023
3efbb85
Huge refactoring
Dentosal Jun 1, 2023
3d467ab
Fix issues with memory initialization and access checks
Dentosal Jun 1, 2023
1688eef
Move PC incrementing to a single location
Dentosal Jun 1, 2023
c2436bc
Fix/cleanup crypto opcode tests
Dentosal Jun 1, 2023
cde908b
Fix logd test case
Dentosal Jun 1, 2023
0c39471
Restore writable-target-register checks
Dentosal Jun 1, 2023
0d4efc3
Tighten rules around empty memory ranges, and fix meq access checks
Dentosal Jun 1, 2023
0b227e1
Register writability check fixes
Dentosal Jun 1, 2023
0ed033b
Fix memory size for variable_output_updates_in_memory test
Dentosal Jun 1, 2023
444c44a
Add some gas for predicate test for allocation
Dentosal Jun 1, 2023
bd6ff25
Add some gas to change amount tests so that they run
Dentosal Jun 2, 2023
4c1bfb6
One more test was out of ~~fuel~~ gas
Dentosal Jun 2, 2023
bfe43f8
Restore old error-triggering order for LDC with a missing contract input
Dentosal Jun 2, 2023
6ca2bc8
Fix stack allocation permissions during LDC
Dentosal Jun 2, 2023
aae1f59
Use more specific error
Dentosal Jun 2, 2023
5530968
Fix an issue of mint,burn introduced earlier
Dentosal Jun 2, 2023
0f3e234
Restore diffing functionality
Dentosal Jun 2, 2023
ea000bd
optimization: remove intermediate allocation from memory copy
Dentosal Jun 2, 2023
878bb39
Fix heap memory order on new page allocation by using VecDeque
Dentosal Jun 2, 2023
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
1 change: 1 addition & 0 deletions fuel-asm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ description = "Atomic types of the FuelVM."
arbitrary = { version = "1.1", features = ["derive"], optional = true }
bitflags = "1.3"
fuel-types = { workspace = true, default-features = false }
num_enum = "0.6"
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
strum = { version = "0.24", default-features = false, features = ["derive"] }

Expand Down
36 changes: 1 addition & 35 deletions fuel-asm/src/encoding_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,41 +69,7 @@ fn opcode() {
fn panic_reason_description() {
let imm24 = 0xbfffff;

let reasons = vec![
PanicReason::Revert,
PanicReason::OutOfGas,
PanicReason::TransactionValidity,
PanicReason::MemoryOverflow,
PanicReason::ArithmeticOverflow,
PanicReason::ContractNotFound,
PanicReason::MemoryOwnership,
PanicReason::NotEnoughBalance,
PanicReason::ExpectedInternalContext,
PanicReason::AssetIdNotFound,
PanicReason::InputNotFound,
PanicReason::OutputNotFound,
PanicReason::WitnessNotFound,
PanicReason::TransactionMaturity,
PanicReason::InvalidMetadataIdentifier,
PanicReason::MalformedCallStructure,
PanicReason::ReservedRegisterNotWritable,
PanicReason::ErrorFlag,
PanicReason::InvalidImmediateValue,
PanicReason::ExpectedCoinInput,
PanicReason::MaxMemoryAccess,
PanicReason::MemoryWriteOverlap,
PanicReason::ContractNotInInputs,
PanicReason::InternalBalanceOverflow,
PanicReason::ContractMaxSize,
PanicReason::ExpectedUnallocatedStack,
PanicReason::MaxStaticContractsReached,
PanicReason::TransferAmountCannotBeZero,
PanicReason::ExpectedOutputVariable,
PanicReason::ExpectedParentInternalContext,
PanicReason::IllegalJump,
PanicReason::ArithmeticError,
PanicReason::ContractInstructionNotAllowed,
];
let reasons = PanicReason::iter();

for r in reasons {
let b = r as u8;
Expand Down
10 changes: 10 additions & 0 deletions fuel-asm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,16 @@ impl Opcode {
_ => false,
}
}

/// Checks if the instruction has special control flow, i.e.
/// returns `false` if $pc is always incremented by four after this.
pub fn has_control_flow(&self) -> bool {
use Opcode::*;
matches!(
self,
CALL | RET | JNEI | JNZI | JI | JMP | JNE | JMPF | JMPB | JNZF | JNZB | JNEF | JNEB
)
}
}

// Direct conversions
Expand Down
11 changes: 4 additions & 7 deletions fuel-asm/src/panic_instruction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::panic_reason::InvalidPanicReason;
use crate::{Instruction, PanicReason, RawInstruction, Word};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -39,15 +38,13 @@ impl From<PanicInstruction> for Word {
}
}

impl TryFrom<Word> for PanicInstruction {
type Error = InvalidPanicReason;

fn try_from(val: Word) -> Result<Self, Self::Error> {
impl From<Word> for PanicInstruction {
fn from(val: Word) -> Self {
// Safe to cast as we've shifted the 8 MSB.
let reason_u8 = (val >> REASON_OFFSET) as u8;
// Cast to truncate in order to remove the `reason` bits.
let instruction = (val >> INSTR_OFFSET) as u32;
let reason = PanicReason::try_from(reason_u8)?;
Ok(Self { reason, instruction })
let reason = PanicReason::from(reason_u8);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we want to change it in this PR?

I'm okay with treating all unknown numbers as unknown, but the idea was don't allow panic with a value of zero because it means something is wrong in our internal logic=) And my question is do we need it in this PR or can we do that in another?

Self { reason, instruction }
}
}
204 changes: 57 additions & 147 deletions fuel-asm/src/panic_reason.rs
Original file line number Diff line number Diff line change
@@ -1,89 +1,102 @@
use core::{convert, fmt};
use core::fmt;
use num_enum::{FromPrimitive, IntoPrimitive};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, FromPrimitive)]
#[cfg_attr(test, derive(strum::EnumIter))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[repr(u8)]
#[non_exhaustive]
/// Panic reason representation for the interpreter.
pub enum PanicReason {
/// The byte can't be mapped to any known `PanicReason`.
#[num_enum(default)]
UnknownPanicReason,
/// Found `RVRT` instruction.
Revert = 0x01,
Revert,
/// Execution ran out of gas.
OutOfGas = 0x02,
OutOfGas,
/// The transaction validity is violated.
TransactionValidity = 0x03,
/// Attempt to write outside interpreter memory boundaries.
MemoryOverflow = 0x04,
TransactionValidity,
/// Attempting to execute memory that doesn't contain a valid instruction.
InvalidInstruction,
/// Attempting to read current instruction from unaccessible memory area, i.e.
/// either between the stack and the heap, or after the heap.
InstructionFetch,
/// Attempt to read or write unaccessible memory area, i.e.
/// either between the stack and the heap, or after the heap.
MemoryAccess,
/// Attempt to write accessible memory area that's not owned by the caller.
MemoryOwnership,
/// The requested memory access size exceeds the limits of the interpreter.
MemoryAccessSize,
/// Two segments of the interpreter memory should not intersect for write operations.
MemoryWriteOverlap,
/// Attempting to allocate stack or heap memory so that sp and hp would collide.
OutOfMemory,
/// Overflow while executing arithmetic operation.
/// These errors are ignored using the WRAPPING flag.
ArithmeticOverflow = 0x05,
ArithmeticOverflow,
/// Designed contract was not found in the storage.
ContractNotFound = 0x06,
/// Memory ownership rules are violated.
MemoryOwnership = 0x07,
ContractNotFound,
/// The asset ID balance isn't enough for the instruction.
NotEnoughBalance = 0x08,
NotEnoughBalance,
/// The interpreter is expected to be in internal context.
ExpectedInternalContext = 0x09,
ExpectedInternalContext,
/// The queried asset ID was not found in the state.
AssetIdNotFound = 0x0a,
AssetIdNotFound,
/// The provided input is not found in the transaction.
InputNotFound = 0x0b,
InputNotFound,
/// The provided output is not found in the transaction.
OutputNotFound = 0x0c,
OutputNotFound,
/// The provided witness is not found in the transaction.
WitnessNotFound = 0x0d,
WitnessNotFound,
/// The transaction maturity is not valid for this request.
TransactionMaturity = 0x0e,
TransactionMaturity,
/// The metadata identifier is invalid.
InvalidMetadataIdentifier = 0x0f,
InvalidMetadataIdentifier,
/// The call structure is not valid.
MalformedCallStructure = 0x10,
MalformedCallStructure,
/// The provided register does not allow write operations.
ReservedRegisterNotWritable = 0x11,
ReservedRegisterNotWritable,
/// Invalid value was written to a system register.
InvalidValue,
/// The execution resulted in an erroneous state of the interpreter.
ErrorFlag = 0x12,
ErrorFlag,
/// The provided immediate value is not valid for this instruction.
InvalidImmediateValue = 0x13,
InvalidImmediateValue,
/// The provided transaction input is not of type `Coin`.
ExpectedCoinInput = 0x14,
/// The requested memory access exceeds the limits of the interpreter.
MaxMemoryAccess = 0x15,
/// Two segments of the interpreter memory should not intersect for write operations.
MemoryWriteOverlap = 0x16,
ExpectedCoinInput,
/// The requested contract is not listed in the transaction inputs.
ContractNotInInputs = 0x17,
ContractNotInInputs,
/// The internal asset ID balance overflowed with the provided instruction.
InternalBalanceOverflow = 0x18,
InternalBalanceOverflow,
/// The maximum allowed contract size is violated.
ContractMaxSize = 0x19,
ContractMaxSize,
/// This instruction expects the stack area to be unallocated for this call.
ExpectedUnallocatedStack = 0x1a,
ExpectedUnallocatedStack,
/// The maximum allowed number of static contracts was reached for this transaction.
MaxStaticContractsReached = 0x1b,
MaxStaticContractsReached,
/// The requested transfer amount cannot be zero.
TransferAmountCannotBeZero = 0x1c,
TransferAmountCannotBeZero,
/// The provided transaction output should be of type `Variable`.
ExpectedOutputVariable = 0x1d,
ExpectedOutputVariable,
/// The expected context of the stack parent is internal.
ExpectedParentInternalContext = 0x1e,
ExpectedParentInternalContext,
/// The jump instruction cannot move backwards in predicate verification.
IllegalJump = 0x1f,
IllegalJump,
/// The contract ID is already deployed and can't be overwritten.
ContractIdAlreadyDeployed = 0x20,
ContractIdAlreadyDeployed,
/// The loaded contract mismatch expectations.
ContractMismatch = 0x21,
ContractMismatch,
/// Attempting to send message data longer than `MAX_MESSAGE_DATA_LENGTH`
MessageDataTooLong = 0x22,
MessageDataTooLong,
/// Mathimatically invalid arguments where given to an arithmetic instruction.
/// For instance, division by zero produces this.
/// These errors are ignored using the UNSAFEMATH flag.
ArithmeticError = 0x23,
ArithmeticError,
/// The contract instruction is not allowed in predicates.
ContractInstructionNotAllowed = 0x24,
/// The byte can't be mapped to any known `PanicReason`.
UnknownPanicReason = 0x25,
ContractInstructionNotAllowed,
}

impl fmt::Display for PanicReason {
Expand All @@ -99,80 +112,6 @@ impl std::error::Error for PanicReason {
}
}

// TODO: Remove this - `Infallible` has nothing to do with `PanicReason`.
impl From<convert::Infallible> for PanicReason {
fn from(_i: convert::Infallible) -> Self {
unreachable!()
}
}

/// Failed to parse a `u8` as a valid panic reason.
#[derive(Debug, Eq, PartialEq)]
pub struct InvalidPanicReason;

impl TryFrom<u8> for PanicReason {
type Error = InvalidPanicReason;

/// Converts the `u8` into a `PanicReason`.
fn try_from(b: u8) -> Result<Self, Self::Error> {
if b == 0 {
return Err(InvalidPanicReason);
}

use PanicReason::*;
let reason = match b {
0x01 => Revert,
0x02 => OutOfGas,
0x03 => TransactionValidity,
0x04 => MemoryOverflow,
0x05 => ArithmeticOverflow,
0x06 => ContractNotFound,
0x07 => MemoryOwnership,
0x08 => NotEnoughBalance,
0x09 => ExpectedInternalContext,
0x0a => AssetIdNotFound,
0x0b => InputNotFound,
0x0c => OutputNotFound,
0x0d => WitnessNotFound,
0x0e => TransactionMaturity,
0x0f => InvalidMetadataIdentifier,
0x10 => MalformedCallStructure,
0x11 => ReservedRegisterNotWritable,
0x12 => ErrorFlag,
0x13 => InvalidImmediateValue,
0x14 => ExpectedCoinInput,
0x15 => MaxMemoryAccess,
0x16 => MemoryWriteOverlap,
0x17 => ContractNotInInputs,
0x18 => InternalBalanceOverflow,
0x19 => ContractMaxSize,
0x1a => ExpectedUnallocatedStack,
0x1b => MaxStaticContractsReached,
0x1c => TransferAmountCannotBeZero,
0x1d => ExpectedOutputVariable,
0x1e => ExpectedParentInternalContext,
0x1f => IllegalJump,
0x20 => ContractIdAlreadyDeployed,
0x21 => ContractMismatch,
0x22 => MessageDataTooLong,
0x23 => ArithmeticError,
0x24 => ContractInstructionNotAllowed,
_ => UnknownPanicReason,
};

Ok(reason)
}
}

#[cfg(feature = "std")]
impl From<InvalidPanicReason> for std::io::Error {
fn from(_: InvalidPanicReason) -> Self {
use std::io;

io::Error::new(io::ErrorKind::InvalidInput, "Panic reason can't be zero")
}
}

#[cfg(feature = "std")]
impl From<PanicReason> for std::io::Error {
fn from(reason: PanicReason) -> Self {
Expand All @@ -181,32 +120,3 @@ impl From<PanicReason> for std::io::Error {
io::Error::new(io::ErrorKind::Other, reason)
}
}

impl From<core::array::TryFromSliceError> for PanicReason {
fn from(_: core::array::TryFromSliceError) -> Self {
Self::MemoryOverflow
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_u8_panic_reason_round_trip() {
const LAST_PANIC_REASON: u8 = PanicReason::UnknownPanicReason as u8;
let reason = PanicReason::try_from(0);
assert!(reason.is_err());

for i in 1..LAST_PANIC_REASON {
let reason = PanicReason::try_from(i).unwrap();
let i2 = reason as u8;
assert_eq!(i, i2);
}
for i in LAST_PANIC_REASON..=255 {
let reason = PanicReason::try_from(i).unwrap();
let i2 = reason as u8;
assert_eq!(PanicReason::UnknownPanicReason as u8, i2);
}
}
}
2 changes: 1 addition & 1 deletion fuel-tx/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ mod tests {
let expected_root = {
let mut tree = BinaryMerkleTree::new();

let leaves = code.chunks(LEAF_SIZE).into_iter().collect::<Vec<_>>();
let leaves = code.chunks(LEAF_SIZE).collect::<Vec<_>>();
tree.push(leaves[0]);
tree.push(leaves[1]);
tree.push(leaves[2]);
Expand Down
2 changes: 1 addition & 1 deletion fuel-tx/src/receipt/receipt_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ impl io::Write for Receipt {

let id = id.into();

*self = Self::panic(id, PanicInstruction::try_from(reason)?, pc, is);
*self = Self::panic(id, PanicInstruction::from(reason), pc, is);
}

ReceiptRepr::Revert => {
Expand Down
4 changes: 2 additions & 2 deletions fuel-tx/src/tests/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ fn receipt() {
Receipt::panic(
rng.gen(),
PanicInstruction::error(
PanicReason::MemoryOverflow,
PanicReason::MemoryAccess,
op::ji(rng.gen::<Immediate24>() & 0xffffff).into(),
),
rng.gen(),
Expand Down Expand Up @@ -382,7 +382,7 @@ fn receipt() {
Receipt::panic(
rng.gen(),
PanicInstruction::error(
PanicReason::MaxMemoryAccess,
PanicReason::MemoryAccessSize,
op::ji(rng.gen::<Immediate24>() & 0xffffff).into(),
),
rng.gen(),
Expand Down
Loading