Skip to content
Merged
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
a25bb62
managed dealloc - api
andrei-marinica Jun 10, 2024
9dd2bff
drop for managed buffer
alyn509 Jun 18, 2024
951a7d8
leave just managed buffer destructor
alyn509 Jun 18, 2024
db4ea70
Merge branch 'master' into mdrop
alyn509 Aug 14, 2024
8c93071
Merge branch 'rc/v0.54' into mdrop
andrei-marinica Sep 18, 2024
38d100e
Merge branch 'rc/v0.54' into mdrop
andrei-marinica Oct 25, 2024
94e6981
managed dealloc - implementation
andrei-marinica Oct 27, 2024
96f3944
Merge branch 'rc/v0.54' into mdrop
andrei-marinica Oct 28, 2024
74e38a7
Merge branch 'managed-ref-mut' into mdrop-merge
andrei-marinica Nov 11, 2024
b54f8d5
Merge branch 'man-opt-refactor' into mdrop-merge
andrei-marinica Nov 20, 2024
4b87e5b
Merge branch 'master' into mdrop3
andrei-marinica Jul 7, 2025
a81e3fd
Merge pull request #2112 from multiversx/mdrop3
andrei-marinica Jul 8, 2025
4d42c54
managed dealloc - m buffer wip
andrei-marinica Jul 8, 2025
5c824fe
managed dealloc - remove item drop
andrei-marinica Jul 8, 2025
c483300
managed dealloc - unsafe read_from_payload
andrei-marinica Jul 8, 2025
a93e906
mVec does not force mBuffer drop, vh is dropped explicitly
mihaicalinluca Jul 18, 2025
f9cfb2b
protect dispatcher lock all apis
mihaicalinluca Jul 21, 2025
d1b302a
MultiValueEncoded to_arg_buffer fix
andrei-marinica Jul 21, 2025
3e2a1a3
cargo fmt
andrei-marinica Jul 21, 2025
c62ef32
managed dealloc - removed some leaking from_handle calls
andrei-marinica Jul 21, 2025
fa1d233
managed dealloc - ManagedVecRef soft drop via save_to_payload
andrei-marinica Jul 24, 2025
534d39c
managed dealloc - ManagedVecRefMut cleanup & comment
andrei-marinica Jul 24, 2025
20843a8
typo
andrei-marinica Jul 24, 2025
a001f6a
Merge branch 'rc/v0.65' into feat/mdrop
andrei-marinica Jan 16, 2026
b7069f4
Merge pull request #2269 from multiversx/feat-mdrop-merge
andrei-marinica Jan 16, 2026
c231052
Merge branch 'feat/mdrop' into mdrop-mb
andrei-marinica Jan 16, 2026
2e09013
fix after merge
andrei-marinica Jan 16, 2026
15900d7
merge master -> feat/mdrop
andrei-marinica Mar 6, 2026
be956b5
Merge pull request #2304 from multiversx/mdrop-merge
andrei-marinica Mar 6, 2026
e2e071c
managed dealloc - memory benchmark
andrei-marinica Mar 13, 2026
88c47c0
Merge branch 'feat/mdrop' into mdrop-mb
andrei-marinica Mar 13, 2026
9a4dea8
fix after merge
andrei-marinica Mar 13, 2026
926b493
test fix
andrei-marinica Mar 13, 2026
5cce989
EncodedManagedVecItem decode fix
andrei-marinica Mar 16, 2026
cf19bd3
typo
andrei-marinica Mar 16, 2026
200e03c
cleanup
andrei-marinica Mar 16, 2026
578aef3
managed dealloc - print cleanup
andrei-marinica Mar 16, 2026
e32273c
managed dealloc - memory benchmark doc fix
andrei-marinica Mar 16, 2026
25fdcbe
managed dealloc - fix m vec eq memory leak
andrei-marinica Mar 16, 2026
049f8d9
Merge pull request #2114 from multiversx/mdrop-mb
andrei-marinica Mar 16, 2026
c91c8c2
Merge branch 'master' into mdrop-merge
andrei-marinica Mar 16, 2026
2c1a17c
Merge pull request #2310 from multiversx/mdrop-merge
andrei-marinica Mar 16, 2026
de143da
managed dealloc - benchmark more types
andrei-marinica Mar 16, 2026
7eb7dd1
managed dealloc - BigInt
andrei-marinica Mar 16, 2026
8375eca
managed dealloc - BigInt unwind fix
andrei-marinica Mar 17, 2026
4fc36f1
clippy fix
andrei-marinica Mar 17, 2026
a808123
BigInt - new_init_handle
andrei-marinica Mar 17, 2026
455f21f
managed dealloc - only call drop if context is live
andrei-marinica Mar 17, 2026
d242ff1
cleanup
andrei-marinica Mar 17, 2026
49f7bc5
cleanup
andrei-marinica Mar 17, 2026
399f87e
fixes after review
andrei-marinica Mar 18, 2026
aed08e6
Merge pull request #2312 from multiversx/mdrop-bi
andrei-marinica Mar 18, 2026
3915f5f
managed dealloc - BigFloat
andrei-marinica Mar 16, 2026
24514e7
managed ealloc - benchmark managed map
andrei-marinica Mar 16, 2026
06c1068
managed dealloc - managed map
andrei-marinica Mar 16, 2026
40d3deb
managed dealloc - BigFloat init fix
andrei-marinica Mar 18, 2026
b9138e3
Merge pull request #2313 from multiversx/mdrop-rest
andrei-marinica Mar 18, 2026
8c32ccf
managed drop - benchmark more vec types
andrei-marinica Mar 19, 2026
50e76cd
ManagedVecItem StaticApi drop, fixed memory leaks & double drops
andrei-marinica Mar 19, 2026
7904853
ManagedVec - docs
andrei-marinica Mar 19, 2026
3703ece
ManagedVec - more tests
andrei-marinica Mar 19, 2026
9351213
ManagedVecItem derive - generate requires_drop correctly
andrei-marinica Mar 19, 2026
efc7202
managed dealloc - benchmark with custom types
andrei-marinica Mar 19, 2026
0744f7f
managed dealloc - more benchmarks
andrei-marinica Mar 20, 2026
66473ad
doc fix
andrei-marinica Mar 20, 2026
3afeaae
ManagedVec slice out of bounds fix & test
andrei-marinica Mar 20, 2026
d0fbb88
managed dealloc - tx benchmarks
andrei-marinica Mar 20, 2026
7345145
managed dealloc - benchmark more types
andrei-marinica Mar 20, 2026
35e26ae
Merge pull request #2314 from multiversx/mdrop-vec
andrei-marinica Mar 23, 2026
6b4847a
StaticApi threading test
andrei-marinica Mar 23, 2026
805e7fb
Static Api more threading tests (revealing Send issue)
andrei-marinica Mar 27, 2026
28dc62e
DebugHandle !Send
andrei-marinica Mar 27, 2026
12b93db
StaticApiHandle, !Send
andrei-marinica Mar 27, 2026
a9ec51c
DebugHandle file rename
andrei-marinica Mar 27, 2026
f212429
test fix
andrei-marinica Mar 30, 2026
b2d59cf
imports cleanup
andrei-marinica Mar 30, 2026
fa581f1
DebugHandle/StaticHandle !Send + !Sync test
andrei-marinica Mar 30, 2026
c72f41c
doc
andrei-marinica Mar 30, 2026
5ac255d
managed type !Send + !Sync tests
andrei-marinica Mar 30, 2026
5746824
Merge pull request #2322 from multiversx/threading
andrei-marinica Mar 30, 2026
72dee34
Merge branch master into feat/mdrop
andrei-marinica Mar 30, 2026
b1cad6f
Merge pull request #2323 from multiversx/mdrop-merge
andrei-marinica Mar 30, 2026
48c69e6
vm - remove handle debug_assert
andrei-marinica Mar 30, 2026
c63bd6c
big int neg optimization & tests
andrei-marinica Mar 30, 2026
1c570cd
docs
andrei-marinica Mar 30, 2026
bba919e
vm - VMHooksDispatcher handler private again
andrei-marinica Mar 30, 2026
75533de
cleanup
andrei-marinica Mar 30, 2026
6f28b87
managed benchmark readme fix
andrei-marinica Mar 30, 2026
5afcaea
Merge pull request #2325 from multiversx/mdrop-fix
andrei-marinica Mar 30, 2026
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
9 changes: 9 additions & 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ members = [
"sdk/http",
"sdk/scenario-format",

"tools/mxpy-snippet-generator",
# "tools/plotter",
"tools/interactor-system-func-calls/",
"tools/interactor-delegation-func-calls/",
"tools/interactor-governance-func-calls/",
"tools/gas-schedule-generator",
"tools/managed-mem-bench",
"tools/mxpy-snippet-generator",
"tools/op-test-gen",
"tools/wat-gen",

Expand Down
320 changes: 320 additions & 0 deletions chain/vm/src/host/context/managed_type_container/handle_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,324 @@ impl<V> HandleMap<V> {
pub fn insert(&mut self, handle: RawHandle, value: V) {
let _ = self.map.insert(handle, value);
}

pub fn is_empty(&self) -> bool {
self.map.is_empty()
}

pub fn len(&self) -> usize {
self.map.len()
}

pub fn remove_handle(&mut self, handle: RawHandle) {
assert!(
self.map.contains_key(&handle),
"attempting to remove non-existing handle {handle}, this is a memory management issue"
);
let _ = self.map.remove(&handle);
Comment thread
andrei-marinica marked this conversation as resolved.

// Shrink only when capacity has grown 4x beyond the live entry count.
// This triggers at most O(log n) times during a bulk delete, keeping
// total rehash work at O(n log n) instead of O(n²).
if self.map.capacity() > 4 * self.map.len() + 16 {
self.map.shrink_to_fit();
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_new_handle_map_is_empty() {
let map: HandleMap<String> = HandleMap::new();
assert_eq!(map.len(), 0);
assert_eq!(map.next_handle, 0);
}

#[test]
fn test_default_handle_map_is_empty() {
let map: HandleMap<i32> = HandleMap::default();
assert_eq!(map.len(), 0);
assert_eq!(map.next_handle, 0);
}

#[test]
fn test_insert_new_handle_raw_single() {
let mut map = HandleMap::new();
let handle = map.insert_new_handle_raw("test value");

assert_eq!(handle, 0);
assert_eq!(map.next_handle, 1);
assert_eq!(map.len(), 1);
assert_eq!(*map.get(handle), "test value");
}

#[test]
fn test_insert_new_handle_raw_multiple() {
let mut map = HandleMap::new();

let h0 = map.insert_new_handle_raw(100);
let h1 = map.insert_new_handle_raw(200);
let h2 = map.insert_new_handle_raw(300);

assert_eq!(h0, 0);
assert_eq!(h1, 1);
assert_eq!(h2, 2);
assert_eq!(map.next_handle, 3);
assert_eq!(map.len(), 3);

assert_eq!(*map.get(h0), 100);
assert_eq!(*map.get(h1), 200);
assert_eq!(*map.get(h2), 300);
}

#[test]
fn test_get_existing_handle() {
let mut map = HandleMap::new();
let handle = map.insert_new_handle_raw(vec![1, 2, 3]);

let value = map.get(handle);
assert_eq!(value, &vec![1, 2, 3]);
}

#[test]
#[should_panic(expected = "handle not found: 999")]
fn test_get_non_existent_handle_panics() {
let map: HandleMap<i32> = HandleMap::new();
let _ = map.get(999);
}

#[test]
fn test_get_mut_and_modify() {
let mut map = HandleMap::new();
let handle = map.insert_new_handle_raw(42);

{
let value = map.get_mut(handle);
*value = 100;
}

assert_eq!(*map.get(handle), 100);
}

#[test]
#[should_panic(expected = "handle not found: 5")]
fn test_get_mut_non_existent_handle_panics() {
let mut map: HandleMap<String> = HandleMap::new();
let _ = map.get_mut(5);
}

#[test]
fn test_insert_at_specific_handle() {
let mut map = HandleMap::new();

map.insert(10, "value at 10");
map.insert(20, "value at 20");

assert_eq!(map.len(), 2);
assert_eq!(*map.get(10), "value at 10");
assert_eq!(*map.get(20), "value at 20");
// next_handle should still be 0 since we didn't use insert_new_handle_raw
assert_eq!(map.next_handle, 0);
}

#[test]
fn test_insert_overwrites_existing_handle() {
let mut map = HandleMap::new();
let handle = map.insert_new_handle_raw("original");

map.insert(handle, "updated");

assert_eq!(map.len(), 1);
assert_eq!(*map.get(handle), "updated");
}

#[test]
fn test_remove_handle_success() {
let mut map = HandleMap::new();
let h0 = map.insert_new_handle_raw(100);
let h1 = map.insert_new_handle_raw(200);

assert_eq!(map.len(), 2);

map.remove_handle(h0);

assert_eq!(map.len(), 1);
assert_eq!(*map.get(h1), 200);
}

#[test]
#[should_panic(
expected = "attempting to remove non-existing handle 42, this is a memory management issue"
)]
fn test_remove_non_existent_handle_panics() {
let mut map: HandleMap<i32> = HandleMap::new();
map.remove_handle(42);
}

#[test]
#[should_panic(
expected = "attempting to remove non-existing handle 0, this is a memory management issue"
)]
fn test_remove_already_removed_handle_panics() {
let mut map = HandleMap::new();
let handle = map.insert_new_handle_raw("value");

map.remove_handle(handle);
// Attempting to remove again should panic
map.remove_handle(handle);
}

#[test]
fn test_handle_lifecycle() {
let mut map = HandleMap::new();

// Insert multiple handles
let h0 = map.insert_new_handle_raw("zero");
let h1 = map.insert_new_handle_raw("one");
let h2 = map.insert_new_handle_raw("two");

assert_eq!(map.len(), 3);

// Remove middle handle
map.remove_handle(h1);
assert_eq!(map.len(), 2);

// Can still access other handles
assert_eq!(*map.get(h0), "zero");
assert_eq!(*map.get(h2), "two");

// Insert new handle - should continue from next_handle
let h3 = map.insert_new_handle_raw("three");
assert_eq!(h3, 3);
assert_eq!(map.len(), 3);
}

#[test]
fn test_stress_many_handles() {
let mut map = HandleMap::new();
let count = 1000;

// Insert many handles
for i in 0..count {
let handle = map.insert_new_handle_raw(i);
assert_eq!(handle, i);
}

assert_eq!(map.len(), count as usize);
assert_eq!(map.next_handle, count);

// Verify all values are correct
for i in 0..count {
assert_eq!(*map.get(i), i);
}

// Remove every other handle
for i in (0..count).step_by(2) {
map.remove_handle(i);
}

assert_eq!(map.len(), (count / 2) as usize);

// Verify remaining handles
for i in (1..count).step_by(2) {
assert_eq!(*map.get(i), i);
}
}

#[test]
fn test_handle_map_with_complex_type() {
#[derive(Debug, PartialEq)]
struct ComplexData {
id: u32,
name: String,
values: Vec<i32>,
}

let mut map = HandleMap::new();

let data1 = ComplexData {
id: 1,
name: "first".to_string(),
values: vec![1, 2, 3],
};

let data2 = ComplexData {
id: 2,
name: "second".to_string(),
values: vec![4, 5, 6],
};

let h1 = map.insert_new_handle_raw(data1);
let h2 = map.insert_new_handle_raw(data2);

assert_eq!(map.get(h1).id, 1);
assert_eq!(map.get(h1).name, "first");
assert_eq!(map.get(h2).values, vec![4, 5, 6]);

// Modify complex data
map.get_mut(h1).values.push(99);
assert_eq!(map.get(h1).values, vec![1, 2, 3, 99]);
}

#[test]
fn test_no_handle_reuse_after_remove() {
let mut map = HandleMap::new();

let h0 = map.insert_new_handle_raw("value0");
let _h1 = map.insert_new_handle_raw("value1");

map.remove_handle(h0);

// Next handle should be 2, not reusing 0
let h2 = map.insert_new_handle_raw("value2");
assert_eq!(h2, 2);
assert_eq!(map.next_handle, 3);
}

#[test]
fn test_insert_and_insert_new_handle_raw_interleaved() {
let mut map = HandleMap::new();

// Use insert_new_handle_raw
let h0 = map.insert_new_handle_raw("auto0");
assert_eq!(h0, 0);

// Manually insert at specific handle
map.insert(100, "manual100");

// Use insert_new_handle_raw again
let h1 = map.insert_new_handle_raw("auto1");
assert_eq!(h1, 1);

// Verify all values
assert_eq!(*map.get(0), "auto0");
assert_eq!(*map.get(1), "auto1");
assert_eq!(*map.get(100), "manual100");
assert_eq!(map.len(), 3);
}

#[test]
fn test_shrink_triggered_automatically_after_bulk_delete() {
let mut map = HandleMap::new();
let count = 1000;

for i in 0..count {
map.insert_new_handle_raw(i);
}

let capacity_before = map.map.capacity();
assert!(capacity_before >= count as usize);

// Removing all entries triggers automatic shrinking via the 4x heuristic.
for i in 0..count {
map.remove_handle(i);
}
assert_eq!(map.len(), 0);

// Capacity must be much smaller than after the bulk insert.
assert!(map.map.capacity() < capacity_before);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ impl ManagedTypeContainer {
pub fn bf_overwrite(&mut self, handle: RawHandle, value: f64) {
self.big_float_map.insert(handle, value);
}

pub fn bf_remove(&mut self, handle: RawHandle) {
self.big_float_map.remove_handle(handle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ impl ManagedTypeContainer {
self.big_int_map.insert_new_handle_raw(value)
}

pub fn bi_remove(&mut self, handle: RawHandle) {
self.big_int_map.remove_handle(handle);
}

pub fn bi_overwrite(&mut self, destination: RawHandle, value: num_bigint::BigInt) {
self.big_int_map.insert(destination, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ impl ManagedTypeContainer {

num_bytes_copied
}

pub fn mb_remove(&mut self, handle: RawHandle) {
self.managed_buffer_map.remove_handle(handle);
}
}

pub fn handle_to_be_bytes(handle: RawHandle) -> [u8; 4] {
Expand Down
Loading
Loading