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] Avoid double hashing in favour of equivalent keys for type tag cache #15740

Merged
merged 2 commits into from
Jan 29, 2025
Merged
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.

1 change: 1 addition & 0 deletions third_party/move/move-vm/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ move-ir-compiler = { workspace = true }
move-vm-test-utils ={ workspace = true }
move-vm-types = { workspace = true, features = ["testing"] }
proptest = { workspace = true }
smallbitvec = { workspace = true }

[features]
default = []
Expand Down
138 changes: 127 additions & 11 deletions third_party/move/move-vm/runtime/src/storage/ty_tag_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,66 @@
// SPDX-License-Identifier: Apache-2.0

use crate::{loader::PseudoGasContext, RuntimeEnvironment};
use hashbrown::{hash_map::Entry, HashMap};
use move_binary_format::errors::{PartialVMError, PartialVMResult};
use move_core_types::{
language_storage::{StructTag, TypeTag},
vm_status::StatusCode,
};
use move_vm_types::loaded_data::{runtime_types::Type, struct_name_indexing::StructNameIndex};
use parking_lot::RwLock;
use std::collections::{hash_map::Entry, HashMap};
use std::hash::{Hash, Hasher};

/// Key type for [TypeTagCache] that corresponds to a fully-instantiated struct.
#[derive(Clone, Eq, PartialEq)]
struct StructKey {
idx: StructNameIndex,
ty_args: Vec<Type>,
}

#[derive(Eq, PartialEq)]
struct StructKeyRef<'a> {
idx: &'a StructNameIndex,
ty_args: &'a [Type],
}

impl StructKey {
#[cfg(test)]
fn as_ref(&self) -> StructKeyRef {
StructKeyRef {
idx: &self.idx,
ty_args: self.ty_args.as_slice(),
}
}
}

impl<'a> hashbrown::Equivalent<StructKeyRef<'a>> for StructKey {
fn equivalent(&self, other: &StructKeyRef<'a>) -> bool {
&self.idx == other.idx && self.ty_args.as_slice() == other.ty_args
}
}

impl<'a> hashbrown::Equivalent<StructKey> for StructKeyRef<'a> {
fn equivalent(&self, other: &StructKey) -> bool {
self.idx == &other.idx && self.ty_args == other.ty_args.as_slice()
}
}

// Ensure hash is the same as for StructKeyRef.
impl Hash for StructKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.idx.hash(state);
self.ty_args.hash(state);
}
}

// Ensure hash is the same as for StructKey.
impl<'a> Hash for StructKeyRef<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.idx.hash(state);
self.ty_args.hash(state);
}
}

/// An entry in [TypeTagCache] that also stores a "cost" of the tag. The cost is proportional to
/// the size of the tag, which includes the number of inner nodes and the sum of the sizes in bytes
Expand Down Expand Up @@ -46,7 +98,7 @@ pub(crate) struct PricedStructTag {
/// If thread 3 reads the tag of this enum, the read result is always **deterministic** for the
/// fixed type parameters used by thread 3.
pub(crate) struct TypeTagCache {
cache: RwLock<HashMap<StructNameIndex, HashMap<Vec<Type>, PricedStructTag>>>,
cache: RwLock<HashMap<StructKey, PricedStructTag>>,
}

impl TypeTagCache {
Expand All @@ -68,7 +120,10 @@ impl TypeTagCache {
idx: &StructNameIndex,
ty_args: &[Type],
) -> Option<PricedStructTag> {
self.cache.read().get(idx)?.get(ty_args).cloned()
self.cache
.read()
.get(&StructKeyRef { idx, ty_args })
.cloned()
}

/// Inserts the struct tag and its pseudo-gas cost ([PricedStructTag]) into the cache. Returns
Expand All @@ -80,19 +135,24 @@ impl TypeTagCache {
priced_struct_tag: &PricedStructTag,
) -> bool {
// Check if already cached.
if let Some(inner_cache) = self.cache.read().get(idx) {
if inner_cache.contains_key(ty_args) {
return false;
}
if self
.cache
.read()
.contains_key(&StructKeyRef { idx, ty_args })
{
return false;
}

let key = StructKey {
idx: *idx,
ty_args: ty_args.to_vec(),
};
let priced_struct_tag = priced_struct_tag.clone();
let ty_args = ty_args.to_vec();

// Otherwise, we need to insert. We did the clones outside the lock, and also avoid the
// double insertion.
let mut cache = self.cache.write();
if let Entry::Vacant(entry) = cache.entry(*idx).or_default().entry(ty_args) {
if let Entry::Vacant(entry) = cache.entry(key) {
entry.insert(priced_struct_tag);
true
} else {
Expand Down Expand Up @@ -225,15 +285,71 @@ mod tests {
use super::*;
use crate::config::VMConfig;
use claims::{assert_err, assert_none, assert_ok, assert_ok_eq, assert_some};
use hashbrown::Equivalent;
use move_binary_format::file_format::StructTypeParameter;
use move_core_types::{
ability::AbilitySet, account_address::AccountAddress, identifier::Identifier,
ability::{Ability, AbilitySet},
account_address::AccountAddress,
identifier::Identifier,
language_storage::ModuleId,
};
use move_vm_types::loaded_data::runtime_types::{
AbilityInfo, StructIdentifier, StructLayout, StructType, TypeBuilder,
};
use std::str::FromStr;
use smallbitvec::SmallBitVec;
use std::{collections::hash_map::DefaultHasher, str::FromStr};
use triomphe::Arc;

fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}

#[test]
fn test_struct_key_equivalence_and_hash() {
let struct_keys = [
StructKey {
idx: StructNameIndex::new(0),
ty_args: vec![],
},
StructKey {
idx: StructNameIndex::new(1),
ty_args: vec![Type::U8],
},
StructKey {
idx: StructNameIndex::new(2),
ty_args: vec![Type::Bool, Type::Vector(Arc::new(Type::Bool))],
},
StructKey {
idx: StructNameIndex::new(3),
ty_args: vec![
Type::Struct {
idx: StructNameIndex::new(0),
ability: AbilityInfo::struct_(AbilitySet::singleton(Ability::Key)),
},
Type::StructInstantiation {
idx: StructNameIndex::new(1),
ty_args: Arc::new(vec![Type::Address, Type::Struct {
idx: StructNameIndex::new(2),
ability: AbilityInfo::struct_(AbilitySet::singleton(Ability::Copy)),
}]),
ability: AbilityInfo::generic_struct(
AbilitySet::singleton(Ability::Drop),
SmallBitVec::new(),
),
},
],
},
];

for struct_key in struct_keys {
let struct_key_ref = struct_key.as_ref();
assert!(struct_key.equivalent(&struct_key_ref));
assert!(struct_key_ref.equivalent(&struct_key));
assert_eq!(calculate_hash(&struct_key), calculate_hash(&struct_key_ref));
}
}

#[test]
fn test_type_tag_cache() {
Expand Down
Loading