diff --git a/Cargo.lock b/Cargo.lock index c0184afb7d33a..e26aa69d68b4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11939,6 +11939,7 @@ dependencies = [ "hex", "lazy_static", "lru 0.7.8", + "mockall", "move-binary-format", "move-bytecode-verifier", "move-compiler", diff --git a/third_party/move/move-vm/runtime/Cargo.toml b/third_party/move/move-vm/runtime/Cargo.toml index 7000cebb75eb4..28cf3480b5fa9 100644 --- a/third_party/move/move-vm/runtime/Cargo.toml +++ b/third_party/move/move-vm/runtime/Cargo.toml @@ -34,10 +34,11 @@ typed-arena = { workspace = true } [dev-dependencies] anyhow = { workspace = true } hex = { workspace = true } +mockall = { workspace = true } move-binary-format = { workspace = true, features = ["fuzzing"] } move-compiler = { workspace = true } move-ir-compiler = { workspace = true } -move-vm-test-utils ={ workspace = true } +move-vm-test-utils = { workspace = true } move-vm-types = { workspace = true, features = ["testing"] } proptest = { workspace = true } smallbitvec = { workspace = true } diff --git a/third_party/move/move-vm/runtime/src/lib.rs b/third_party/move/move-vm/runtime/src/lib.rs index e668f911147e4..865e21a1d6373 100644 --- a/third_party/move/move-vm/runtime/src/lib.rs +++ b/third_party/move/move-vm/runtime/src/lib.rs @@ -2,7 +2,6 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -#![forbid(unsafe_code)] #![deny(deprecated)] //! The core Move VM logic. @@ -31,6 +30,7 @@ mod debug; mod access_control; mod frame_type_cache; +pub mod mono; mod runtime_type_checks; mod storage; diff --git a/third_party/move/move-vm/runtime/src/mono/README.md b/third_party/move/move-vm/runtime/src/mono/README.md new file mode 100644 index 0000000000000..1cd272b2b301c --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/README.md @@ -0,0 +1,11 @@ +# Mono Runtime + +Mono runtime is a new style of Move VM execution engine based on _monomorphization_. This here is a proof-of-concept prototype. + +Some general design principles: + +- Code is assumed to be monomorphized and compiled from the Move binary format into the new `MonoCode` representation _at execution time_. +- Data is represented in linear contiguous memory and in its native representation. A `u8` is represented as a byte, `u16` as two bytes, and so on. For structs, fields are packed side-by-side; nested structs are inlined in the outer struct. Vectors are currently not implemented, but the idea is manage them on the heap, with fixed size in their container. Hence, every type has a fixed size representation. +- The code and the executor are inspired by the CLR (.Net runtime): instructions are generic except the _size_ of the data they are working on. For example, reading or writing a reference is 'sized' w.r.t. to the type behind the reference. This allows a uniform handling of different kind of primitive types and structs with a small, yet high-performing instruction set. +- The memory of the executor is partitioned into a traditional contiguous stack and a heap. On the stack, call frames are similar represented like structs, that is, parameters and locals are packed side-by-side according to their size. +- The implementation uses unsafe code to manage the contiguous memory representation, namely pointer casting. In the contiguous memory model, a slice of a byte region can have a mutable reference whereas another disjoint region can have an immutable reference. This is not expressible in safe Rust. However, for safe Move code, safety should be maintained. _Any unsafe code does not affect integrity of the Rust process_, as we never reinterpret Rust data structures, but only Move structures. diff --git a/third_party/move/move-vm/runtime/src/mono/code.rs b/third_party/move/move-vm/runtime/src/mono/code.rs new file mode 100644 index 0000000000000..f627df24a7d0d --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/code.rs @@ -0,0 +1,56 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::mono::{ + context::{ExecutionContext, FunctionId, StorageId}, + memory::Memory, +}; +use move_binary_format::{errors::PartialVMResult, file_format::LocalIndex}; + +pub enum MonoCode<'code> { + LoadConst { + value: &'code [u8], + }, + CopyLocal { + size: usize, + local: LocalIndex, + }, + StoreLocal { + size: usize, + local: LocalIndex, + }, + BorrowLocal { + local: LocalIndex, + }, + ReadRef { + size: usize, + }, + WriteRef { + size: usize, + }, + BorrowGlobal { + storage_key: StorageId, + }, + BorrowField { + byte_offset: usize, + }, + CallPrimitive { + size: usize, + operation: fn(&dyn ExecutionContext, &mut Memory, usize) -> PartialVMResult<()>, + }, + CallFunction { + function_id: FunctionId, + }, + Return { + size: usize, + }, + Branch { + offset: usize, + }, + BranchTrue { + offset: usize, + }, + BranchFalse { + offset: usize, + }, +} diff --git a/third_party/move/move-vm/runtime/src/mono/context.rs b/third_party/move/move-vm/runtime/src/mono/context.rs new file mode 100644 index 0000000000000..86467cd8481f3 --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/context.rs @@ -0,0 +1,43 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::mono::code::MonoCode; +#[cfg(test)] +use mockall::*; +use move_binary_format::errors::PartialVMResult; + +#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Debug)] +pub struct FunctionId { + pub function_hash: u128, +} + +#[derive(Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] +pub struct StorageId { + pub type_hash: u128, +} + +#[derive(Clone)] +pub struct FunctionContext<'ctx> { + pub id: FunctionId, + pub code: &'ctx [MonoCode<'ctx>], + pub params_size: usize, + pub locals_size: usize, + pub local_table: &'ctx [usize], +} + +#[derive(Clone)] +pub struct MemoryBounds { + pub initial_capacity: usize, + pub max_size: usize, +} + +#[cfg_attr(test, automock)] +pub trait ExecutionContext { + fn fetch_data<'a>(&'a self, _id: &StorageId) -> PartialVMResult<&'a [u8]>; + + fn fetch_function<'a>(&self, _id: &FunctionId) -> PartialVMResult>; + + fn stack_bounds(&self) -> MemoryBounds; + + fn heap_bounds(&self) -> MemoryBounds; +} diff --git a/third_party/move/move-vm/runtime/src/mono/executor.rs b/third_party/move/move-vm/runtime/src/mono/executor.rs new file mode 100644 index 0000000000000..5da1db368b085 --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/executor.rs @@ -0,0 +1,129 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::mono::{ + code::MonoCode, + context::{ExecutionContext, FunctionContext, FunctionId}, + memory::{Memory, Reference}, +}; +use move_binary_format::{errors::PartialVMResult, file_format::LocalIndex}; + +pub struct Executor<'ctx> { + memory: Memory, + call_stack: Vec>, +} + +pub struct Frame<'ctx> { + function_ctx: FunctionContext<'ctx>, + program_counter: usize, + stack_base: usize, +} + +impl<'ctx> Executor<'ctx> { + pub fn new(ctx: &'ctx dyn ExecutionContext) -> Self { + Self { + memory: Memory::new(ctx), + call_stack: vec![], + } + } + + pub fn execute( + &mut self, + ctx: &'ctx dyn ExecutionContext, + entry_fun: &FunctionId, + argument_block: &[u8], + ) -> PartialVMResult> { + // Push argument block onto the value stack + self.memory.push_blob(ctx, argument_block)?; + // Create function frame and execute + self.new_call_frame(ctx, entry_fun)?; + self.run(ctx) + } + + pub fn new_call_frame( + &mut self, + ctx: &'ctx dyn ExecutionContext, + id: &FunctionId, + ) -> PartialVMResult<()> { + let function_ctx = ctx.fetch_function(id)?; + // Add space for non-parameter locals on the value stack + self.memory + .push_uninit(ctx, function_ctx.locals_size - function_ctx.params_size)?; + // Create a frame on the call stack + let stack_base = self.memory.stack_len() - function_ctx.locals_size; + self.call_stack.push(Frame { + function_ctx, + program_counter: 0, + stack_base, + }); + Ok(()) + } + + pub fn run(&mut self, ctx: &'ctx dyn ExecutionContext) -> PartialVMResult> { + use MonoCode::*; + + loop { + let frame = self.call_stack.last_mut().expect("call stack not empty"); + let instruction = &frame.function_ctx.code[frame.program_counter]; + frame.program_counter += 1; + + let local_ref = |idx: &LocalIndex| { + let offset = frame.function_ctx.local_table[*idx as usize]; + Reference::local(frame.stack_base + offset) + }; + + match instruction { + LoadConst { value } => { + self.memory.push_blob(ctx, value)?; + }, + CopyLocal { size, local } => self.memory.push_from(ctx, local_ref(local), *size)?, + StoreLocal { size, local } => self.memory.pop_to(ctx, local_ref(local), *size)?, + BorrowLocal { local } => self.memory.push_value(ctx, local_ref(local))?, + ReadRef { size } => { + let reference = self.memory.pop_value::(ctx)?; + self.memory.push_from(ctx, reference, *size)? + }, + WriteRef { size } => { + let reference = self.memory.pop_value::(ctx)?; + self.memory.pop_to(ctx, reference, *size)?; + }, + BorrowGlobal { storage_key } => { + let reference = self.memory.borrow_global(ctx, storage_key); + self.memory.push_value(ctx, reference)? + }, + BorrowField { byte_offset } => { + let reference = self.memory.pop_value::(ctx)?; + self.memory + .push_value(ctx, reference.select_field(*byte_offset))?; + }, + Branch { offset } => frame.program_counter = *offset, + BranchTrue { offset } => { + let cond = self.memory.pop_value::(ctx)?; + if cond { + frame.program_counter = *offset + } + }, + BranchFalse { offset } => { + let cond = self.memory.pop_value::(ctx)?; + if !cond { + frame.program_counter = *offset + } + }, + CallPrimitive { size, operation } => { + operation(ctx, &mut self.memory, *size)?; + }, + CallFunction { function_id } => { + self.new_call_frame(ctx, function_id)?; + }, + Return { size } => { + self.memory + .collapse(ctx, frame.function_ctx.locals_size, *size)?; + self.call_stack.pop(); + if self.call_stack.is_empty() { + return Ok(self.memory.top_view(*size).view_as_slice().to_vec()); + } + }, + } + } + } +} diff --git a/third_party/move/move-vm/runtime/src/mono/memory.rs b/third_party/move/move-vm/runtime/src/mono/memory.rs new file mode 100644 index 0000000000000..39e305fc34150 --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/memory.rs @@ -0,0 +1,320 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::mono::context::{ExecutionContext, StorageId}; +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::vm_status::StatusCode; +use std::{collections::BTreeMap, marker::PhantomData, mem, ops::Range, ptr, slice}; + +// ========================================================================================= +// Memory Views + +pub struct MemoryView<'a> { + ptr: *const u8, + size: usize, + _marker: PhantomData<&'a str>, +} + +impl<'a> MemoryView<'a> { + pub fn new(data: &'a [u8]) -> MemoryView<'a> { + MemoryView { + ptr: data.as_ptr(), + size: data.len(), + _marker: Default::default(), + } + } + + #[inline] + pub fn size(&self) -> usize { + self.size + } + + #[inline] + pub fn view_as(&self) -> &T { + assert_eq!(self.size, mem::size_of::()); + unsafe { &*self.ptr.cast::() } + } + + #[inline] + pub fn view_as_mut(&mut self) -> &mut T { + assert_eq!(self.size, mem::size_of::()); + unsafe { &mut *self.ptr.cast_mut().cast::() } + } + + #[inline] + pub fn view_as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.ptr, self.size) } + } + + #[inline] + #[allow(clippy::mut_from_ref)] + pub fn view_as_slice_mut(&self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut(mem::transmute::<*const u8, *mut u8>(self.ptr), self.size) + } + } + + #[inline] + pub fn sub_view(&self, offset: usize, size: usize) -> MemoryView<'_> { + assert!(offset + size < self.size); + unsafe { + MemoryView { + ptr: self.ptr.add(offset), + size, + _marker: Default::default(), + } + } + } +} + +// ========================================================================================= +// Memory Regions + +pub struct Region { + data: Vec, +} + +impl Region { + pub fn new(initial_capacity: usize) -> Region { + Region { + data: Vec::with_capacity(initial_capacity), + } + } + + pub fn size(&self) -> usize { + self.data.len() + } + + pub fn push_uninit( + &mut self, + ctx: &dyn ExecutionContext, + size: usize, + ) -> PartialVMResult { + let cur_size = self.data.len(); + let new_size = cur_size + size; + if new_size > ctx.stack_bounds().max_size { + return Err(PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW) + .with_message("stack exceeded configured size".to_string())); + } + self.data.try_reserve(size).map_err(|_| { + PartialVMError::new(StatusCode::CALL_STACK_OVERFLOW) + .with_message("stack exceeded available process memory".to_string()) + })?; + // Since we ensured capacity is sufficient, and the elements are u8 and don't need to be + // initialized, we can use unsafe set_len to grow the stack. + unsafe { self.data.set_len(new_size) }; + Ok(cur_size) + } + + pub fn push_bytes( + &mut self, + ctx: &dyn ExecutionContext, + block: &[u8], + ) -> PartialVMResult { + let size = block.len(); + let offset = self.push_uninit(ctx, size)?; + unsafe { + let src = block.as_ptr(); + let dest = self.data.as_mut_ptr().add(offset); + ptr::copy_nonoverlapping(src, dest, size) + } + Ok(offset) + } + + pub fn pop(&mut self, _ctx: &dyn ExecutionContext, size: usize) { + unsafe { self.data.set_len(self.data.len() - size) } + } + + pub fn collapse(&mut self, _ctx: &dyn ExecutionContext, frame_size: usize, result_size: usize) { + let size = self.data.len(); + unsafe { + if result_size > 0 { + let src = self.data.as_ptr().add(size - result_size); + let dest = self.data.as_mut_ptr().add(size - result_size - frame_size); + ptr::copy_nonoverlapping(src, dest, result_size) + } + self.data.set_len(size - frame_size) + } + } + + pub fn slice(&self, range: Range) -> &[u8] { + &self.data[range] + } + + pub fn slice_mut(&mut self, range: Range) -> &mut [u8] { + &mut self.data[range] + } +} + +// ========================================================================================= +// Memory + +pub struct Memory { + stack: Region, + heap: Region, + storage_roots: BTreeMap, +} + +#[derive(Clone, Copy)] +pub struct Reference { + tagged_offset: usize, +} + +impl Reference { + pub fn pack(is_stack: bool, offset: usize) -> Self { + Reference { + tagged_offset: offset << 1 | is_stack as usize, + } + } + + pub fn local(offset: usize) -> Self { + Self::pack(true, offset) + } + + pub fn global(offset: usize) -> Self { + Self::pack(false, offset) + } + + #[inline] + pub fn unpack(self) -> (bool, usize) { + (self.tagged_offset & 0x1 != 0, self.tagged_offset >> 1) + } + + #[inline] + pub fn select_field(self, offset: usize) -> Self { + let (region, current) = self.unpack(); + Self::pack(region, current + offset) + } +} + +impl Memory { + pub fn new(ctx: &dyn ExecutionContext) -> Self { + Self { + stack: Region::new(ctx.stack_bounds().initial_capacity), + heap: Region::new(ctx.heap_bounds().initial_capacity), + storage_roots: BTreeMap::default(), + } + } + + // ------------------------------------------------------------------ + // Stack Operations + + pub fn stack_len(&self) -> usize { + self.stack.size() + } + + pub fn view(&self, from_top: usize, size: usize) -> MemoryView { + let start = self.stack_len() - from_top; + MemoryView::new(self.stack.slice(start..start + size)) + } + + pub fn top_view(&self, size: usize) -> MemoryView<'_> { + self.view(size, size) + } + + pub fn push_uninit( + &mut self, + ctx: &dyn ExecutionContext, + size: usize, + ) -> PartialVMResult { + self.stack.push_uninit(ctx, size).map(Reference::local) + } + + pub fn push_blob(&mut self, ctx: &dyn ExecutionContext, data: &[u8]) -> PartialVMResult<()> { + self.stack.push_bytes(ctx, data)?; + Ok(()) + } + + pub fn push_value( + &mut self, + ctx: &dyn ExecutionContext, + value: T, + ) -> PartialVMResult<()> { + let size = mem::size_of::(); + self.stack.push_uninit(ctx, size)?; + *self.top_view(size).view_as_mut::() = value; + Ok(()) + } + + pub fn push_from( + &mut self, + ctx: &dyn ExecutionContext, + from: Reference, + size: usize, + ) -> PartialVMResult<()> { + let new_offset = self.stack.push_uninit(ctx, size)?; + unsafe { + let src = match from.unpack() { + (true, offset) => self.stack.data.as_ptr().add(offset), + (false, offset) => self.heap.data.as_ptr().add(offset), + }; + let dest = self.stack.data.as_mut_ptr().add(new_offset); + ptr::copy_nonoverlapping(src, dest, size); + Ok(()) + } + } + + pub fn pop_to( + &mut self, + ctx: &dyn ExecutionContext, + to: Reference, + size: usize, + ) -> PartialVMResult<()> { + let base = self.stack.size() - size; + unsafe { + let src = self.stack.data.as_ptr().add(base); + let dest = match to.unpack() { + (true, offset) => self.stack.data.as_mut_ptr().add(offset), + (false, offset) => self.heap.data.as_mut_ptr().add(offset), + }; + ptr::copy_nonoverlapping(src, dest, size); + } + self.stack.pop(ctx, size); + Ok(()) + } + + pub fn pop_value( + &mut self, + ctx: &dyn ExecutionContext, + ) -> PartialVMResult { + let size = mem::size_of::(); + let res = self.top_view(size).view_as::().clone(); + self.stack.pop(ctx, size); + Ok(res) + } + + pub fn collapse( + &mut self, + ctx: &dyn ExecutionContext, + frame_size: usize, + result_size: usize, + ) -> PartialVMResult<()> { + self.stack.collapse(ctx, frame_size, result_size); + Ok(()) + } + + // ------------------------------------------------------------------ + // Storage Operations + + pub fn borrow_global( + &mut self, + ctx: &dyn ExecutionContext, + id: &StorageId, + ) -> PartialVMResult { + if let Some(offset) = self.storage_roots.get(id) { + Ok(Reference::global(*offset)) + } else { + let data = ctx.fetch_data(id)?; + // Copy the data to the heap + let offset = self.heap.push_uninit(ctx, data.len())?; + unsafe { + ptr::copy_nonoverlapping( + data.as_ptr(), + self.stack.data.as_mut_ptr().add(offset), + data.len(), + ) + } + Ok(Reference::global(offset)) + } + } +} diff --git a/third_party/move/move-vm/runtime/src/mono/mod.rs b/third_party/move/move-vm/runtime/src/mono/mod.rs new file mode 100644 index 0000000000000..f16aa85bfc5d7 --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/mod.rs @@ -0,0 +1,10 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +pub mod code; +pub mod context; +pub mod executor; +pub mod memory; +#[cfg(test)] +mod mono_tests; +pub mod primitives; diff --git a/third_party/move/move-vm/runtime/src/mono/mono_tests.rs b/third_party/move/move-vm/runtime/src/mono/mono_tests.rs new file mode 100644 index 0000000000000..55076ed3a542a --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/mono_tests.rs @@ -0,0 +1,136 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::mono::{ + code::MonoCode, + context::{FunctionContext, FunctionId, MemoryBounds, MockExecutionContext}, + executor::Executor, + primitives, +}; +use mockall::predicate; + +fn context_mock() -> MockExecutionContext { + let mut mock = MockExecutionContext::new(); + mock.expect_heap_bounds().return_const(MemoryBounds { + initial_capacity: 20, + max_size: 100000, + }); + mock.expect_stack_bounds().return_const(MemoryBounds { + initial_capacity: 20, + max_size: 100000, + }); + mock +} + +#[test] +pub fn identity() { + const FUN_ID: FunctionId = FunctionId { function_hash: 1 }; + const FUN_CTX: FunctionContext = FunctionContext { + id: FUN_ID, + code: &[ + MonoCode::CopyLocal { local: 0, size: 8 }, + MonoCode::Return { size: 8 }, + ], + params_size: 8, + locals_size: 8, + local_table: &[0], + }; + let mut mock = context_mock(); + mock.expect_fetch_function() + .with(predicate::eq(&FUN_ID)) + .returning(|_| Ok(FUN_CTX)); + + let mut executor = Executor::new(&mock); + let bytes = 72777u64.to_le_bytes().to_vec(); + let result = executor + .execute(&mock, &FUN_ID, &bytes) + .expect("no failure"); + assert_eq!(result, bytes) +} + +#[test] +pub fn fibonacci() { + const FUN_ID: FunctionId = FunctionId { function_hash: 1 }; + // Fib is defined as follows: + // fib(0) == 0, fib(1) == 1, fib(n) = fib(n-1) + fib(n-2) + const FUN_CTX: FunctionContext = FunctionContext { + id: FUN_ID, + code: &[ + // if x == 0 return 0 + MonoCode::CopyLocal { local: 0, size: 8 }, + MonoCode::LoadConst { + value: &0u64.to_le_bytes(), + }, + MonoCode::CallPrimitive { + size: 8, + operation: primitives::equals, + }, + // offset 3 + MonoCode::BranchFalse { offset: 3 + 3 }, + MonoCode::LoadConst { + value: &0u64.to_le_bytes(), + }, + MonoCode::Return { size: 8 }, + // if x == 1 return 1 + MonoCode::CopyLocal { local: 0, size: 8 }, + MonoCode::LoadConst { + value: &1u64.to_le_bytes(), + }, + MonoCode::CallPrimitive { + size: 8, + operation: primitives::equals, + }, + // offset 9 + MonoCode::BranchFalse { offset: 9 + 3 }, + MonoCode::LoadConst { + value: &1u64.to_le_bytes(), + }, + MonoCode::Return { size: 8 }, + // else fib(n - 1) + fib(n - 2) + // .. fib(n-1) + MonoCode::CopyLocal { local: 0, size: 8 }, + MonoCode::LoadConst { + value: &1u64.to_le_bytes(), + }, + MonoCode::CallPrimitive { + size: 8, + operation: primitives::sub_u64, + }, + MonoCode::CallFunction { + function_id: FUN_ID, + }, + // .. fib(n-2) + MonoCode::CopyLocal { local: 0, size: 8 }, + MonoCode::LoadConst { + value: &2u64.to_le_bytes(), + }, + MonoCode::CallPrimitive { + size: 8, + operation: primitives::sub_u64, + }, + MonoCode::CallFunction { + function_id: FUN_ID, + }, + // .. fib(n-1) + fib(n-2) + MonoCode::CallPrimitive { + size: 8, + operation: primitives::add_u64, + }, + MonoCode::Return { size: 8 }, + ], + params_size: 8, + locals_size: 8, + local_table: &[0], + }; + let mut mock = context_mock(); + mock.expect_fetch_function() + .with(predicate::eq(&FUN_ID)) + .returning(|_| Ok(FUN_CTX)); + + let mut executor = Executor::new(&mock); + let input = 10u64.to_le_bytes().to_vec(); + let result = executor + .execute(&mock, &FUN_ID, &input) + .expect("no failure"); + assert_eq!(result, 55u64.to_le_bytes()) +} diff --git a/third_party/move/move-vm/runtime/src/mono/primitives.rs b/third_party/move/move-vm/runtime/src/mono/primitives.rs new file mode 100644 index 0000000000000..a8982f334c3fe --- /dev/null +++ b/third_party/move/move-vm/runtime/src/mono/primitives.rs @@ -0,0 +1,52 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::mono::{context::ExecutionContext, memory::Memory}; +use move_binary_format::errors::PartialVMResult; + +pub fn add_u64( + ctx: &dyn ExecutionContext, + memory: &mut Memory, + _size: usize, +) -> PartialVMResult<()> { + let x2 = memory.pop_value::(ctx)?; + let x1 = memory.pop_value::(ctx)?; + // TODO: overflow + memory.push_value(ctx, x1 + x2) +} + +pub fn sub_u64( + ctx: &dyn ExecutionContext, + memory: &mut Memory, + _size: usize, +) -> PartialVMResult<()> { + let x2 = memory.pop_value::(ctx)?; + let x1 = memory.pop_value::(ctx)?; + // TODO: underflow + memory.push_value(ctx, x1 - x2) +} + +pub fn mul_u64( + ctx: &dyn ExecutionContext, + memory: &mut Memory, + _size: usize, +) -> PartialVMResult<()> { + let x2 = memory.pop_value::(ctx)?; + let x1 = memory.pop_value::(ctx)?; + // TODO: overflow + memory.push_value(ctx, x1 * x2) +} + +pub fn equals(ctx: &dyn ExecutionContext, memory: &mut Memory, size: usize) -> PartialVMResult<()> { + // This is generic for arbitrary types, similar as compare would be, though need to revisit + // embedded dynamically sized vectors. + let x1 = memory.view(size + size, size); + let x2 = memory.view(size, size); + let is_eq = x1.view_as_slice() == x2.view_as_slice(); + memory.collapse(ctx, size + size, 0)?; + memory.push_value(ctx, is_eq) +} + +// ... and many more + +// TODO: native interface via primitives