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

[move-vm][experimental] Proof of concept of high-peformance monomorphizing VM #15871

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion third_party/move/move-vm/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion third_party/move/move-vm/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -31,6 +30,7 @@ mod debug;

mod access_control;
mod frame_type_cache;
pub mod mono;
mod runtime_type_checks;
mod storage;

Expand Down
11 changes: 11 additions & 0 deletions third_party/move/move-vm/runtime/src/mono/README.md
Original file line number Diff line number Diff line change
@@ -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.
56 changes: 56 additions & 0 deletions third_party/move/move-vm/runtime/src/mono/code.rs
Original file line number Diff line number Diff line change
@@ -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,
},
}
43 changes: 43 additions & 0 deletions third_party/move/move-vm/runtime/src/mono/context.rs
Original file line number Diff line number Diff line change
@@ -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<FunctionContext<'a>>;

fn stack_bounds(&self) -> MemoryBounds;

fn heap_bounds(&self) -> MemoryBounds;
}
129 changes: 129 additions & 0 deletions third_party/move/move-vm/runtime/src/mono/executor.rs
Original file line number Diff line number Diff line change
@@ -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<Frame<'ctx>>,
}

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<Vec<u8>> {
// 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<Vec<u8>> {
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::<Reference>(ctx)?;
self.memory.push_from(ctx, reference, *size)?
},
WriteRef { size } => {
let reference = self.memory.pop_value::<Reference>(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::<Reference>(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::<bool>(ctx)?;
if cond {
frame.program_counter = *offset
}
},
BranchFalse { offset } => {
let cond = self.memory.pop_value::<bool>(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());
}
},
}
}
}
}
Loading
Loading