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][stdlib] Implement mem::swap native move call #14786

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

igor-aptos
Copy link
Contributor

Description

If we have a field that contains non-copyable type, it is impossible to change it, and get the old value back.

Adding two methods:

  • native mem::swap, that implements swap of contents of two mutable references
  • mem::replace, as a simple wrapper based on mem::swap

How Has This Been Tested?

provided unit tests

Type of Change

  • New feature

Which Components or Systems Does This Change Impact?

  • Move/Aptos Virtual Machine

Checklist

  • I have read and followed the CONTRIBUTING doc
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I identified and added all stakeholders and component owners affected by this change as reviewers
  • I tested both happy and unhappy path of the functionality
  • I have made corresponding changes to the documentation

Copy link

trunk-io bot commented Sep 27, 2024

⏱️ 1h 45m total CI duration on this PR
Slowest 15 Jobs Cumulative Duration Recent Runs
rust-move-unit-coverage 16m 🟩
rust-move-unit-coverage 11m 🟩
rust-move-unit-coverage 10m 🟩
rust-move-tests 10m 🟩
rust-move-tests 10m 🟩
rust-move-unit-coverage 10m 🟩
rust-move-tests 10m 🟥
rust-move-tests 9m 🟥
rust-cargo-deny 7m 🟩🟩🟩🟩
check-dynamic-deps 4m 🟩🟩🟩🟩
general-lints 3m 🟩🟩🟩🟩
semgrep/ci 2m 🟩🟩🟩🟩
file_change_determinator 52s 🟩🟩🟩🟩
permission-check 22s 🟩🟩🟩🟩🟩
file_change_determinator 20s 🟩🟩

settingsfeedbackdocs ⋅ learn more about trunk.io

Copy link

codecov bot commented Sep 27, 2024

Codecov Report

Attention: Patch coverage is 80.08658% with 46 lines in your changes missing coverage. Please review.

Project coverage is 60.1%. Comparing base (0e4f5df) to head (8b0d35b).
Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...party/move/move-vm/types/src/values/values_impl.rs 72.5% 37 Missing ⚠️
...ptos-move/framework/move-stdlib/src/natives/mem.rs 75.0% 9 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #14786    +/-   ##
========================================
  Coverage    60.1%    60.1%            
========================================
  Files         856      857     +1     
  Lines      211110   211341   +231     
========================================
+ Hits       126962   127147   +185     
- Misses      84148    84194    +46     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lightmark
Copy link
Contributor

What's this for?

@igor-aptos
Copy link
Contributor Author

This is needed to be able to modify fields, when they hold a non-copyable non-dropable type.

For example, you cannot do this today:

struct A<T> {
   field: T,
}

public fun swap<T>(self: &mut A<T>, value: T): T {
   let old_value = self.field;  <-- doesn't compile, expects copy
   self.field = value;          <-- doesn't compile, expects drop
   old_value
}

https://aptos-org.slack.com/archives/C05V6JEKZQ9/p1727393314700309

@igor-aptos igor-aptos force-pushed the igor/move_mem_swap branch 4 times, most recently from 6db4bf8 to bd33c50 Compare October 8, 2024 19:00

let cost = MEM_SWAP_BASE
+ MEM_SWAP_PER_ABS_VAL_UNIT
* (context.abs_val_size(&args[0]) + context.abs_val_size(&args[1]));
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to use abstract_value_size_dereferenced as it is the sizes of the referenced values that matter, not the size of the references themselves.
https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/aptos-gas-schedule/src/gas_schedule/misc.rs#L293

You may need to add a helper to expose it through the native context.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why dereferenced value? We are not allocating new memory space?

Copy link
Contributor

Choose a reason for hiding this comment

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

Because the current implementation effective makes deep clones of the two values being swapped.

Anyway, this discussion may no longer be relevant as I've come up with a more efficient constant-time implementation. Will share the PR very very soon.

@igor-aptos igor-aptos force-pushed the igor/move_mem_swap branch 2 times, most recently from 783b338 to 3d7877e Compare October 10, 2024 00:07
Copy link
Contributor

@vgao1996 vgao1996 left a comment

Choose a reason for hiding this comment

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

Okay, I ended up just implementing the more efficient swap as I originally promised:

Please take a careful look and help me check if the implementation is correct. I also just realized that swap is a really powerful feature, and we must take utmost caution to ensure its implementation is correct, or very bad things could happen. For this reason, I'd like to request at least one additional review from @runtian-zhou @ziaptos or @georgemitenkov + a third party audit.

@@ -0,0 +1,65 @@
#[test_only]
module std::mem_tests {
Copy link
Contributor

Choose a reason for hiding this comment

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

The tests here are inadequate considering how important and dangerous the swap feature is.

We need to have full test coverage for all these swaps:

(container, container)

(indexed ref - generic, indexed ref - generic)
(indexed ref - specialized, indexed ref - specialized)
(indexed ref - generic, indexed ref - specialized)
(indexed ref - specialized, indexed ref - generic)

I would also encourage us add (rust-level) unit tests in move-vm/types/src/values/value_tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added some tests, but can add more.

can you explain when does a &mut become each of the :

  • indexer ref - generic
  • indexed ref - specialized
  • container

.map(|(s, _)| s)
.partition(|s| s.is_aptos_code());

let expected_code = vec![
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these changes related? Not familiar with the code here and don't know what it is trying to achieve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there is a check that all resources published in genesis are indexed correctly.

here we added mem.move, so it failed. failing on 153>152 is confusing.

so I just pulled the list of resources - and am asserting on them, and failure will tell which resource is different.

cc @areshand

@vgao1996
Copy link
Contributor

@wrwg I'm also wondering if we should just make this a bytecode instruction. Can probably do it later though.

Copy link
Contributor Author

@igor-aptos igor-aptos left a comment

Choose a reason for hiding this comment

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

is there a way to unit-test call_native?

i.e. we are relying for a lot of tests in third_party/move/move-vm/types/src/values/value_tests.rs that are not failing checks internally, to fail checks when native call types get checked.

Otherwise I am not sure how to have that test coverage, as in end-to-end tests - such code would not compile.

@igor-aptos
Copy link
Contributor Author

addressed all comments and added more tests

Copy link
Contributor

@georgemitenkov georgemitenkov left a comment

Choose a reason for hiding this comment

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

Pass 1: everything but the values

@@ -0,0 +1,27 @@
/// Module with methods for safe memory manipulation.
/// I.e. swapping/replacing non-copyable/non-droppable types.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I.e. reads a bit weird, maybe For example? Also why break into 2 sentences?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the example is just redundant, removing. swap below has the docstring as well.

@@ -69,7 +69,7 @@
/// global operations.
/// - V1
/// - TBA
pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_22;
pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_23;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add documentation to new 1.23 in the comment above that new natives have been added

// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

// Copyright (c) The Diem Core Contributors
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is wrong, multiple licenses and why we even have Diem here?


/// The feature is not enabled.
/// (0xD is unavailable)
pub const EFEATURE_NOT_ENABLED: u64 = 0x0D_0001;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we document this in Move code? Running into these errors is such a pain because you need to search through aptos-core hoping to get the right constant....

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, don't we have errors::unavailable Rust helper already? I think it exists in aptos-types though...

types/src/on_chain_config/aptos_features.rs Show resolved Hide resolved
@@ -317,6 +320,10 @@ impl Features {
self.is_enabled(FeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT)
}

pub fn is_native_memory_operations_enabled(&self) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: are instead of is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

everything is using is?

Copy link
Contributor

Choose a reason for hiding this comment

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

It just reads weird to me because it is plural, but maybe a personal taste

fn swap_contents(&self, other: &Self) -> PartialVMResult<()> {
use Container::*;

// TODO: check if unique ownership?
Copy link
Contributor

Choose a reason for hiding this comment

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

If you add a todo, add a github handle or create a ticket to track this so someone else can have more context about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh this was from Victor's change, missed it. removing, and adding comment.

/// Swap contents of two passed mutable references.
public(friend) native fun swap<T>(left: &mut T, right: &mut T);

/// Replace value reference points to with the given new value,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think articles are missing, but I am not a native speaker 😄

debug_assert!(args.len() == 2);

if args.len() != 2 {
return Err(SafeNativeError::InvariantViolation(PartialVMError::new(
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a message?

Copy link
Contributor Author

@igor-aptos igor-aptos left a comment

Choose a reason for hiding this comment

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

oh I missed your previous review, addressing that one and the new one

@@ -0,0 +1,27 @@
/// Module with methods for safe memory manipulation.
/// I.e. swapping/replacing non-copyable/non-droppable types.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the example is just redundant, removing. swap below has the docstring as well.

fn swap_contents(&self, other: &Self) -> PartialVMResult<()> {
use Container::*;

// TODO: check if unique ownership?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh this was from Victor's change, missed it. removing, and adding comment.

types/src/on_chain_config/aptos_features.rs Show resolved Hide resolved
@@ -317,6 +320,10 @@ impl Features {
self.is_enabled(FeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT)
}

pub fn is_native_memory_operations_enabled(&self) -> bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

everything is using is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants