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

Optimization: Enhanced Orthogonal Persistence with Object Pooling #4463

Merged
merged 8 commits into from
Mar 20, 2024
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
12 changes: 6 additions & 6 deletions design/OrthogonalPersistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ This is because these objects may also need to survive upgrades and must not be

The incremental GC also operates on these objects, meaning that forwarding pointer resolution is also necessary for these objects.

For memory and runtime efficiency, object pooling is implemented for compile-time-known constant objects (with side-effect-free initialization), i.e. those objects are already created on program initialization/upgrade in the dynamic heap and thereafter the reference to the corresponding prefabricated object is looked up whenever the constant value is needed at runtime.

The runtime systems avoids any global Wasm variables for state that needs to be preserved on upgrades.
Instead, such global runtime state is stored in the persistent metadata.

Sharing optimization (pooling) is possible for compile-time-known objects, see below.

### Wasm Data Segments
Only passive Wasm data segments are used by the compiler and runtime system. In contrast to ordinary active data segments, passive segments can be explicitly loaded to a dynamic address.

Expand All @@ -115,10 +115,10 @@ Once operating on the stable heap, the system prevents downgrade attempts to the
### Old Stable Memory
The old stable memory remains equally accessible as secondary memory with the new support.

## Possible Extensions
The following extensions or optimization could be applied in the future:
* 64-bit memory: Extend the main memory to 64-bit by using Wasm64, see https://github.com/dfinity/motoko/pull/4136. The memory layout would need to be extended. Moreover, it would be beneficial to introduce a dynamic partition table for the GC. Ideally, stable heap support is directly rolled out for 64-bit to avoid complicated memory layout upgrades from 32-bit to 64-bit.
* Object pooling: Compile-time-known objects can be shared in the dynamic heap by remembering them in an additional pool table. The pool table needs to be registered as a transient GC root and is recreated on canister upgrades.
## Current Limitations
* Freeing old object fields: While new program versions can drop object fields, the runtime system should also delete the redundant fields of persistent objects of previous program versions. This could be realized during garbage collection when objects are copied. For this purpose, the runtime system may maintain a set of field hashes in use and consult this table during garbage collection.
* Bounded Rust call stack size: The Rust call stack size needs to be bounded and can no longer be configured by the user.
* The Wasm profiler (only used for the flamegraphs) is no longer applicable because the underlying `parity-wasm` crate lacks full support of passive data segments. A re-implementation of the profiler would be needed.

## Related PRs

Expand Down
28 changes: 16 additions & 12 deletions rts/motoko-rts/src/gc/incremental/roots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use motoko_rts_macros::ic_mem_fn;

use crate::{types::Value, visitor::is_non_null_pointer_field};

/// Root referring to all canister variables.
/// This root is reinitialized on each canister upgrade.
/// An array referring to the static program variables, being
/// - All canister variables.
/// - Pooled shared objects.
/// The array constitutes a GC root that is reinitialized on each canister upgrade.
/// The scalar sentinel denotes an uninitialized root.
#[cfg(feature = "ic")]
static mut STATIC_ROOT: Value = Value::from_scalar(0);
static mut STATIC_VARIABLES: Value = Value::from_scalar(0);

/// GC root set.
pub type Roots = [*mut Value; 6];
Expand All @@ -19,7 +21,7 @@ pub unsafe fn root_set() -> Roots {
region::region0_get_ptr_loc,
};
[
static_root_location(),
static_variables_location(),
continuation_table_loc(),
stable_actor_location(),
stable_type_descriptor().candid_data_location(),
Expand All @@ -41,21 +43,23 @@ pub unsafe fn visit_roots<C, V: Fn(&mut C, *mut Value)>(
}

#[cfg(feature = "ic")]
unsafe fn static_root_location() -> *mut Value {
&mut STATIC_ROOT as *mut Value
unsafe fn static_variables_location() -> *mut Value {
&mut STATIC_VARIABLES as *mut Value
}

#[ic_mem_fn(ic_only)]
pub unsafe fn set_static_root<M: crate::memory::Memory>(mem: &mut M, value: Value) {
pub unsafe fn set_static_variables<M: crate::memory::Memory>(mem: &mut M, array: Value) {
use super::barriers::write_with_barrier;
use crate::types::TAG_ARRAY;

let location = &mut STATIC_ROOT as *mut Value;
write_with_barrier(mem, location, value);
assert_eq!(array.tag(), TAG_ARRAY);
let location = &mut STATIC_VARIABLES as *mut Value;
write_with_barrier(mem, location, array);
}

#[no_mangle]
#[cfg(feature = "ic")]
pub unsafe extern "C" fn get_static_root() -> Value {
debug_assert!(STATIC_ROOT.is_non_null_ptr());
STATIC_ROOT
pub unsafe extern "C" fn get_static_variable(index: u32) -> Value {
debug_assert!(STATIC_VARIABLES.is_non_null_ptr());
STATIC_VARIABLES.as_array().get(index)
}
Loading