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
90 changes: 74 additions & 16 deletions third_party/move/move-vm/runtime/src/data_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use move_core_types::{
effects::{AccountChanges, ChangeSet, Changes},
gas_algebra::NumBytes,
language_storage::{StructTag, TypeTag},
value::MoveTypeLayout,
value::{MoveStructLayout, MoveTypeLayout},
vm_status::StatusCode,
};
use move_vm_types::{
Expand Down Expand Up @@ -173,25 +173,48 @@ impl TransactionDataCache {
)?
};

let function_value_extension = FunctionValueExtensionAdapter { module_storage };
let (layout, contains_delayed_fields) = layout_with_delayed_fields.unpack();
let value = match data {
Some(blob) => {
let function_value_extension = FunctionValueExtensionAdapter { module_storage };
let max_value_nest_depth = function_value_extension.max_value_nest_depth();
let val = ValueSerDeContext::new(max_value_nest_depth)
.with_func_args_deserialization(&function_value_extension)
.with_delayed_fields_serde()
.deserialize(&blob, &layout)
.ok_or_else(|| {
let msg = format!(
"Failed to deserialize resource {} at {}!",
struct_tag.to_canonical_string(),
addr
);
PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE)
.with_message(msg)
})?;
GlobalValue::cached(val)?
if contains_delayed_fields || Self::layout_contains_functions(&layout) {
// Need function value extension, directly parse here.
let val = ValueSerDeContext::new(max_value_nest_depth)
.with_func_args_deserialization(&function_value_extension)
.with_delayed_fields_serde()
.deserialize(&blob, &layout)
.ok_or_else(|| {
let msg = format!(
"Failed to deserialize resource {} at {}!",
struct_tag.to_canonical_string(),
addr
);
PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE)
.with_message(msg)
})?;
GlobalValue::cached(val)?
} else {
// Do not need function value extension, can delay deserialization.
let layout = layout.clone(); // too many clones for error case only
let blob = blob.to_vec();
let addr = *addr;
let struct_tag = struct_tag.clone();
let deserializer = move || {
ValueSerDeContext::new(max_value_nest_depth)
.deserialize(&blob, &layout)
.ok_or_else(|| {
let msg = format!(
"Failed to deserialize resource {} at {}!",
struct_tag.to_canonical_string(),
addr
);
PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE)
.with_message(msg)
})
};
GlobalValue::fetched(deserializer)?
}
},
None => GlobalValue::none(),
};
Expand All @@ -205,6 +228,41 @@ impl TransactionDataCache {
Ok((entry, NumBytes::new(bytes_loaded as u64)))
}

// HACK for benchmarking
fn layout_contains_functions(ly: &MoveTypeLayout) -> bool {
match ly {
MoveTypeLayout::Function => true,
MoveTypeLayout::Bool
| MoveTypeLayout::U8
| MoveTypeLayout::U64
| MoveTypeLayout::U128
| MoveTypeLayout::Address
| MoveTypeLayout::Signer
| MoveTypeLayout::U16
| MoveTypeLayout::U32
| MoveTypeLayout::U256
| MoveTypeLayout::Native(_, _) => false,
MoveTypeLayout::Vector(ly) => Self::layout_contains_functions(ly),
MoveTypeLayout::Struct(sly) => {
match sly {
MoveStructLayout::Runtime(fields) => {
fields.iter().any(Self::layout_contains_functions)
},
MoveStructLayout::RuntimeVariants(variants) => variants
.iter()
.flatten()
.any(Self::layout_contains_functions),
MoveStructLayout::WithFields(_)
| MoveStructLayout::WithTypes { .. }
| MoveStructLayout::WithVariants(_) => {
// overapproximate
true
},
}
},
}
}

/// Returns true if resource has been inserted into the cache. Otherwise, returns false. The
/// state of the cache does not chang when calling this function.
pub(crate) fn contains_resource(&self, addr: &AccountAddress, ty: &Type) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fn test_copy_value() {
test_unop_with_max_depth(|v, max_depth| v.copy_value_with_depth(max_depth));

// Special-case: reference clone Rcs, so their depth can be larger.
let v = assert_ok!(GlobalValue::cached(Value::struct_(Struct::pack(vec![
let mut v = assert_ok!(GlobalValue::cached(Value::struct_(Struct::pack(vec![
Value::u8(0)
]))));
let v_ref = assert_ok!(v.borrow_global());
Expand All @@ -85,7 +85,7 @@ fn test_copy_value() {

#[test]
fn test_read_ref() {
let v = assert_ok!(GlobalValue::cached(Value::struct_(Struct::pack(vec![
let mut v = assert_ok!(GlobalValue::cached(Value::struct_(Struct::pack(vec![
Value::u8(0)
]))));
let v_ref = assert_ok!(assert_ok!(v.borrow_global()).value_as::<StructRef>());
Expand Down Expand Up @@ -206,7 +206,7 @@ where
assert_eq!(err.major_status(), StatusCode::VM_MAX_VALUE_DEPTH_REACHED);

// Create a reference to struct with 1 field (3 nodes).
let v = assert_ok!(GlobalValue::cached(Value::struct_(Struct::pack(vec![
let mut v = assert_ok!(GlobalValue::cached(Value::struct_(Struct::pack(vec![
Value::u8(0)
]))));
let v_ref = assert_ok!(v.borrow_global());
Expand Down
54 changes: 48 additions & 6 deletions third_party/move/move-vm/types/src/values/values_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
values::function_values_impl::{AbstractFunction, Closure, ClosureVisitor},
views::{ValueView, ValueVisitor},
};
use derivative::Derivative;
use itertools::Itertools;
use move_binary_format::{
errors::*,
Expand Down Expand Up @@ -239,10 +240,17 @@ pub struct VectorRef(ContainerRef);
/// A special "slot" in global storage that can hold a resource. It also keeps track of the status
/// of the resource relative to the global state, which is necessary to compute the effects to emit
/// at the end of transaction execution.
#[derive(Debug)]
#[derive(Derivative)]
#[derivative(Debug)]
enum GlobalValueImpl {
/// No resource resides in this slot or in storage.
None,
/// A resource has been fetched from storage but is not yet deserialized and not in the
/// cache.
Fetched {
#[derivative(Debug = "ignore")]
deserializer: Box<dyn FnOnce() -> PartialVMResult<Value>>,
},
/// A resource has been published to this slot and it did not previously exist in storage.
Fresh { fields: Rc<RefCell<Vec<ValueImpl>>> },
/// A resource resides in this slot and also in storage. The status flag indicates whether
Expand Down Expand Up @@ -3365,6 +3373,14 @@ impl Struct {
**************************************************************************************/
#[allow(clippy::unnecessary_wraps)]
impl GlobalValueImpl {
fn fetched(
deserializer: impl FnOnce() -> PartialVMResult<Value> + 'static,
) -> PartialVMResult<Self> {
Ok(GlobalValueImpl::Fetched {
deserializer: Box::new(deserializer),
})
}

fn cached(
val: ValueImpl,
status: GlobalDataStatus,
Expand Down Expand Up @@ -3393,7 +3409,22 @@ impl GlobalValueImpl {
}
}

fn ensure_cached(&mut self) -> PartialVMResult<()> {
if matches!(self, Self::Fetched { .. }) {
// For Rust semantics restrictions, need to replace data before we can consume.
let data = mem::replace(self, GlobalValueImpl::None);
let Self::Fetched { deserializer } = data else {
unreachable!()
};
*self = Self::cached(deserializer()?.0, GlobalDataStatus::Clean).map_err(|_| {
PartialVMError::new_invariant_violation("unexpected lazy deserialization error")
})?;
}
Ok(())
}

fn move_from(&mut self) -> PartialVMResult<ValueImpl> {
self.ensure_cached()?;
let fields = match self {
Self::None | Self::Deleted => {
return Err(PartialVMError::new(StatusCode::MISSING_DATA))
Expand All @@ -3406,6 +3437,7 @@ impl GlobalValueImpl {
Self::Cached { fields, .. } => fields,
_ => unreachable!(),
},
Self::Fetched { .. } => unreachable!(),
};
if Rc::strong_count(&fields) != 1 {
return Err(
Expand All @@ -3419,7 +3451,7 @@ impl GlobalValueImpl {

fn move_to(&mut self, val: ValueImpl) -> Result<(), (PartialVMError, ValueImpl)> {
match self {
Self::Fresh { .. } | Self::Cached { .. } => {
Self::Fresh { .. } | Self::Cached { .. } | Self::Fetched { .. } => {
return Err((
PartialVMError::new(StatusCode::RESOURCE_ALREADY_EXISTS),
val,
Expand All @@ -3433,12 +3465,13 @@ impl GlobalValueImpl {

fn exists(&self) -> PartialVMResult<bool> {
match self {
Self::Fresh { .. } | Self::Cached { .. } => Ok(true),
Self::Fresh { .. } | Self::Cached { .. } | Self::Fetched { .. } => Ok(true),
Self::None | Self::Deleted => Ok(false),
}
}

fn borrow_global(&self) -> PartialVMResult<ValueImpl> {
fn borrow_global(&mut self) -> PartialVMResult<ValueImpl> {
self.ensure_cached()?;
match self {
Self::None | Self::Deleted => Err(PartialVMError::new(StatusCode::MISSING_DATA)),
Self::Fresh { fields } => Ok(ValueImpl::ContainerRef(ContainerRef::Local(
Expand All @@ -3448,6 +3481,7 @@ impl GlobalValueImpl {
container: Container::Struct(Rc::clone(fields)),
status: Rc::clone(status),
})),
Self::Fetched { .. } => unreachable!(),
}
}

Expand All @@ -3464,6 +3498,7 @@ impl GlobalValueImpl {
},
GlobalDataStatus::Clean => None,
},
Self::Fetched { .. } => None,
}
}

Expand All @@ -3476,6 +3511,7 @@ impl GlobalValueImpl {
GlobalDataStatus::Dirty => true,
GlobalDataStatus::Clean => false,
},
Self::Fetched { .. } => false,
}
}
}
Expand All @@ -3485,6 +3521,12 @@ impl GlobalValue {
Self(GlobalValueImpl::None)
}

pub fn fetched(
deserializer: impl FnOnce() -> PartialVMResult<Value> + 'static,
) -> PartialVMResult<Self> {
Ok(Self(GlobalValueImpl::fetched(deserializer)?))
}

pub fn cached(val: Value) -> PartialVMResult<Self> {
Ok(Self(
GlobalValueImpl::cached(val.0, GlobalDataStatus::Clean).map_err(|(err, _val)| err)?,
Expand All @@ -3501,7 +3543,7 @@ impl GlobalValue {
.map_err(|(err, val)| (err, Value(val)))
}

pub fn borrow_global(&self) -> PartialVMResult<Value> {
pub fn borrow_global(&mut self) -> PartialVMResult<Value> {
Ok(Value(self.0.borrow_global()?))
}

Expand Down Expand Up @@ -4752,7 +4794,7 @@ impl GlobalValue {
}

match &self.0 {
G::None | G::Deleted => None,
G::None | G::Deleted | G::Fetched { .. } => None,
G::Cached { fields, .. } | G::Fresh { fields } => Some(Wrapper(fields)),
}
}
Expand Down
Loading