-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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][closures] Type and Value representation and serialization #15670
base: main
Are you sure you want to change the base?
Conversation
⏱️ 1h 6m total CI duration on this PR
|
4972a5c
to
2fed681
Compare
2fed681
to
9c0814c
Compare
9c0814c
to
8b11451
Compare
8b11451
to
dcb6bc5
Compare
95728c9
to
6eea07c
Compare
dcb6bc5
to
bfc6f4c
Compare
6eea07c
to
06363e0
Compare
bfc6f4c
to
355525d
Compare
6f6afe3
to
f178823
Compare
e6560c5
to
b3492b5
Compare
f178823
to
2707a22
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reviews! All comments of this round addressed.
b3492b5
to
c668c04
Compare
3864e40
to
cfde21d
Compare
[PR 3/n vm closures] This implements types and values for functions and closures, as well as serialization. For a detailed discussion, see the design doc. It does not yet implement the interpreter and runtime verification. Overview: - `MoveValue` and `MoveTypeLayout` are extended to represent closures and functions. A closure carries the function name, the closure mask, the function layout, and the captured arguments. The function layout enables serialization of the captured arguments without any context. - Runtime `Type` is extended by functions. - `ValueImpl` is extended by a closure variant. This representation uses `trait AbstractFunction` to represent the function value and implement core functionality like comparison and printing. The existing trait `FunctionValueExtension` is used to create `AbstracFunction` from the serialized representation. - Similar as with `MoveValue`, the serialized representation of `ValueImpl::Closure` contains the full function layout, enabling to deserialize the captured arguments context independently. This design has been chosen for robustness, avoiding a dependency of serialization from loaded functions, and to enable fully 'lazy' deserialization of closures. The later allows a contract to store hundreds of function values and on loading them from storage, avoiding loading the associated code until the moment the closure is actually executed. - `AbstractFunction` is implemented in the loader such that it can be either in 'unresolved' or in `resolved' state.
cfde21d
to
70bf138
Compare
70bf138
to
41baffe
Compare
@@ -366,6 +378,13 @@ impl AbstractValueSizeGasParameters { | |||
false | |||
} | |||
|
|||
#[inline] | |||
fn visit_closure(&mut self, _depth: usize, _len: usize) -> bool { | |||
// TODO(#15664): independent gas parameter for closures? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vgao1996 can you look at gas related aspects?
out.push_str(&fun.to_stable_string()); | ||
format_vector( | ||
context, | ||
data.captured_layouts.iter(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to return an error here instead of doing this? get_serialization_data
is also not cheap for resolved functions with all conversions, I do not see a value of printing partial information + we should be charging gas here like we do for structs. If we want to keep it in tests, let's put it under cfg(test)
etc, and otherwise return an error, e.g.
return Err(SafeNativeError::Abort {
abort_code: EINVALID_FORMAT,
});
third_party/move/move-vm/types/src/loaded_data/runtime_types.rs
Outdated
Show resolved
Hide resolved
third_party/move/move-vm/types/src/loaded_data/runtime_types.rs
Outdated
Show resolved
Hide resolved
third_party/move/move-vm/runtime/src/storage/ty_layout_converter.rs
Outdated
Show resolved
Hide resolved
third_party/move/move-vm/runtime/src/storage/ty_layout_converter.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can already add tests for closure de/serialization. We have two representations here: MoveClosure and ValueImpl and we can implement some round trip tests there
@@ -220,7 +220,7 @@ pub(crate) fn is_valid_txn_arg( | |||
)) | |||
}) | |||
}, | |||
Signer | Reference(_) | MutableReference(_) | TyParam(_) => false, | |||
Signer | Reference(_) | MutableReference(_) | TyParam(_) | Function { .. } => false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change seems unneeded? Maybe affected by rebase?
let converter = StorageLayoutConverter::new(module_storage); | ||
if captured_arg_types.len() != captured_layouts.len() { | ||
return Err( | ||
PartialVMError::new(StatusCode::FUNCTION_RESOLUTION_FAILURE).with_message( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I‘d prefer not having ok
but return error immediately here and at line 235
/// immutable (public), also store. | ||
pub fn abilities(&self) -> AbilitySet { | ||
let result = AbilitySet::singleton(Ability::Copy).add(Ability::Drop); | ||
if !self.is_friend_or_private { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need to check the captured types as well?
}) | ||
} | ||
} | ||
|
||
// Matches the actual returned type to the expected type, binding any type args to the | ||
// necessary type as stored in the map. The expected type must be a concrete type (no TyParam). | ||
// Returns true if a successful match is made. | ||
// TODO: is this really needed in presence of paranoid mode? This does a deep structural | ||
// comparison and is expensive. | ||
fn match_return_type<'a>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC this function is needed for handling entry function with returned types needed by Sui, we didn't need such functionality and can probably clean up such logic later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM. I felt this PR needs some unit tests on ser/de logic as they can be tested rightaway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also changes in the VM seems to be gated reasonably so we should be safe with merging it.
Description
[PR 4/n vm closures]
This implements types and values for functions and closures, as well as serialization. For a detailed discussion, see the design doc. It does not yet implement the interpreter and runtime verification.
Overview:
MoveValue
andMoveTypeLayout
are extended to represent closures and functions. A closure carries the function name, the closure mask, the layout of the captured arguments, and their values. The layout enables serialization of the captured arguments without any context.Type
is extended by functions.ValueImpl
is extended by a closure variant. This representation usestrait AbstractFunction
to represent the function value and implement core functionality like comparison and printing. The existing traitFunctionValueExtension
is used to createAbstractFunction
from the serialized representation.MoveValue
, the serialized representation ofValueImpl::Closure
contains the captured parameter layouts, enabling to deserialize the captured arguments context independently. This design has been chosen for robustness, avoiding a dependency of serialization from loaded functions, and to enable fully 'lazy' deserialization of closures. The later allows a contract to store hundreds of function values and on loading them from storage, avoiding loading the associated code until the moment the closure is actually executed.AbstractFunction
is implemented in the loader such that it can be either in 'unresolved' or in `resolved' state.How Has This Been Tested?
Testing postponed until e2e wiring is up
Type of Change
Which Components or Systems Does This Change Impact?