Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
c231052
Merge branch 'feat/mdrop' into mdrop-mb
andrei-marinica Jan 16, 2026
2e09013
fix after merge
andrei-marinica Jan 16, 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
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
8 changes: 8 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
316 changes: 316 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 @@ -48,7 +48,323 @@ impl<V> HandleMap<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);

// 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 @@ -243,7 +243,7 @@ pub trait ForwarderNftModule: fwd_storage_legacy::ForwarderStorageModule {
&amount,
self.blockchain().get_gas_left(),
&function,
&arguments.to_arg_buffer(),
&arguments.into_arg_buffer(),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub trait ForwarderQueue {
to,
gas_limit,
endpoint_name,
args: args.to_arg_buffer(),
args: args.into_arg_buffer(),
payments: payments.clone(),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub trait ForwarderRawAlternativeInit: super::forwarder_raw_common::ForwarderRaw
self.tx()
.to(&to)
.raw_call(endpoint_name)
.arguments_raw(args.to_arg_buffer())
.arguments_raw(args.into_arg_buffer())
.async_call_and_exit();
}

Expand Down Expand Up @@ -61,7 +61,7 @@ pub trait ForwarderRawAlternativeInit: super::forwarder_raw_common::ForwarderRaw
.gas(half_gas)
.egld(payment)
.raw_call(endpoint_name)
.arguments_raw(args.to_arg_buffer())
.arguments_raw(args.into_arg_buffer())
.returns(ReturnsRawResult)
.sync_call();

Expand Down
Loading
Loading