From ee9edef09734f68b85fbcf9f0a92b341b040c693 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Tue, 8 Aug 2023 09:50:35 +0200 Subject: [PATCH 001/260] Keep heap state on upgrade --- src/codegen/compile.ml | 46 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 023c08ba7aa..e9e0646e96b 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7155,8 +7155,49 @@ module BlobStream : Stream = struct end +module Stabilization = struct + (* TODO: Define memory layout and predefined address for actor object *) + let register_globals env = + (* Only reserve static memory without appending a data segment. + This is necessary because data segments are overwritten on upgrade + and we need to retain the old actor object address at this location. + The Wasm memory is guaranteed to be zero-initialized on canister installation. + *) + let static_address = E.reserve_static_memory env Heap.word_size in + E.add_global32 env "__actor_object" Mutable static_address; + () -(* Stabilization (serialization to/from stable memory) of both: + let actor_address env = + G.i (GlobalGet (nr (E.get_global env "__actor_object"))) + + let load_actor env = + actor_address env ^^ load_unskewed_ptr + + let save_actor env = + let (set_value, get_value) = new_local env "value" in + set_value ^^ + actor_address env ^^ get_value ^^ store_unskewed_ptr + + let empty_actor env t = + let (_, fs) = Type.as_obj t in + let fs' = List.map + (fun f -> (f.Type.lab, fun () -> Opt.null_lit env)) + fs + in + Object.lit_raw env fs' + + let stabilize env t = + save_actor env + + let destabilize env t = + load_actor env ^^ + G.i (Test (Wasm.Values.I32 I32Op.Eqz)) ^^ + G.if1 I32Type + (empty_actor env t) + (load_actor env) +end + +(* OldStabilization (serialization to/from stable memory) of both: * stable variables; and * virtual stable memory. c.f. @@ -7164,7 +7205,7 @@ end * ../../design/StableMemory.md *) -module Stabilization = struct +module OldStabilization = struct let extend64 code = code ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) @@ -11253,6 +11294,7 @@ let compile mode rts (prog : Ir.prog) : Wasm_exts.CustomModule.extended_module = GC.register_globals env; StableMem.register_globals env; Serialization.Registers.register_globals env; + Stabilization.register_globals env; (* See Note [Candid subtype checks] *) let set_serialization_globals = Serialization.register_delayed_globals env in From d22060cd38e628b3fe2f07714df200adb7c642c1 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Tue, 8 Aug 2023 16:25:23 +0200 Subject: [PATCH 002/260] Disable unused code --- src/codegen/compile.ml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index e9e0646e96b..e0a50f319e3 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -665,7 +665,7 @@ let compile_eq_const = function let compile_op64_const op i = compile_const_64 i ^^ G.i (Binary (Wasm.Values.I64 op)) -let compile_add64_const = compile_op64_const I64Op.Add +let _compile_add64_const = compile_op64_const I64Op.Add let compile_sub64_const = compile_op64_const I64Op.Sub let compile_mul64_const = compile_op64_const I64Op.Mul let _compile_divU64_const = compile_op64_const I64Op.DivU @@ -1139,9 +1139,11 @@ module Heap = struct (* At this level of abstraction, heap objects are just flat arrays of words *) +(* let load_field_unskewed (i : int32) : G.t = let offset = Int32.mul word_size i in G.i (Load {ty = I32Type; align = 2; offset; sz = None}) +*) let load_field (i : int32) : G.t = let offset = Int32.(add (mul word_size i) ptr_unskew) in @@ -1153,11 +1155,13 @@ module Heap = struct (* Although we occasionally want to treat two consecutive 32 bit fields as one 64 bit number *) - + (* Requires little-endian encoding, see also `Stream` in `types.rs` *) +(* let load_field64_unskewed (i : int32) : G.t = let offset = Int32.mul word_size i in G.i (Load {ty = I64Type; align = 2; offset; sz = None}) +*) let load_field64 (i : int32) : G.t = let offset = Int32.(add (mul word_size i) ptr_unskew) in @@ -1712,6 +1716,7 @@ module Tagged = struct (if !Flags.sanity then check_forwarding_for_store env I32Type else G.nop) ^^ Heap.store_field index +(* let load_field_unskewed env index = (if !Flags.sanity then check_forwarding env true else G.nop) ^^ Heap.load_field_unskewed index @@ -1719,6 +1724,7 @@ module Tagged = struct let load_field64_unskewed env index = (if !Flags.sanity then check_forwarding env true else G.nop) ^^ Heap.load_field64_unskewed index +*) let load_field64 env index = (if !Flags.sanity then check_forwarding env false else G.nop) ^^ @@ -3826,6 +3832,7 @@ module Blob = struct let dyn_alloc_scratch env = alloc env ^^ payload_ptr_unskewed env +(* (* TODO: rewrite using MemoryFill *) let clear env = Func.share_code1 env "blob_clear" ("x", I32Type) [] (fun env get_x -> @@ -3849,6 +3856,7 @@ module Blob = struct get_ptr ^^ compile_add_const Heap.word_size ^^ set_ptr)) +*) end (* Blob *) @@ -4916,7 +4924,7 @@ end (* Cycles *) module StableMem = struct (* start from 1 to avoid accidental reads of 0 *) - let version = Int32.of_int 1 + let _version = Int32.of_int 1 let register_globals env = (* size (in pages) *) @@ -5009,12 +5017,12 @@ module StableMem = struct IC.system_call env "stable64_write")) | _ -> assert false +(* let _read_word32 env = read env false "word32" I32Type 4l load_unskewed_ptr let write_word32 env = write env false "word32" I32Type 4l store_unskewed_ptr - (* read and clear word32 from stable mem offset on stack *) let read_and_clear_word32 env = match E.mode env with @@ -5041,6 +5049,7 @@ module StableMem = struct get_word )) | _ -> assert false +*) (* ensure_pages : ensure at least num pages allocated, growing (real) stable memory if needed *) @@ -5070,6 +5079,7 @@ module StableMem = struct get_size) | _ -> assert false +(* (* ensure stable memory includes [offset..offset+size), assumes size > 0 *) let ensure env = match E.mode env with @@ -5098,6 +5108,7 @@ module StableMem = struct G.i (Compare (Wasm.Values.I64 I64Op.LtS)) ^^ E.then_trap_with env "Out of stable memory.") | _ -> assert false +*) (* API *) @@ -7082,6 +7093,7 @@ end (* MakeSerialization *) module Serialization = MakeSerialization(BumpStream) +(* module BlobStream : Stream = struct let create env get_data_size set_token get_token header = let header_size = Int32.of_int (String.length header) in @@ -7154,6 +7166,7 @@ module BlobStream : Stream = struct BigNum.compile_store_to_stream_signed env end +*) module Stabilization = struct (* TODO: Define memory layout and predefined address for actor object *) @@ -7197,6 +7210,7 @@ module Stabilization = struct (load_actor env) end +(* (* OldStabilization (serialization to/from stable memory) of both: * stable variables; and * virtual stable memory. @@ -7507,6 +7521,7 @@ module OldStabilization = struct end | _ -> assert false end +*) module GCRoots = struct let register env static_roots = From 4c85e4002d0ec46ecfe71ccb967a3c3d99bc6991 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Tue, 8 Aug 2023 17:14:08 +0200 Subject: [PATCH 003/260] Allocate blob literals in heap --- src/codegen/compile.ml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index e0a50f319e3..4bb5d34d6b6 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -3668,14 +3668,12 @@ module Blob = struct BigNum.from_word32 env ) - let vanilla_lit env s = + let static_lit env s = Tagged.shared_static_obj env Tagged.Blob StaticBytes.[ I32 (Int32.of_int (String.length s)); Bytes s; ] - let lit env s = compile_unboxed_const (vanilla_lit env s) - let lit_ptr_len env s = compile_unboxed_const (Int32.add ptr_unskew (E.add_static env StaticBytes.[Bytes s])) ^^ compile_unboxed_const (Int32.of_int (String.length s)) @@ -3691,6 +3689,19 @@ module Blob = struct Tagged.load_forwarding_pointer env ^^ compile_add_const (unskewed_payload_offset env) + (* TODO: Implement dynamic sharing with lazy instantiation on the heap, + the static memory only denotes singleton address per upgrade mode, + the static singleton address needs to be re-initialized on upgrade *) + let lit env s = + let blob_length = Int32.of_int (String.length s) in + let (set_new_blob, get_new_blob) = new_local env "new_blob" in + compile_unboxed_const blob_length ^^ alloc env ^^ set_new_blob ^^ + get_new_blob ^^ payload_ptr_unskewed env ^^ (* target address *) + compile_unboxed_const (static_lit env s) ^^ payload_ptr_unskewed env ^^ (* source address *) + compile_unboxed_const blob_length ^^ (* copy length *) + Heap.memcpy env ^^ + get_new_blob + let as_ptr_len env = Func.share_code1 env "as_ptr_size" ("x", I32Type) [I32Type; I32Type] ( fun env get_x -> get_x ^^ payload_ptr_unskewed env ^^ @@ -7629,7 +7640,7 @@ module StackRep = struct | Const.Word32 n -> BoxedSmallWord.vanilla_lit env n | Const.Word64 n -> BoxedWord64.vanilla_lit env n | Const.Float64 f -> Float.vanilla_lit env f - | Const.Blob t -> Blob.vanilla_lit env t + | Const.Blob t -> assert false | Const.Null -> Opt.null_vanilla_lit env let rec materialize_const_t env (p, cv) : int32 = @@ -7678,6 +7689,7 @@ module StackRep = struct | Vanilla, UnboxedFloat64 -> Float.unbox env | Const (_, Const.Lit (Const.Bool b)), Vanilla -> Bool.lit b + | Const (_, Const.Lit (Const.Blob t)), Vanilla -> Blob.lit env t | Const c, Vanilla -> compile_unboxed_const (materialize_const_t env c) | Const (_, Const.Lit (Const.Word32 n)), UnboxedWord32 -> compile_unboxed_const n | Const (_, Const.Lit (Const.Word64 n)), UnboxedWord64 -> compile_const_64 n From f69c2910fb11d7ce75dee8a503d0ed822a01a3b3 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 9 Aug 2023 13:57:22 +0200 Subject: [PATCH 004/260] Introduce persistent metadata --- rts/motoko-rts/src/gc/copying.rs | 3 +- rts/motoko-rts/src/gc/generational.rs | 6 +- .../src/gc/generational/write_barrier.rs | 4 +- rts/motoko-rts/src/gc/incremental.rs | 12 ++- .../src/gc/incremental/partitioned_heap.rs | 5 ++ rts/motoko-rts/src/gc/mark_compact.rs | 3 +- rts/motoko-rts/src/lib.rs | 1 + rts/motoko-rts/src/memory/ic.rs | 6 -- rts/motoko-rts/src/memory/ic/linear_memory.rs | 8 +- rts/motoko-rts/src/persistence.rs | 90 +++++++++++++++++++ src/codegen/compile.ml | 32 +++---- src/linking/linkModule.ml | 3 + 12 files changed, 130 insertions(+), 43 deletions(-) create mode 100644 rts/motoko-rts/src/persistence.rs diff --git a/rts/motoko-rts/src/gc/copying.rs b/rts/motoko-rts/src/gc/copying.rs index f1b0b20c5f7..70041e382c2 100644 --- a/rts/motoko-rts/src/gc/copying.rs +++ b/rts/motoko-rts/src/gc/copying.rs @@ -1,6 +1,7 @@ use crate::constants::WORD_SIZE; use crate::mem_utils::{memcpy_bytes, memcpy_words}; use crate::memory::Memory; +use crate::persistence::HEAP_START; use crate::types::*; use motoko_rts_macros::ic_mem_fn; @@ -29,7 +30,7 @@ unsafe fn copying_gc(mem: &mut M) { copying_gc_internal( mem, - ic::get_aligned_heap_base(), + HEAP_START, // get_hp || linear_memory::get_hp_unskewed(), // set_hp diff --git a/rts/motoko-rts/src/gc/generational.rs b/rts/motoko-rts/src/gc/generational.rs index 1ea995573ea..09cbdb5f8e7 100644 --- a/rts/motoko-rts/src/gc/generational.rs +++ b/rts/motoko-rts/src/gc/generational.rs @@ -80,10 +80,10 @@ unsafe fn generational_gc(mem: &mut M) { #[cfg(feature = "ic")] unsafe fn get_limits() -> Limits { - use crate::memory::ic::{self, linear_memory}; - assert!(linear_memory::LAST_HP >= ic::get_aligned_heap_base()); + use crate::{memory::ic::linear_memory, persistence::HEAP_START}; + assert!(linear_memory::LAST_HP >= HEAP_START); Limits { - base: ic::get_aligned_heap_base(), + base: HEAP_START, last_free: linear_memory::LAST_HP, free: (linear_memory::get_hp_unskewed()), } diff --git a/rts/motoko-rts/src/gc/generational/write_barrier.rs b/rts/motoko-rts/src/gc/generational/write_barrier.rs index 1c8b43493fa..95b9db3820d 100644 --- a/rts/motoko-rts/src/gc/generational/write_barrier.rs +++ b/rts/motoko-rts/src/gc/generational/write_barrier.rs @@ -12,9 +12,9 @@ pub static mut LAST_HP: usize = 0; #[cfg(feature = "ic")] /// (Re-)initialize the write barrier for generational GC. pub(crate) unsafe fn init_generational_write_barrier(mem: &mut M) { - use crate::memory::ic::{self, linear_memory}; + use crate::{memory::ic::linear_memory, persistence::HEAP_START}; REMEMBERED_SET = Some(RememberedSet::new(mem)); - HEAP_BASE = ic::get_aligned_heap_base(); + HEAP_BASE = HEAP_START; LAST_HP = linear_memory::LAST_HP; } diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index b136ddea147..478fbaf5cfb 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -10,7 +10,7 @@ use core::cell::RefCell; use motoko_rts_macros::ic_mem_fn; -use crate::{memory::Memory, types::*, visitor::visit_pointer_fields}; +use crate::{memory::Memory, persistence::HEAP_START, types::*, visitor::visit_pointer_fields}; use self::{ partitioned_heap::{PartitionedHeap, PartitionedHeapIterator, UNINITIALIZED_HEAP}, @@ -37,8 +37,7 @@ pub mod time; #[ic_mem_fn(ic_only)] unsafe fn initialize_incremental_gc(mem: &mut M) { - use crate::memory::ic; - IncrementalGC::::initialize(mem, ic::get_aligned_heap_base()); + IncrementalGC::::initialize(mem, HEAP_START); } #[ic_mem_fn(ic_only)] @@ -101,7 +100,7 @@ unsafe fn record_gc_stop() { use crate::memory::ic::{self, partitioned_memory}; let heap_size = partitioned_memory::get_heap_size(); - let static_size = Bytes(ic::get_aligned_heap_base() as u32); + let static_size = Bytes(HEAP_START as u32); debug_assert!(heap_size >= static_size); let dynamic_size = heap_size - static_size; ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, dynamic_size); @@ -166,6 +165,11 @@ impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { /// (Re-)Initialize the entire incremental garbage collector. /// Called on a runtime system start with incremental GC and also during RTS testing. pub unsafe fn initialize(mem: &'a mut M, heap_base: usize) { + println!( + 100, + "INITIALIZE INCREMENTAL GC {}", + &mut STATE.borrow_mut().partitioned_heap as *mut PartitionedHeap as usize + ); let state = STATE.get_mut(); state.phase = Phase::Pause; state.partitioned_heap = PartitionedHeap::new(mem, heap_base); diff --git a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs index 2238150c771..92ac06f1b46 100644 --- a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs +++ b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs @@ -371,6 +371,11 @@ pub const UNINITIALIZED_HEAP: PartitionedHeap = PartitionedHeap { impl PartitionedHeap { pub unsafe fn new(mem: &mut M, heap_base: usize) -> PartitionedHeap { + println!( + 100, + "INITIALIZE PARTITIONED HEAP {}", + size_of::().to_bytes().as_usize() + ); let allocation_index = heap_base / PARTITION_SIZE; mem.grow_memory(((allocation_index + 1) * PARTITION_SIZE) as u64); let partitions = from_fn(|index| Partition { diff --git a/rts/motoko-rts/src/gc/mark_compact.rs b/rts/motoko-rts/src/gc/mark_compact.rs index c19ce3e8d57..09d2f61f75a 100644 --- a/rts/motoko-rts/src/gc/mark_compact.rs +++ b/rts/motoko-rts/src/gc/mark_compact.rs @@ -11,6 +11,7 @@ use mark_stack::{alloc_mark_stack, free_mark_stack, pop_mark_stack, push_mark_st use crate::constants::WORD_SIZE; use crate::mem_utils::memcpy_words; use crate::memory::Memory; +use crate::persistence::HEAP_START; use crate::types::*; use crate::visitor::{pointer_to_dynamic_heap, visit_pointer_fields}; @@ -44,7 +45,7 @@ unsafe fn compacting_gc(mem: &mut M) { compacting_gc_internal( mem, - ic::get_aligned_heap_base(), + HEAP_START, // get_hp || linear_memory::get_hp_unskewed(), // set_hp diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs index 51b68028d19..58dabc776c2 100644 --- a/rts/motoko-rts/src/lib.rs +++ b/rts/motoko-rts/src/lib.rs @@ -31,6 +31,7 @@ mod idl; pub mod leb128; mod mem_utils; pub mod memory; +pub mod persistence; pub mod principal_id; mod static_checks; pub mod stream; diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index 68b4effccaa..50e710ce96e 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -14,15 +14,9 @@ use motoko_rts_macros::*; // Provided by generated code extern "C" { - fn get_heap_base() -> usize; pub(crate) fn get_static_roots() -> Value; } -pub(crate) unsafe fn get_aligned_heap_base() -> usize { - // align to 32 bytes - ((get_heap_base() + 31) / 32) * 32 -} - /// Maximum live data retained in a GC. pub(crate) static mut MAX_LIVE: Bytes = Bytes(0); diff --git a/rts/motoko-rts/src/memory/ic/linear_memory.rs b/rts/motoko-rts/src/memory/ic/linear_memory.rs index 14c94de5adc..426aab494c3 100644 --- a/rts/motoko-rts/src/memory/ic/linear_memory.rs +++ b/rts/motoko-rts/src/memory/ic/linear_memory.rs @@ -1,7 +1,7 @@ use core::arch::wasm32; -use super::{get_aligned_heap_base, IcMemory, Memory}; -use crate::types::*; +use super::{IcMemory, Memory}; +use crate::{persistence::HEAP_START, types::*}; /// Amount of garbage collected so far. pub(crate) static mut RECLAIMED: Bytes = Bytes(0); @@ -23,7 +23,7 @@ pub(crate) unsafe fn get_hp_unskewed() -> usize { pub(crate) static mut LAST_HP: usize = 0; pub(crate) unsafe fn initialize() { - LAST_HP = get_aligned_heap_base(); + LAST_HP = HEAP_START; set_hp_unskewed(LAST_HP); } @@ -39,7 +39,7 @@ pub unsafe extern "C" fn get_total_allocations() -> Bytes { #[no_mangle] pub unsafe extern "C" fn get_heap_size() -> Bytes { - Bytes((get_hp_unskewed() - get_aligned_heap_base()) as u32) + Bytes((get_hp_unskewed() - HEAP_START) as u32) } impl Memory for IcMemory { diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs new file mode 100644 index 00000000000..174bc2aeec7 --- /dev/null +++ b/rts/motoko-rts/src/persistence.rs @@ -0,0 +1,90 @@ +//! Orthogonal persistence support +//! +//! Persistent metadata table, located at 4MB, in the static partition space. + +use core::mem::size_of; + +use motoko_rts_macros::ic_mem_fn; + +use crate::{barriers::write_with_barrier, memory::Memory, types::Value}; + +const FINGERPRINT: [char; 32] = [ + 'M', 'O', 'T', 'O', 'K', 'O', ' ', 'O', 'R', 'T', 'H', 'O', 'G', 'O', 'N', 'A', 'L', ' ', 'P', + 'E', 'R', 'S', 'I', 'S', 'T', 'E', 'N', 'C', 'E', ' ', '3', '2', +]; +const VERSION: usize = 1; +const NO_ACTOR: Value = Value::from_scalar(0); + +// Use a long-term representation by relying on C layout. +#[repr(C)] +struct PersistentMetadata { + fingerprint: [char; 32], + version: usize, + actor: Value, // Must be added to the root set, use forwarding +} + +const METATDATA_ADDRESS: usize = 4 * 1024 * 1024; +const METADATA_RESERVE: usize = 128 * 1024; + +// TODO: Include partition table in reserved space. +#[cfg(feature = "ic")] +pub const HEAP_START: usize = METATDATA_ADDRESS + METADATA_RESERVE; + +const _: () = assert!(size_of::() <= METADATA_RESERVE); + +impl PersistentMetadata { + fn get() -> *mut Self { + METATDATA_ADDRESS as *mut Self + } + + unsafe fn is_initialized(self: *mut Self) -> bool { + // Wasm memory is zero-initialized according to the Wasm specification. + let initialized = (*self).version != 0; + assert!(initialized || (*self).fingerprint == ['\0'; 32] && (*self).actor == NO_ACTOR); + initialized + } + + unsafe fn check_version(self: *const Self) { + if (*self).version != VERSION { + panic!( + "Incompatible persistent memory version: {} instead of {}.", + (*self).version, + VERSION + ); + } + } + + unsafe fn initialize(self: *mut Self) { + debug_assert!(!self.is_initialized()); + (*self).fingerprint = FINGERPRINT; + (*self).version = VERSION; + (*self).actor = NO_ACTOR; + } +} + +#[ic_mem_fn] +pub unsafe fn initialize_memory(mem: &mut M) { + mem.grow_memory(HEAP_START as u64); + let metadata = PersistentMetadata::get(); + if metadata.is_initialized() { + metadata.check_version(); + } else { + metadata.initialize(); + } + println!(100, "MEMORY INITIALIZED {HEAP_START:#x}"); +} + +/// Returns scalar 0 if no actor is stored. +#[no_mangle] +pub unsafe extern "C" fn load_actor() -> Value { + let metadata = PersistentMetadata::get(); + (*metadata).actor.forward_if_possible() +} + +#[ic_mem_fn] +pub unsafe fn save_actor(mem: &mut M, actor: Value) { + assert!(actor != NO_ACTOR); + let metadata = PersistentMetadata::get(); + let location = &mut (*metadata).actor as *mut Value; + write_with_barrier(mem, location, actor); +} diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 4bb5d34d6b6..9a0d973cd83 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -924,6 +924,9 @@ module RTS = struct (* The connection to the C and Rust parts of the RTS *) let system_imports env = + E.add_func_import env "rts" "initialize_memory" [] []; + E.add_func_import env "rts" "load_actor" [] [I32Type]; + E.add_func_import env "rts" "save_actor" [I32Type] []; E.add_func_import env "rts" "memcpy" [I32Type; I32Type; I32Type] [I32Type]; (* standard libc memcpy *) E.add_func_import env "rts" "memcmp" [I32Type; I32Type; I32Type] [I32Type]; E.add_func_import env "rts" "version" [] [I32Type]; @@ -7180,28 +7183,10 @@ end *) module Stabilization = struct - (* TODO: Define memory layout and predefined address for actor object *) - let register_globals env = - (* Only reserve static memory without appending a data segment. - This is necessary because data segments are overwritten on upgrade - and we need to retain the old actor object address at this location. - The Wasm memory is guaranteed to be zero-initialized on canister installation. - *) - let static_address = E.reserve_static_memory env Heap.word_size in - E.add_global32 env "__actor_object" Mutable static_address; - () - - let actor_address env = - G.i (GlobalGet (nr (E.get_global env "__actor_object"))) - - let load_actor env = - actor_address env ^^ load_unskewed_ptr + let load_actor env = E.call_import env "rts" "load_actor" + + let save_actor env = E.call_import env "rts" "save_actor" - let save_actor env = - let (set_value, get_value) = new_local env "value" in - set_value ^^ - actor_address env ^^ get_value ^^ store_unskewed_ptr - let empty_actor env t = let (_, fs) = Type.as_obj t in let fs' = List.map @@ -11093,6 +11078,7 @@ and compile_init_func mod_env ((cu, flavor) : Ir.prog) = | ProgU ds -> Func.define_built_in mod_env "init" [] [] (fun env -> let _ae, codeW = compile_decs env VarEnv.empty_ae ds Freevars.S.empty in + E.call_import mod_env "rts" "initialize_memory" ^^ codeW G.nop ) | ActorU (as_opt, ds, fs, up, _t) -> @@ -11186,6 +11172,9 @@ and main_actor as_opt mod_env ds fs up = env.E.service := metadata "candid:service" up.meta.candid.service; env.E.args := metadata "candid:args" up.meta.candid.args; + (* Initialize persistent memory *) + E.call_import mod_env "rts" "initialize_memory" ^^ + (* Deserialize any arguments *) begin match as_opt with | None @@ -11321,7 +11310,6 @@ let compile mode rts (prog : Ir.prog) : Wasm_exts.CustomModule.extended_module = GC.register_globals env; StableMem.register_globals env; Serialization.Registers.register_globals env; - Stabilization.register_globals env; (* See Note [Candid subtype checks] *) let set_serialization_globals = Serialization.register_delayed_globals env in diff --git a/src/linking/linkModule.ml b/src/linking/linkModule.ml index dc37b5e0227..0cf3f7ddfb5 100644 --- a/src/linking/linkModule.ml +++ b/src/linking/linkModule.ml @@ -766,6 +766,9 @@ let link (em1 : extended_module) libname (em2 : extended_module) = let lib_heap_start = align dylink.memory_alignment old_heap_start in let new_heap_start = align 4l (Int32.add lib_heap_start dylink.memory_size) in + (* Data segments must fit below 4MB according to the persistent heap layout. *) + assert ((Int32.to_int new_heap_start) <= 4 * 1024 * 1024); + let old_table_size = read_table_size em1.module_ in let lib_table_start = align dylink.table_alignment old_table_size in From e3cf57290057ec06c3f33aa952cb50ae12532f64 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 9 Aug 2023 14:30:59 +0200 Subject: [PATCH 005/260] Place GC state in persistent memory --- rts/motoko-rts/src/gc/copying.rs | 5 +- rts/motoko-rts/src/gc/generational.rs | 4 +- .../src/gc/generational/write_barrier.rs | 2 +- rts/motoko-rts/src/gc/incremental.rs | 49 ++++++++----------- rts/motoko-rts/src/gc/incremental/barriers.rs | 8 +-- .../src/gc/incremental/mark_bitmap.rs | 2 +- .../src/gc/incremental/partitioned_heap.rs | 34 +------------ rts/motoko-rts/src/gc/mark_compact.rs | 5 +- rts/motoko-rts/src/lib.rs | 1 + rts/motoko-rts/src/memory/ic.rs | 4 ++ rts/motoko-rts/src/memory/ic/linear_memory.rs | 4 +- rts/motoko-rts/src/persistence.rs | 12 +++-- src/codegen/compile.ml | 5 -- 13 files changed, 52 insertions(+), 83 deletions(-) diff --git a/rts/motoko-rts/src/gc/copying.rs b/rts/motoko-rts/src/gc/copying.rs index 70041e382c2..0e306c3c3ed 100644 --- a/rts/motoko-rts/src/gc/copying.rs +++ b/rts/motoko-rts/src/gc/copying.rs @@ -1,14 +1,15 @@ use crate::constants::WORD_SIZE; use crate::mem_utils::{memcpy_bytes, memcpy_words}; use crate::memory::Memory; -use crate::persistence::HEAP_START; use crate::types::*; use motoko_rts_macros::ic_mem_fn; #[no_mangle] #[cfg(feature = "ic")] +#[allow(unreachable_code)] pub unsafe extern "C" fn initialize_copying_gc() { + panic!("Copying GC is not supported with the persistent heap"); crate::memory::ic::linear_memory::initialize(); } @@ -26,7 +27,7 @@ unsafe fn schedule_copying_gc(mem: &mut M) { #[ic_mem_fn(ic_only)] unsafe fn copying_gc(mem: &mut M) { - use crate::memory::ic::{self, linear_memory}; + use crate::memory::ic::{self, linear_memory, HEAP_START}; copying_gc_internal( mem, diff --git a/rts/motoko-rts/src/gc/generational.rs b/rts/motoko-rts/src/gc/generational.rs index 09cbdb5f8e7..ca740fde297 100644 --- a/rts/motoko-rts/src/gc/generational.rs +++ b/rts/motoko-rts/src/gc/generational.rs @@ -28,7 +28,9 @@ use self::mark_stack::{free_mark_stack, pop_mark_stack}; use self::write_barrier::REMEMBERED_SET; #[ic_mem_fn(ic_only)] +#[allow(unreachable_code, unused_variables)] unsafe fn initialize_generational_gc(mem: &mut M) { + panic!("Generational GC is not supported with the persistent heap"); crate::memory::ic::linear_memory::initialize(); write_barrier::init_generational_write_barrier(mem); } @@ -80,7 +82,7 @@ unsafe fn generational_gc(mem: &mut M) { #[cfg(feature = "ic")] unsafe fn get_limits() -> Limits { - use crate::{memory::ic::linear_memory, persistence::HEAP_START}; + use crate::memory::ic::{linear_memory, HEAP_START}; assert!(linear_memory::LAST_HP >= HEAP_START); Limits { base: HEAP_START, diff --git a/rts/motoko-rts/src/gc/generational/write_barrier.rs b/rts/motoko-rts/src/gc/generational/write_barrier.rs index 95b9db3820d..53fb3a87723 100644 --- a/rts/motoko-rts/src/gc/generational/write_barrier.rs +++ b/rts/motoko-rts/src/gc/generational/write_barrier.rs @@ -12,7 +12,7 @@ pub static mut LAST_HP: usize = 0; #[cfg(feature = "ic")] /// (Re-)initialize the write barrier for generational GC. pub(crate) unsafe fn init_generational_write_barrier(mem: &mut M) { - use crate::{memory::ic::linear_memory, persistence::HEAP_START}; + use crate::memory::ic::{linear_memory, HEAP_START}; REMEMBERED_SET = Some(RememberedSet::new(mem)); HEAP_BASE = HEAP_START; LAST_HP = linear_memory::LAST_HP; diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 478fbaf5cfb..6435c9d35fa 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -6,14 +6,18 @@ //! - Focus on reclaiming high-garbage partitions. //! - Compacting heap space with partition evacuations. //! - Incremental copying enabled by forwarding pointers. -use core::cell::RefCell; use motoko_rts_macros::ic_mem_fn; -use crate::{memory::Memory, persistence::HEAP_START, types::*, visitor::visit_pointer_fields}; +use crate::{ + memory::Memory, + persistence::{get_incremenmtal_gc_state, HEAP_START, initialize_memory}, + types::*, + visitor::visit_pointer_fields, +}; use self::{ - partitioned_heap::{PartitionedHeap, PartitionedHeapIterator, UNINITIALIZED_HEAP}, + partitioned_heap::{PartitionedHeap, PartitionedHeapIterator}, phases::{ evacuation_increment::EvacuationIncrement, mark_increment::{MarkIncrement, MarkState}, @@ -37,12 +41,14 @@ pub mod time; #[ic_mem_fn(ic_only)] unsafe fn initialize_incremental_gc(mem: &mut M) { - IncrementalGC::::initialize(mem, HEAP_START); + println!(100, "INITIALIZE INCREMENTAL GC"); + initialize_memory(mem); + IncrementalGC::::initialize(mem, get_incremenmtal_gc_state(), HEAP_START); } #[ic_mem_fn(ic_only)] unsafe fn schedule_incremental_gc(mem: &mut M) { - let state = STATE.borrow(); + let state = get_incremenmtal_gc_state(); assert!(state.phase != Phase::Stop); let running = state.phase != Phase::Pause; if running || should_start() { @@ -53,7 +59,7 @@ unsafe fn schedule_incremental_gc(mem: &mut M) { #[ic_mem_fn(ic_only)] unsafe fn incremental_gc(mem: &mut M) { use self::roots::root_set; - let state = STATE.get_mut(); + let state = get_incremenmtal_gc_state(); if state.phase == Phase::Pause { record_gc_start::(); } @@ -136,6 +142,7 @@ enum Phase { Stop, // GC stopped on canister upgrade. } +/// GC state retained over multiple GC increments. pub struct State { phase: Phase, partitioned_heap: PartitionedHeap, @@ -144,15 +151,6 @@ pub struct State { iterator_state: Option, } -/// GC state retained over multiple GC increments. -static mut STATE: RefCell = RefCell::new(State { - phase: Phase::Pause, - partitioned_heap: UNINITIALIZED_HEAP, - allocation_count: 0, - mark_state: None, - iterator_state: None, -}); - /// Incremental GC. /// Each GC call has its new GC instance that shares the common GC state `STATE`. pub struct IncrementalGC<'a, M: Memory> { @@ -164,13 +162,7 @@ pub struct IncrementalGC<'a, M: Memory> { impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { /// (Re-)Initialize the entire incremental garbage collector. /// Called on a runtime system start with incremental GC and also during RTS testing. - pub unsafe fn initialize(mem: &'a mut M, heap_base: usize) { - println!( - 100, - "INITIALIZE INCREMENTAL GC {}", - &mut STATE.borrow_mut().partitioned_heap as *mut PartitionedHeap as usize - ); - let state = STATE.get_mut(); + pub unsafe fn initialize(mem: &'a mut M, state: &mut State, heap_base: usize) { state.phase = Phase::Pause; state.partitioned_heap = PartitionedHeap::new(mem, heap_base); state.allocation_count = 0; @@ -196,6 +188,7 @@ impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { /// * The mark phase can only be started on an empty call stack. /// * The update phase can only be completed on an empty call stack. pub unsafe fn empty_call_stack_increment(&mut self, roots: Roots) { + println!(100, "GC INCREMENT"); assert!(self.state.phase != Phase::Stop); if self.pausing() { self.start_marking(roots); @@ -399,14 +392,12 @@ unsafe fn count_allocation(state: &mut State) { /// the compiler must not schedule the GC during stabilization anyway. #[no_mangle] pub unsafe extern "C" fn stop_gc_on_upgrade() { - STATE.get_mut().phase = Phase::Stop; -} - -pub unsafe fn incremental_gc_state() -> &'static mut State { - STATE.get_mut() + get_incremenmtal_gc_state().phase = Phase::Stop; } pub unsafe fn get_partitioned_heap() -> &'static mut PartitionedHeap { - debug_assert!(STATE.get_mut().partitioned_heap.is_initialized()); - &mut STATE.get_mut().partitioned_heap + debug_assert!(get_incremenmtal_gc_state() + .partitioned_heap + .is_initialized()); + &mut get_incremenmtal_gc_state().partitioned_heap } diff --git a/rts/motoko-rts/src/gc/incremental/barriers.rs b/rts/motoko-rts/src/gc/incremental/barriers.rs index dd67c08d01b..ca8f351bbce 100644 --- a/rts/motoko-rts/src/gc/incremental/barriers.rs +++ b/rts/motoko-rts/src/gc/incremental/barriers.rs @@ -5,8 +5,8 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ - gc::incremental::incremental_gc_state, memory::Memory, + persistence::get_incremenmtal_gc_state, types::{is_skewed, Value}, }; @@ -14,7 +14,7 @@ use super::{count_allocation, post_allocation_barrier, pre_write_barrier, Phase} #[no_mangle] pub unsafe extern "C" fn running_gc() -> bool { - incremental_gc_state().phase != Phase::Pause + get_incremenmtal_gc_state().phase != Phase::Pause } /// Write a potential pointer value with a pre-update barrier and resolving pointer forwarding. @@ -30,7 +30,7 @@ pub unsafe fn write_with_barrier(mem: &mut M, location: *mut Value, v debug_assert!(!is_skewed(location as u32)); debug_assert_ne!(location, core::ptr::null_mut()); - let state = incremental_gc_state(); + let state = get_incremenmtal_gc_state(); pre_write_barrier(mem, state, *location); *location = value.forward_if_possible(); } @@ -47,7 +47,7 @@ pub unsafe fn write_with_barrier(mem: &mut M, location: *mut Value, v /// * Keep track of concurrent allocations to adjust the GC increment time limit. #[no_mangle] pub unsafe extern "C" fn allocation_barrier(new_object: Value) -> Value { - let state = incremental_gc_state(); + let state = get_incremenmtal_gc_state(); if state.phase != Phase::Pause { post_allocation_barrier(state, new_object); count_allocation(state); diff --git a/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs b/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs index e614d5b2b4a..8afc38e73d9 100644 --- a/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs +++ b/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs @@ -35,7 +35,7 @@ pub struct MarkBitmap { pointer: *mut u8, } -pub const DEFAULT_MARK_BITMAP: MarkBitmap = MarkBitmap { +const DEFAULT_MARK_BITMAP: MarkBitmap = MarkBitmap { pointer: null_mut(), }; diff --git a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs index 92ac06f1b46..4984dd2dc7d 100644 --- a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs +++ b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs @@ -46,7 +46,7 @@ use crate::{ }; use super::{ - mark_bitmap::{BitmapIterator, MarkBitmap, BITMAP_SIZE, DEFAULT_MARK_BITMAP}, + mark_bitmap::{BitmapIterator, MarkBitmap, BITMAP_SIZE}, sort::sort, time::BoundedTime, }; @@ -86,20 +86,6 @@ pub struct Partition { update: bool, // Specifies whether the pointers in the partition have to be updated. } -/// Optimization: Avoiding `Option` or `Lazy`. -const UNINITIALIZED_PARTITION: Partition = Partition { - index: usize::MAX, - free: false, - large_content: false, - marked_size: 0, - static_size: 0, - dynamic_size: 0, - bitmap: DEFAULT_MARK_BITMAP, - temporary: false, - evacuate: false, - update: false, -}; - impl Partition { pub fn get_index(&self) -> usize { self.index @@ -356,26 +342,8 @@ pub struct PartitionedHeap { precomputed_heap_size: usize, // Occupied heap size, excluding the dynamic heap in the allocation partition. } -/// Optimization: Avoiding `Option` or `LazyCell`. -pub const UNINITIALIZED_HEAP: PartitionedHeap = PartitionedHeap { - partitions: [UNINITIALIZED_PARTITION; MAX_PARTITIONS], - heap_base: 0, - allocation_index: 0, - free_partitions: 0, - evacuating: false, - reclaimed: 0, - bitmap_allocation_pointer: 0, - gc_running: false, - precomputed_heap_size: 0, -}; - impl PartitionedHeap { pub unsafe fn new(mem: &mut M, heap_base: usize) -> PartitionedHeap { - println!( - 100, - "INITIALIZE PARTITIONED HEAP {}", - size_of::().to_bytes().as_usize() - ); let allocation_index = heap_base / PARTITION_SIZE; mem.grow_memory(((allocation_index + 1) * PARTITION_SIZE) as u64); let partitions = from_fn(|index| Partition { diff --git a/rts/motoko-rts/src/gc/mark_compact.rs b/rts/motoko-rts/src/gc/mark_compact.rs index 09d2f61f75a..685e80c63cc 100644 --- a/rts/motoko-rts/src/gc/mark_compact.rs +++ b/rts/motoko-rts/src/gc/mark_compact.rs @@ -11,7 +11,6 @@ use mark_stack::{alloc_mark_stack, free_mark_stack, pop_mark_stack, push_mark_st use crate::constants::WORD_SIZE; use crate::mem_utils::memcpy_words; use crate::memory::Memory; -use crate::persistence::HEAP_START; use crate::types::*; use crate::visitor::{pointer_to_dynamic_heap, visit_pointer_fields}; @@ -19,7 +18,9 @@ use motoko_rts_macros::ic_mem_fn; #[no_mangle] #[cfg(feature = "ic")] +#[allow(unreachable_code)] pub unsafe extern "C" fn initialize_compacting_gc() { + panic!("Compacting GC is not supported with the persistent heap"); crate::memory::ic::linear_memory::initialize(); } @@ -41,7 +42,7 @@ unsafe fn schedule_compacting_gc(mem: &mut M) { #[ic_mem_fn(ic_only)] unsafe fn compacting_gc(mem: &mut M) { - use crate::memory::ic::{self, linear_memory}; + use crate::memory::ic::{self, linear_memory, HEAP_START}; compacting_gc_internal( mem, diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs index 58dabc776c2..2a2adf34d79 100644 --- a/rts/motoko-rts/src/lib.rs +++ b/rts/motoko-rts/src/lib.rs @@ -31,6 +31,7 @@ mod idl; pub mod leb128; mod mem_utils; pub mod memory; +#[incremental_gc] pub mod persistence; pub mod principal_id; mod static_checks; diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index 50e710ce96e..94915932553 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -12,6 +12,10 @@ use crate::types::{Bytes, Value}; use core::arch::wasm32; use motoko_rts_macros::*; +// TODO: Remove once the classical GCs have been removed. +#[non_incremental_gc] +pub const HEAP_START: usize = 4 * 1024 * 1024 + 128 * 1024; + // Provided by generated code extern "C" { pub(crate) fn get_static_roots() -> Value; diff --git a/rts/motoko-rts/src/memory/ic/linear_memory.rs b/rts/motoko-rts/src/memory/ic/linear_memory.rs index 426aab494c3..af2ee92bc4b 100644 --- a/rts/motoko-rts/src/memory/ic/linear_memory.rs +++ b/rts/motoko-rts/src/memory/ic/linear_memory.rs @@ -1,7 +1,7 @@ use core::arch::wasm32; -use super::{IcMemory, Memory}; -use crate::{persistence::HEAP_START, types::*}; +use super::{IcMemory, Memory, HEAP_START}; +use crate::types::*; /// Amount of garbage collected so far. pub(crate) static mut RECLAIMED: Bytes = Bytes(0); diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index 174bc2aeec7..3ba0a40a5f9 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -6,7 +6,7 @@ use core::mem::size_of; use motoko_rts_macros::ic_mem_fn; -use crate::{barriers::write_with_barrier, memory::Memory, types::Value}; +use crate::{barriers::write_with_barrier, gc::incremental::State, memory::Memory, types::Value}; const FINGERPRINT: [char; 32] = [ 'M', 'O', 'T', 'O', 'K', 'O', ' ', 'O', 'R', 'T', 'H', 'O', 'G', 'O', 'N', 'A', 'L', ' ', 'P', @@ -21,6 +21,7 @@ struct PersistentMetadata { fingerprint: [char; 32], version: usize, actor: Value, // Must be added to the root set, use forwarding + incremental_gc_state: State, } const METATDATA_ADDRESS: usize = 4 * 1024 * 1024; @@ -62,16 +63,16 @@ impl PersistentMetadata { } } -#[ic_mem_fn] pub unsafe fn initialize_memory(mem: &mut M) { mem.grow_memory(HEAP_START as u64); let metadata = PersistentMetadata::get(); if metadata.is_initialized() { + println!(100, "MEMORY REUSED"); metadata.check_version(); } else { + println!(100, "MEMORY INITIALIZED"); metadata.initialize(); } - println!(100, "MEMORY INITIALIZED {HEAP_START:#x}"); } /// Returns scalar 0 if no actor is stored. @@ -88,3 +89,8 @@ pub unsafe fn save_actor(mem: &mut M, actor: Value) { let location = &mut (*metadata).actor as *mut Value; write_with_barrier(mem, location, actor); } + +pub(crate) unsafe fn get_incremenmtal_gc_state() -> &'static mut State { + let metadata = PersistentMetadata::get(); + &mut (*metadata).incremental_gc_state +} diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 9a0d973cd83..ad4253ce7cb 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -924,7 +924,6 @@ module RTS = struct (* The connection to the C and Rust parts of the RTS *) let system_imports env = - E.add_func_import env "rts" "initialize_memory" [] []; E.add_func_import env "rts" "load_actor" [] [I32Type]; E.add_func_import env "rts" "save_actor" [I32Type] []; E.add_func_import env "rts" "memcpy" [I32Type; I32Type; I32Type] [I32Type]; (* standard libc memcpy *) @@ -11078,7 +11077,6 @@ and compile_init_func mod_env ((cu, flavor) : Ir.prog) = | ProgU ds -> Func.define_built_in mod_env "init" [] [] (fun env -> let _ae, codeW = compile_decs env VarEnv.empty_ae ds Freevars.S.empty in - E.call_import mod_env "rts" "initialize_memory" ^^ codeW G.nop ) | ActorU (as_opt, ds, fs, up, _t) -> @@ -11172,9 +11170,6 @@ and main_actor as_opt mod_env ds fs up = env.E.service := metadata "candid:service" up.meta.candid.service; env.E.args := metadata "candid:args" up.meta.candid.args; - (* Initialize persistent memory *) - E.call_import mod_env "rts" "initialize_memory" ^^ - (* Deserialize any arguments *) begin match as_opt with | None From 2103e65b8734849a364ead632fbf2a908320a623 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Thu, 10 Aug 2023 15:30:54 +0200 Subject: [PATCH 006/260] Place static roots in the dynamic heap --- rts/motoko-rts/src/gc/copying.rs | 2 +- rts/motoko-rts/src/gc/generational.rs | 2 +- rts/motoko-rts/src/gc/incremental.rs | 2 +- rts/motoko-rts/src/gc/incremental/roots.rs | 7 +- rts/motoko-rts/src/gc/mark_compact.rs | 2 +- rts/motoko-rts/src/memory/ic.rs | 7 +- rts/motoko-rts/src/memory/ic/linear_memory.rs | 4 + rts/motoko-rts/src/persistence.rs | 36 +++-- src/codegen/compile.ml | 123 +++++++++--------- 9 files changed, 102 insertions(+), 83 deletions(-) diff --git a/rts/motoko-rts/src/gc/copying.rs b/rts/motoko-rts/src/gc/copying.rs index 0e306c3c3ed..8a2376a54a9 100644 --- a/rts/motoko-rts/src/gc/copying.rs +++ b/rts/motoko-rts/src/gc/copying.rs @@ -36,7 +36,7 @@ unsafe fn copying_gc(mem: &mut M) { || linear_memory::get_hp_unskewed(), // set_hp |hp| linear_memory::set_hp_unskewed(hp), - ic::get_static_roots(), + ic::linear_memory::get_static_roots(), crate::continuation_table::continuation_table_loc(), // note_live_size |live_size| ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size), diff --git a/rts/motoko-rts/src/gc/generational.rs b/rts/motoko-rts/src/gc/generational.rs index ca740fde297..b9083ac2872 100644 --- a/rts/motoko-rts/src/gc/generational.rs +++ b/rts/motoko-rts/src/gc/generational.rs @@ -49,7 +49,7 @@ unsafe fn generational_gc(mem: &mut M) { let old_limits = get_limits(); let roots = Roots { - static_roots: ic::get_static_roots(), + static_roots: ic::linear_memory::get_static_roots(), continuation_table_ptr_loc: crate::continuation_table::continuation_table_loc(), }; let heap = Heap { diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 6435c9d35fa..2a016d4aec3 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -11,7 +11,7 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ memory::Memory, - persistence::{get_incremenmtal_gc_state, HEAP_START, initialize_memory}, + persistence::{get_incremenmtal_gc_state, initialize_memory, HEAP_START}, types::*, visitor::visit_pointer_fields, }; diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs index b33be52ef3c..d1477b98bcc 100644 --- a/rts/motoko-rts/src/gc/incremental/roots.rs +++ b/rts/motoko-rts/src/gc/incremental/roots.rs @@ -10,10 +10,11 @@ pub struct Roots { #[cfg(feature = "ic")] pub unsafe fn root_set() -> Roots { - use crate::memory::ic; + use crate::continuation_table::continuation_table_loc; + use crate::persistence::get_static_root; Roots { - static_roots: ic::get_static_roots(), - continuation_table_location: crate::continuation_table::continuation_table_loc(), + static_roots: get_static_root(), + continuation_table_location: continuation_table_loc(), } } diff --git a/rts/motoko-rts/src/gc/mark_compact.rs b/rts/motoko-rts/src/gc/mark_compact.rs index 685e80c63cc..c6716cfc2cf 100644 --- a/rts/motoko-rts/src/gc/mark_compact.rs +++ b/rts/motoko-rts/src/gc/mark_compact.rs @@ -51,7 +51,7 @@ unsafe fn compacting_gc(mem: &mut M) { || linear_memory::get_hp_unskewed(), // set_hp |hp| linear_memory::set_hp_unskewed(hp), - ic::get_static_roots(), + ic::linear_memory::get_static_roots(), crate::continuation_table::continuation_table_loc(), // note_live_size |live_size| ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size), diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index 94915932553..1fe07f0ef7a 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -8,7 +8,7 @@ pub mod partitioned_memory; use super::Memory; use crate::constants::WASM_PAGE_SIZE; use crate::rts_trap_with; -use crate::types::{Bytes, Value}; +use crate::types::Bytes; use core::arch::wasm32; use motoko_rts_macros::*; @@ -16,11 +16,6 @@ use motoko_rts_macros::*; #[non_incremental_gc] pub const HEAP_START: usize = 4 * 1024 * 1024 + 128 * 1024; -// Provided by generated code -extern "C" { - pub(crate) fn get_static_roots() -> Value; -} - /// Maximum live data retained in a GC. pub(crate) static mut MAX_LIVE: Bytes = Bytes(0); diff --git a/rts/motoko-rts/src/memory/ic/linear_memory.rs b/rts/motoko-rts/src/memory/ic/linear_memory.rs index af2ee92bc4b..18262afae7f 100644 --- a/rts/motoko-rts/src/memory/ic/linear_memory.rs +++ b/rts/motoko-rts/src/memory/ic/linear_memory.rs @@ -12,6 +12,10 @@ extern "C" { fn getHP() -> u32; } +pub(crate) fn get_static_roots() -> Value { + unimplemented!() +} + pub(crate) unsafe fn set_hp_unskewed(new_hp: usize) { setHP(skew(new_hp)) } diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index 3ba0a40a5f9..e502bc4eddb 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -13,15 +13,16 @@ const FINGERPRINT: [char; 32] = [ 'E', 'R', 'S', 'I', 'S', 'T', 'E', 'N', 'C', 'E', ' ', '3', '2', ]; const VERSION: usize = 1; -const NO_ACTOR: Value = Value::from_scalar(0); +const NO_OBJECT: Value = Value::from_scalar(0); // Use a long-term representation by relying on C layout. #[repr(C)] struct PersistentMetadata { fingerprint: [char; 32], version: usize, - actor: Value, // Must be added to the root set, use forwarding + stable_actor: Value, // Must be added to the root set, use forwarding incremental_gc_state: State, + static_root: Value, } const METATDATA_ADDRESS: usize = 4 * 1024 * 1024; @@ -41,7 +42,9 @@ impl PersistentMetadata { unsafe fn is_initialized(self: *mut Self) -> bool { // Wasm memory is zero-initialized according to the Wasm specification. let initialized = (*self).version != 0; - assert!(initialized || (*self).fingerprint == ['\0'; 32] && (*self).actor == NO_ACTOR); + assert!( + initialized || (*self).fingerprint == ['\0'; 32] && (*self).stable_actor == NO_OBJECT + ); initialized } @@ -59,7 +62,8 @@ impl PersistentMetadata { debug_assert!(!self.is_initialized()); (*self).fingerprint = FINGERPRINT; (*self).version = VERSION; - (*self).actor = NO_ACTOR; + (*self).stable_actor = NO_OBJECT; + (*self).static_root = NO_OBJECT; } } @@ -77,16 +81,16 @@ pub unsafe fn initialize_memory(mem: &mut M) { /// Returns scalar 0 if no actor is stored. #[no_mangle] -pub unsafe extern "C" fn load_actor() -> Value { +pub unsafe extern "C" fn load_stable_actor() -> Value { let metadata = PersistentMetadata::get(); - (*metadata).actor.forward_if_possible() + (*metadata).stable_actor.forward_if_possible() } #[ic_mem_fn] -pub unsafe fn save_actor(mem: &mut M, actor: Value) { - assert!(actor != NO_ACTOR); +pub unsafe fn save_stable_actor(mem: &mut M, actor: Value) { + assert!(actor != NO_OBJECT); let metadata = PersistentMetadata::get(); - let location = &mut (*metadata).actor as *mut Value; + let location = &mut (*metadata).stable_actor as *mut Value; write_with_barrier(mem, location, actor); } @@ -94,3 +98,17 @@ pub(crate) unsafe fn get_incremenmtal_gc_state() -> &'static mut State { let metadata = PersistentMetadata::get(); &mut (*metadata).incremental_gc_state } + +#[ic_mem_fn] +pub unsafe fn set_static_root(mem: &mut M, value: Value) { + let metadata = PersistentMetadata::get(); + let location = &mut (*metadata).static_root as *mut Value; + write_with_barrier(mem, location, value); +} + +#[no_mangle] +pub unsafe extern "C" fn get_static_root() -> Value { + let metadata = PersistentMetadata::get(); + assert!((*metadata).static_root != NO_OBJECT); + (*metadata).static_root +} diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index ad4253ce7cb..b50a215c7a7 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -301,8 +301,9 @@ module E = struct static_memory : (int32 * string) list ref; (* Content of static memory *) static_memory_frozen : bool ref; (* Sanity check: Nothing should bump end_of_static_memory once it has been read *) - static_roots : int32 list ref; - (* GC roots in static memory. (Everything that may be mutable.) *) + static_variables : int32 ref; + (* Number of static variables (MutBox), accessed by index via the runtime system, + and belonging to the GC root set. *) (* Types accumulated in global typtbl (for candid subtype checks) See Note [Candid subtype checks] @@ -348,7 +349,7 @@ module E = struct end_of_static_memory = ref dyn_mem; static_memory = ref []; static_memory_frozen = ref false; - static_roots = ref []; + static_variables = ref 0l; typtbl_typs = ref []; (* Metadata *) args = ref None; @@ -592,11 +593,13 @@ module E = struct env.static_memory_frozen := true; !(env.end_of_static_memory) - let add_static_root (env : t) ptr = - env.static_roots := ptr :: !(env.static_roots) + let add_static_variable (env : t) : int32 = + let index = !(env.static_variables) in + env.static_variables := Int32.add index 1l; + index - let get_static_roots (env : t) = - !(env.static_roots) + let count_static_variables (env : t) = + !(env.static_variables) let get_static_memory env = !(env.static_memory) @@ -924,8 +927,10 @@ module RTS = struct (* The connection to the C and Rust parts of the RTS *) let system_imports env = - E.add_func_import env "rts" "load_actor" [] [I32Type]; - E.add_func_import env "rts" "save_actor" [I32Type] []; + E.add_func_import env "rts" "load_stable_actor" [] [I32Type]; + E.add_func_import env "rts" "save_stable_actor" [I32Type] []; + E.add_func_import env "rts" "set_static_root" [I32Type] []; + E.add_func_import env "rts" "get_static_root" [] [I32Type]; E.add_func_import env "rts" "memcpy" [I32Type; I32Type; I32Type] [I32Type]; (* standard libc memcpy *) E.add_func_import env "rts" "memcmp" [I32Type; I32Type; I32Type] [I32Type]; E.add_func_import env "rts" "version" [] [I32Type]; @@ -1891,13 +1896,6 @@ module MutBox = struct let alloc env = Tagged.obj env Tagged.MutBox [ compile_unboxed_zero ] - let static env = - let ptr = Tagged.new_static_obj env Tagged.MutBox StaticBytes.[ - I32 0l; (* zero *) - ] in - E.add_static_root env ptr; - ptr - let load_field env = Tagged.load_forwarding_pointer env ^^ Tagged.load_field env (field env) @@ -7182,9 +7180,9 @@ end *) module Stabilization = struct - let load_actor env = E.call_import env "rts" "load_actor" + let load_stable_actor env = E.call_import env "rts" "load_stable_actor" - let save_actor env = E.call_import env "rts" "save_actor" + let save_stable_actor env = E.call_import env "rts" "save_stable_actor" let empty_actor env t = let (_, fs) = Type.as_obj t in @@ -7195,14 +7193,14 @@ module Stabilization = struct Object.lit_raw env fs' let stabilize env t = - save_actor env + save_stable_actor env let destabilize env t = - load_actor env ^^ + load_stable_actor env ^^ G.i (Test (Wasm.Values.I32 I32Op.Eqz)) ^^ G.if1 I32Type (empty_actor env t) - (load_actor env) + (load_stable_actor env) end (* @@ -7519,20 +7517,20 @@ end *) module GCRoots = struct - let register env static_roots = - - let get_static_roots = E.add_fun env "get_static_roots" (Func.of_body env [] [I32Type] (fun env -> - compile_unboxed_const static_roots - )) in - - E.add_export env (nr { - name = Lib.Utf8.decode "get_static_roots"; - edesc = nr (FuncExport (nr get_static_roots)) - }) + let create_root_array env = Func.share_code0 env "create_root_array" [I32Type] (fun env -> + let length = Int32.to_int (E.count_static_variables env) in + let variables = Lib.List.table length (fun _ -> MutBox.alloc env) in + Arr.lit env variables + ) - let store_static_roots env = - Arr.vanilla_lit env (E.get_static_roots env) + let register_static_variables env = + create_root_array env ^^ + E.call_import env "rts" "set_static_root" + let get_static_variable env index = + E.call_import env "rts" "get_static_root" ^^ + compile_unboxed_const index ^^ + Arr.idx env end (* GCRoots *) module StackRep = struct @@ -7700,9 +7698,9 @@ module VarEnv = struct (* A Wasm Local of the current function, that points to memory location, which is a MutBox. Used for mutable captured data *) | HeapInd of int32 - (* A static mutable memory location (static address of a MutBox object) *) - (* TODO: Do we need static immutable? *) - | HeapStatic of int32 + (* A static variable accessed by an index via the runtime system, refers to a MutBox, + belonging to the GC root set *) + | Static of int32 (* Not materialized (yet), statically known constant, static location on demand *) | Const of Const.t (* public method *) @@ -7711,7 +7709,7 @@ module VarEnv = struct let is_non_local : varloc -> bool = function | Local _ | HeapInd _ -> false - | HeapStatic _ + | Static _ | PublicMethod _ | Const _ -> true @@ -7771,8 +7769,8 @@ module VarEnv = struct E.add_local_name env i name; ({ ae with vars = NameEnv.add name ((HeapInd i), typ) ae.vars }, i) - let add_local_heap_static (ae : t) name ptr typ = - { ae with vars = NameEnv.add name ((HeapStatic ptr), typ) ae.vars } + let add_static_variable (ae : t) name index typ = + { ae with vars = NameEnv.add name ((Static index), typ) ae.vars } let add_local_public_method (ae : t) name (fi, exported_name) typ = { ae with vars = NameEnv.add name ((PublicMethod (fi, exported_name) : varloc), typ) ae.vars } @@ -7797,9 +7795,9 @@ module VarEnv = struct E.add_local_name env i name; let ae' = { ae with vars = NameEnv.add name ((Local (SR.Vanilla, i)), typ) ae.vars } in add_arguments env ae' as_local remainder - else (* needs to go to static memory *) - let ptr = MutBox.static env in - let ae' = add_local_heap_static ae name ptr typ in + else + let index = E.add_static_variable env in + let ae' = add_static_variable ae name index typ in add_arguments env ae' as_local remainder let add_argument_locals env (ae : t) = @@ -7866,24 +7864,27 @@ module Var = struct G.i (LocalGet (nr i)), SR.Vanilla, MutBox.store_field env - | (Some ((HeapStatic ptr), typ), Flags.Generational) when potential_pointer typ -> - compile_unboxed_const ptr, + | (Some ((Static index), typ), Flags.Generational) when potential_pointer typ -> + let (set_static_variable, get_static_variable) = new_local env "static_variable" in + GCRoots.get_static_variable env index ^^ + Tagged.load_forwarding_pointer env ^^ (* not needed for this GC, but only for forward pointer sanity checks *) + set_static_variable ^^ + get_static_variable, SR.Vanilla, MutBox.store_field env ^^ - compile_unboxed_const ptr ^^ - Tagged.load_forwarding_pointer env ^^ (* not needed for this GC, but only for forward pointer sanity checks *) + get_static_variable ^^ compile_add_const ptr_unskew ^^ compile_add_const (Int32.mul (MutBox.field env) Heap.word_size) ^^ E.call_import env "rts" "post_write_barrier" - | (Some ((HeapStatic ptr), typ), Flags.Incremental) when potential_pointer typ -> - compile_unboxed_const ptr ^^ + | (Some ((Static index), typ), Flags.Incremental) when potential_pointer typ -> + GCRoots.get_static_variable env index ^^ Tagged.load_forwarding_pointer env ^^ compile_add_const ptr_unskew ^^ compile_add_const (Int32.mul (MutBox.field env) Heap.word_size), SR.Vanilla, Tagged.write_with_barrier env - | (Some ((HeapStatic ptr), typ), _) -> - compile_unboxed_const ptr, + | (Some ((Static index), typ), _) -> + GCRoots.get_static_variable env index, SR.Vanilla, MutBox.store_field env | (Some ((Const _), _), _) -> fatal "set_val: %s is const" var @@ -7914,8 +7915,10 @@ module Var = struct sr, G.i (LocalGet (nr i)) | Some (HeapInd i) -> SR.Vanilla, G.i (LocalGet (nr i)) ^^ MutBox.load_field env - | Some (HeapStatic i) -> - SR.Vanilla, compile_unboxed_const i ^^ MutBox.load_field env + | Some (Static index) -> + SR.Vanilla, + GCRoots.get_static_variable env index ^^ + MutBox.load_field env | Some (Const c) -> SR.Const c, G.nop | Some (PublicMethod (_, name)) -> @@ -7956,12 +7959,12 @@ module Var = struct In the IR, mutable fields of objects are pre-allocated as MutBox objects, to allow the async/await. So we expect the variable to be in a HeapInd (pointer to MutBox on the heap), - or HeapStatic (statically known MutBox in the static memory) and we use - the pointer. + or Static (static variable represented as a MutBox that is accessed via the + runtime system) and we use the pointer. *) let get_aliased_box env ae var = match VarEnv.lookup_var ae var with | Some (HeapInd i) -> G.i (LocalGet (nr i)) - | Some (HeapStatic i) -> compile_unboxed_const i + | Some (Static index) -> GCRoots.get_static_variable env index | _ -> assert false let capture_aliased_box env ae var = match VarEnv.lookup_var ae var with @@ -8633,7 +8636,7 @@ module AllocHow = struct let how_of_ae ae : allocHow = M.map (fun (l, _) -> match l with | VarEnv.Const _ -> (Const : how) - | VarEnv.HeapStatic _ -> StoreStatic + | VarEnv.Static _ -> StoreStatic | VarEnv.HeapInd _ -> StoreHeap | VarEnv.Local (sr, _) -> LocalMut sr (* conservatively assume mutable *) | VarEnv.PublicMethod _ -> LocalMut SR.Vanilla @@ -8667,8 +8670,8 @@ module AllocHow = struct let alloc_code = MutBox.alloc env ^^ G.i (LocalSet (nr i)) in (ae1, alloc_code) | StoreStatic -> - let ptr = MutBox.static env in - let ae1 = VarEnv.add_local_heap_static ae name ptr typ in + let index = E.add_static_variable env in + let ae1 = VarEnv.add_static_variable ae name index typ in (ae1, G.nop) let add_local_for_alias env ae how name typ : VarEnv.t * G.t = @@ -11209,8 +11212,6 @@ and conclude_module env set_serialization_globals start_fi_o = (* See Note [Candid subtype checks] *) Serialization.set_delayed_globals env set_serialization_globals; - let static_roots = GCRoots.store_static_roots env in - (* declare before building GC *) (* add beginning-of-heap pointer, may be changed by linker *) @@ -11219,7 +11220,6 @@ and conclude_module env set_serialization_globals start_fi_o = E.export_global env "__heap_base"; Heap.register env; - GCRoots.register env static_roots; IC.register env; set_heap_base (E.get_end_of_static_memory env); @@ -11227,6 +11227,7 @@ and conclude_module env set_serialization_globals start_fi_o = (* Wrap the start function with the RTS initialization *) let rts_start_fi = E.add_fun env "rts_start" (Func.of_body env [] [] (fun env1 -> E.call_import env "rts" ("initialize_" ^ E.gc_strategy_name !Flags.gc_strategy ^ "_gc") ^^ + GCRoots.register_static_variables env ^^ match start_fi_o with | Some fi -> G.i (Call fi) From 801eeb6288e0102ef6e63169c514af0776dc17df Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Thu, 10 Aug 2023 18:14:24 +0200 Subject: [PATCH 007/260] Reorganize static root --- rts/motoko-rts/src/gc/incremental/roots.rs | 79 ++++++++++------------ rts/motoko-rts/src/persistence.rs | 15 +--- 2 files changed, 37 insertions(+), 57 deletions(-) diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs index d1477b98bcc..5e196d15c8a 100644 --- a/rts/motoko-rts/src/gc/incremental/roots.rs +++ b/rts/motoko-rts/src/gc/incremental/roots.rs @@ -1,21 +1,25 @@ -use crate::{types::Value, visitor::pointer_to_dynamic_heap}; +use motoko_rts_macros::ic_mem_fn; + +use crate::{memory::Memory, types::Value, visitor::pointer_to_dynamic_heap}; + +use super::barriers::write_with_barrier; + +/// Root referring to all canister variables. +/// This root is reinitialized on each canister upgrade. +/// The scalar sentinel denotes an uninitialized root. +static mut STATIC_ROOT: Value = Value::from_scalar(0); /// GC root set. -#[derive(Clone, Copy)] -pub struct Roots { - pub static_roots: Value, - pub continuation_table_location: *mut Value, - // If new roots are added in future, extend `visit_roots()`. -} +pub type Roots = [*mut Value; 3]; #[cfg(feature = "ic")] pub unsafe fn root_set() -> Roots { - use crate::continuation_table::continuation_table_loc; - use crate::persistence::get_static_root; - Roots { - static_roots: get_static_root(), - continuation_table_location: continuation_table_loc(), - } + use crate::{continuation_table::continuation_table_loc, persistence::stable_actor_location}; + [ + static_root_location(), + continuation_table_loc(), + stable_actor_location(), + ] } pub unsafe fn visit_roots( @@ -24,39 +28,26 @@ pub unsafe fn visit_roots( context: &mut C, visit_field: V, ) { - visit_static_roots(roots.static_roots, heap_base, context, &visit_field); - visit_continuation_table( - roots.continuation_table_location, - heap_base, - context, - &visit_field, - ); -} - -unsafe fn visit_static_roots( - static_roots: Value, - heap_base: usize, - context: &mut C, - visit_field: &V, -) { - let root_array = static_roots.as_array(); - for index in 0..root_array.len() { - let mutbox = root_array.get(index).as_mutbox(); - debug_assert!((mutbox as usize) < heap_base); - let field = &mut (*mutbox).field; - if pointer_to_dynamic_heap(field, heap_base) { - visit_field(context, field); + for location in roots { + // TODO: Check whether all pointers lead to dynamic heap, no static heap + if pointer_to_dynamic_heap(location, heap_base) { + visit_field(context, location); } } } -unsafe fn visit_continuation_table( - continuation_table_location: *mut Value, - heap_base: usize, - context: &mut C, - visit_field: &V, -) { - if pointer_to_dynamic_heap(continuation_table_location, heap_base) { - visit_field(context, continuation_table_location); - } +unsafe fn static_root_location() -> *mut Value { + &mut STATIC_ROOT as *mut Value +} + +#[ic_mem_fn] +pub unsafe fn set_static_root(mem: &mut M, value: Value) { + let location = &mut STATIC_ROOT as *mut Value; + write_with_barrier(mem, location, value); +} + +#[no_mangle] +pub unsafe extern "C" fn get_static_root() -> Value { + assert!(STATIC_ROOT.is_ptr()); + STATIC_ROOT } diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index e502bc4eddb..a57f342b94a 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -22,7 +22,6 @@ struct PersistentMetadata { version: usize, stable_actor: Value, // Must be added to the root set, use forwarding incremental_gc_state: State, - static_root: Value, } const METATDATA_ADDRESS: usize = 4 * 1024 * 1024; @@ -63,7 +62,6 @@ impl PersistentMetadata { (*self).fingerprint = FINGERPRINT; (*self).version = VERSION; (*self).stable_actor = NO_OBJECT; - (*self).static_root = NO_OBJECT; } } @@ -99,16 +97,7 @@ pub(crate) unsafe fn get_incremenmtal_gc_state() -> &'static mut State { &mut (*metadata).incremental_gc_state } -#[ic_mem_fn] -pub unsafe fn set_static_root(mem: &mut M, value: Value) { - let metadata = PersistentMetadata::get(); - let location = &mut (*metadata).static_root as *mut Value; - write_with_barrier(mem, location, value); -} - -#[no_mangle] -pub unsafe extern "C" fn get_static_root() -> Value { +pub(crate) unsafe fn stable_actor_location() -> *mut Value { let metadata = PersistentMetadata::get(); - assert!((*metadata).static_root != NO_OBJECT); - (*metadata).static_root + &mut (*metadata).stable_actor as *mut Value } From bc7e8e152fb337be41dc03b56a0009199427a485 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 11:23:53 +0200 Subject: [PATCH 008/260] Place static optional values in the heap --- rts/motoko-rts/src/gc/incremental/roots.rs | 8 +- rts/motoko-rts/src/persistence.rs | 85 +++++++++++++++++----- src/codegen/compile.ml | 52 +++++-------- 3 files changed, 91 insertions(+), 54 deletions(-) diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs index 5e196d15c8a..d1fd77f7a51 100644 --- a/rts/motoko-rts/src/gc/incremental/roots.rs +++ b/rts/motoko-rts/src/gc/incremental/roots.rs @@ -10,15 +10,19 @@ use super::barriers::write_with_barrier; static mut STATIC_ROOT: Value = Value::from_scalar(0); /// GC root set. -pub type Roots = [*mut Value; 3]; +pub type Roots = [*mut Value; 4]; #[cfg(feature = "ic")] pub unsafe fn root_set() -> Roots { - use crate::{continuation_table::continuation_table_loc, persistence::stable_actor_location}; + use crate::{ + continuation_table::continuation_table_loc, + persistence::{null_singleton_location, stable_actor_location}, + }; [ static_root_location(), continuation_table_loc(), stable_actor_location(), + null_singleton_location(), ] } diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index a57f342b94a..cebd977d7a4 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -2,25 +2,40 @@ //! //! Persistent metadata table, located at 4MB, in the static partition space. -use core::mem::size_of; - use motoko_rts_macros::ic_mem_fn; -use crate::{barriers::write_with_barrier, gc::incremental::State, memory::Memory, types::Value}; +use crate::{ + barriers::{allocation_barrier, write_with_barrier}, + gc::incremental::State, + memory::Memory, + types::{size_of, Null, Value, TAG_NULL}, +}; const FINGERPRINT: [char; 32] = [ 'M', 'O', 'T', 'O', 'K', 'O', ' ', 'O', 'R', 'T', 'H', 'O', 'G', 'O', 'N', 'A', 'L', ' ', 'P', 'E', 'R', 'S', 'I', 'S', 'T', 'E', 'N', 'C', 'E', ' ', '3', '2', ]; const VERSION: usize = 1; -const NO_OBJECT: Value = Value::from_scalar(0); +// The `Value` representation in the default-initialized Wasm memory. +// The GC ignores this value since it is a scalar representation. +const DEFAULT_VALUE: Value = Value::from_scalar(0); // Use a long-term representation by relying on C layout. +// The `Value` references belong to the GC root set and require forwarding pointer resolution. #[repr(C)] struct PersistentMetadata { + // Predefined character sequence in the memory to double check the orthogonal persistence mode. fingerprint: [char; 32], + // Version of the orthogonal persistence. To be increased on every persistent memory layout modification. version: usize, - stable_actor: Value, // Must be added to the root set, use forwarding + // Reference to the stable sub-record of the actor, comprising all stable actor fields. Set before upgrade. + // Constitutes a GC root and requires pointer forwarding. + stable_actor: Value, + // Singleton of the top-level null value. To be retained across upgrades. + // Constitutes a GC root and requires pointer forwarding. + null_singleton: Value, + // The state of the incremental GC including the partitioned heap description. + // The GC continues work after upgrades. incremental_gc_state: State, } @@ -31,7 +46,7 @@ const METADATA_RESERVE: usize = 128 * 1024; #[cfg(feature = "ic")] pub const HEAP_START: usize = METATDATA_ADDRESS + METADATA_RESERVE; -const _: () = assert!(size_of::() <= METADATA_RESERVE); +const _: () = assert!(core::mem::size_of::() <= METADATA_RESERVE); impl PersistentMetadata { fn get() -> *mut Self { @@ -42,7 +57,10 @@ impl PersistentMetadata { // Wasm memory is zero-initialized according to the Wasm specification. let initialized = (*self).version != 0; assert!( - initialized || (*self).fingerprint == ['\0'; 32] && (*self).stable_actor == NO_OBJECT + initialized + || (*self).fingerprint == ['\0'; 32] + && (*self).stable_actor == DEFAULT_VALUE + && (*self).null_singleton == DEFAULT_VALUE ); initialized } @@ -57,47 +75,78 @@ impl PersistentMetadata { } } - unsafe fn initialize(self: *mut Self) { + unsafe fn initialize(self: *mut Self, mem: &mut M) { debug_assert!(!self.is_initialized()); (*self).fingerprint = FINGERPRINT; (*self).version = VERSION; - (*self).stable_actor = NO_OBJECT; + (*self).stable_actor = DEFAULT_VALUE; + (*self).null_singleton = alloc_null(mem); } } +/// Initialize fresh peristent memory after the canister installation or +/// reuse the persistent memory on a canister upgrade. pub unsafe fn initialize_memory(mem: &mut M) { mem.grow_memory(HEAP_START as u64); let metadata = PersistentMetadata::get(); if metadata.is_initialized() { - println!(100, "MEMORY REUSED"); metadata.check_version(); } else { - println!(100, "MEMORY INITIALIZED"); - metadata.initialize(); + metadata.initialize(mem); } } -/// Returns scalar 0 if no actor is stored. +/// Returns the stable sub-record of the actor of the upgraded canister version. +/// Returns scalar 0 if no actor is stored after on a fresh memory. #[no_mangle] pub unsafe extern "C" fn load_stable_actor() -> Value { let metadata = PersistentMetadata::get(); (*metadata).stable_actor.forward_if_possible() } +/// Save the stable sub-record, the stable variables, of a canister before an upgrade. #[ic_mem_fn] pub unsafe fn save_stable_actor(mem: &mut M, actor: Value) { - assert!(actor != NO_OBJECT); + assert!(actor != DEFAULT_VALUE); let metadata = PersistentMetadata::get(); let location = &mut (*metadata).stable_actor as *mut Value; write_with_barrier(mem, location, actor); } -pub(crate) unsafe fn get_incremenmtal_gc_state() -> &'static mut State { +// GC root pointer required for GC marking and updating. +pub(crate) unsafe fn stable_actor_location() -> *mut Value { let metadata = PersistentMetadata::get(); - &mut (*metadata).incremental_gc_state + &mut (*metadata).stable_actor as *mut Value } -pub(crate) unsafe fn stable_actor_location() -> *mut Value { +unsafe fn alloc_null(mem: &mut M) -> Value { + let value = mem.alloc_words(size_of::()); + let null = value.get_ptr() as *mut Null; + (*null).header.tag = TAG_NULL; + (*null).header.init_forward(value); + allocation_barrier(value); + value +} + +/// Get the null singleton used for top-level optional types. +/// Serves for optimized null checks by pointer comparison. +/// The forwarding pointer of this object is already resolved. +/// NOTE: The forwarding pointer of the other comparison argument needs +/// to be resolved too. +#[no_mangle] +pub unsafe extern "C" fn null_singleton() -> Value { let metadata = PersistentMetadata::get(); - &mut (*metadata).stable_actor as *mut Value + (*metadata).null_singleton.forward_if_possible() +} + +// GC root pointer required for GC marking and updating. +pub(crate) unsafe fn null_singleton_location() -> *mut Value { + let metadata = PersistentMetadata::get(); + &mut (*metadata).null_singleton as *mut Value +} + +// GC root pointer required for GC marking and updating. +pub(crate) unsafe fn get_incremenmtal_gc_state() -> &'static mut State { + let metadata = PersistentMetadata::get(); + &mut (*metadata).incremental_gc_state } diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index b50a215c7a7..c51738034d4 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -931,6 +931,7 @@ module RTS = struct E.add_func_import env "rts" "save_stable_actor" [I32Type] []; E.add_func_import env "rts" "set_static_root" [I32Type] []; E.add_func_import env "rts" "get_static_root" [] [I32Type]; + E.add_func_import env "rts" "null_singleton" [] [I32Type]; E.add_func_import env "rts" "memcpy" [I32Type; I32Type; I32Type] [I32Type]; (* standard libc memcpy *) E.add_func_import env "rts" "memcmp" [I32Type; I32Type; I32Type] [I32Type]; E.add_func_import env "rts" "version" [] [I32Type]; @@ -1916,9 +1917,9 @@ module Opt = struct │ null │ └──────┘ - A special null value. It is fully static, and because it is unique, can - be recognized by pointer comparison (only the GC will care about the heap - tag). + A special null value. This is a singleton object stored in the persistent memory + and retained across all objects. This serves for efficient null checks by using + pointer comparison (and applying pointer forwarding resolution beforehand). 2. ┌──────┬─────────┐ @@ -1942,22 +1943,18 @@ module Opt = struct let some_payload_field = Tagged.header_size - (* This relies on the fact that add_static deduplicates *) - let null_vanilla_lit env : int32 = - Tagged.shared_static_obj env Tagged.Null [] - let null_lit env = - compile_unboxed_const (null_vanilla_lit env) - - let vanilla_lit env ptr : int32 = - Tagged.shared_static_obj env Tagged.Some StaticBytes.[ - I32 ptr - ] + E.call_import env "rts" "null_singleton" (* forwarding pointer already resolved *) - let is_some env = + let is_some env = + (* resolve forwarding pointer on pointer equality check *) + Tagged.load_forwarding_pointer env ^^ null_lit env ^^ G.i (Compare (Wasm.Values.I32 I32Op.Ne)) + let alloc_some env get_payload = + Tagged.obj env Tagged.Some [ get_payload ] + let inject env e = e ^^ Func.share_code1 env "opt_inject" ("x", I32Type) [I32Type] (fun env get_x -> @@ -1968,13 +1965,8 @@ module Opt = struct ( get_x ) (* true literal, no wrapping *) ( get_x ^^ Tagged.branch_default env [I32Type] ( get_x ) (* default tag, no wrapping *) - [ Tagged.Null, - (* NB: even ?null does not require allocation: We use a static - singleton for that: *) - compile_unboxed_const (vanilla_lit env (null_vanilla_lit env)) - ; Tagged.Some, - Tagged.obj env Tagged.Some [get_x] - ] + [ Tagged.Null, alloc_some env get_x + ; Tagged.Some, alloc_some env get_x ] ) ) ) @@ -3689,9 +3681,6 @@ module Blob = struct Tagged.load_forwarding_pointer env ^^ compile_add_const (unskewed_payload_offset env) - (* TODO: Implement dynamic sharing with lazy instantiation on the heap, - the static memory only denotes singleton address per upgrade mode, - the static singleton address needs to be re-initialized on upgrade *) let lit env s = let blob_length = Int32.of_int (String.length s) in let (set_new_blob, get_new_blob) = new_local env "new_blob" in @@ -7622,8 +7611,8 @@ module StackRep = struct | Const.Word32 n -> BoxedSmallWord.vanilla_lit env n | Const.Word64 n -> BoxedWord64.vanilla_lit env n | Const.Float64 f -> Float.vanilla_lit env f - | Const.Blob t -> assert false - | Const.Null -> Opt.null_vanilla_lit env + | Const.Blob t -> assert false (* TODO: Handle case, e.g. part of array literal *) + | Const.Null -> assert false (* TODO: Handle case, e.g. part of array literal *) let rec materialize_const_t env (p, cv) : int32 = Lib.Promise.lazy_value p (fun () -> materialize_const_v env cv) @@ -7642,14 +7631,7 @@ module StackRep = struct let ptr = materialize_const_t env c in Variant.vanilla_lit env i ptr | Const.Lit l -> materialize_lit env l - | Const.Opt c -> - let rec kernel = Const.(function - | (_, Lit Null) -> None - | (_, Opt c) -> kernel c - | (_, other) -> Some (materialize_const_v env other)) in - match kernel c with - | Some ptr -> ptr - | None -> Opt.vanilla_lit env (materialize_const_t env c) + | Const.Opt c -> assert false (* TODO: Handle case, e.g. part of array literal *) let adjust env (sr_in : t) sr_out = if eq sr_in sr_out @@ -7672,6 +7654,8 @@ module StackRep = struct | Const (_, Const.Lit (Const.Bool b)), Vanilla -> Bool.lit b | Const (_, Const.Lit (Const.Blob t)), Vanilla -> Blob.lit env t + | Const (_, Const.Lit (Const.Null)), Vanilla -> Opt.null_lit env + | Const (_, Const.Opt c), Vanilla -> Opt.inject env (compile_unboxed_const (materialize_const_t env c)) | Const c, Vanilla -> compile_unboxed_const (materialize_const_t env c) | Const (_, Const.Lit (Const.Word32 n)), UnboxedWord32 -> compile_unboxed_const n | Const (_, Const.Lit (Const.Word64 n)), UnboxedWord64 -> compile_const_64 n From 9f9262d263b696b853757f817330f89946de620b Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 13:48:37 +0200 Subject: [PATCH 009/260] Redesign constant materialization --- src/codegen/compile.ml | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index c51738034d4..c7390abd2f3 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -161,6 +161,8 @@ module Const = struct let t_of_v v = (Lib.Promise.make (), v) + let get_value (_, v) = v + end (* Const *) module SR = struct @@ -7600,7 +7602,7 @@ module StackRep = struct | UnboxedTuple n -> G.table n (fun _ -> G.i Drop) | Const _ | Unreachable -> G.nop - (* Materializes a Const.lit: If necessary, puts + (* (* Materializes a Const.lit: If necessary, puts bytes into static memory, and returns a vanilla value. *) let materialize_lit env (lit : Const.lit) : int32 = @@ -7631,7 +7633,28 @@ module StackRep = struct let ptr = materialize_const_t env c in Variant.vanilla_lit env i ptr | Const.Lit l -> materialize_lit env l - | Const.Opt c -> assert false (* TODO: Handle case, e.g. part of array literal *) + | Const.Opt c -> assert false TODO: Handle case, e.g. part of array literal *) + + (* let rec new_materialize_lazy env (promise, value) : G.t = + Lib.Promise.lazy_value promise (fun () -> new_materialize_constant env value) *) + + let rec materialize_constant env = function + | Const.Lit (Const.Bool b) -> Bool.lit b + | Const.Lit (Const.Blob t) -> Blob.lit env t + | Const.Lit (Const.Null) -> Opt.null_lit env + | Const.Lit (Const.BigInt n) -> compile_unboxed_const (BigNum.vanilla_lit env n) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.Word32 n) -> compile_unboxed_const (BoxedSmallWord.vanilla_lit env n) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.Word64 n) -> compile_unboxed_const (BoxedWord64.vanilla_lit env n) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.Float64 f) -> compile_unboxed_const (Float.vanilla_lit env f) (* TODO: Redesign for heap allocations *) + | Const.Opt c -> Opt.inject env (materialize_constant env (Const.get_value c)) + | Const.Fun (get_fi, _) -> compile_unboxed_const (Closure.static_closure env (get_fi ())) (* TODO: Redesign for heap allocations *) + | Const.Message fi -> assert false + | Const.Unit -> compile_unboxed_const Tuple.unit_vanilla_lit (* TODO: Redesign for heap allocations *) + | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env (Const.get_value c)) + + | Const.Lit (Const.Vanilla n) -> assert false + | Const.Array cs -> assert false + | Const.Obj l -> assert false let adjust env (sr_in : t) sr_out = if eq sr_in sr_out @@ -7652,18 +7675,19 @@ module StackRep = struct | UnboxedFloat64, Vanilla -> Float.box env | Vanilla, UnboxedFloat64 -> Float.unbox env - | Const (_, Const.Lit (Const.Bool b)), Vanilla -> Bool.lit b + | Const (_, value), Vanilla -> materialize_constant env value + (* | Const (_, Const.Lit (Const.Bool b)), Vanilla -> Bool.lit b | Const (_, Const.Lit (Const.Blob t)), Vanilla -> Blob.lit env t | Const (_, Const.Lit (Const.Null)), Vanilla -> Opt.null_lit env - | Const (_, Const.Opt c), Vanilla -> Opt.inject env (compile_unboxed_const (materialize_const_t env c)) - | Const c, Vanilla -> compile_unboxed_const (materialize_const_t env c) + | Const (_, Const.Opt c), Vanilla -> Opt.inject env (materialize_const_t env c)) + | Const c, Vanilla -> compile_unboxed_const (materialize_const_t env c) *) | Const (_, Const.Lit (Const.Word32 n)), UnboxedWord32 -> compile_unboxed_const n | Const (_, Const.Lit (Const.Word64 n)), UnboxedWord64 -> compile_const_64 n | Const (_, Const.Lit (Const.Float64 f)), UnboxedFloat64 -> Float.compile_unboxed_const f | Const c, UnboxedTuple 0 -> G.nop | Const (_, Const.Array cs), UnboxedTuple n -> assert (n = List.length cs); - G.concat_map (fun c -> compile_unboxed_const (materialize_const_t env c)) cs + G.concat_map (fun c -> materialize_constant env (Const.get_value c)) cs | _, _ -> Printf.eprintf "Unknown stack_rep conversion %s -> %s\n" (to_string sr_in) (to_string sr_out); From 296bbcd50071277a92f22266a2005a538808c9bc Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:02:51 +0200 Subject: [PATCH 010/260] Place static Word64 in dynamic heap --- src/codegen/compile.ml | 43 ++++++------------------------------------ 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index c7390abd2f3..45b5ad4f870 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2037,12 +2037,6 @@ module Variant = struct get_variant_tag env ^^ compile_eq_const (hash_variant_label env l) - let vanilla_lit env i ptr = - Tagged.shared_static_obj env Tagged.Variant StaticBytes.[ - I32 (hash_variant_label env i); - I32 ptr - ] - end (* Variant *) @@ -2125,14 +2119,6 @@ module BoxedWord64 = struct let payload_field = Tagged.header_size - let vanilla_lit env i = - if BitTagged.can_tag_const i - then BitTagged.tag_const i - else - Tagged.shared_static_obj env Tagged.Bits64 StaticBytes.[ - I64 i - ] - let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i64" in let size = if !Flags.gc_strategy = Flags.Incremental then 4l else 3l in @@ -2142,6 +2128,11 @@ module BoxedWord64 = struct get_i ^^ Tagged.allocation_barrier env + let lit env i = + if BitTagged.can_tag_const i + then compile_unboxed_const (BitTagged.tag_const i) + else compile_box env (compile_const_64 i) + let box env = Func.share_code1 env "box_i64" ("n", I64Type) [I32Type] (fun env get_n -> get_n ^^ BitTagged.if_can_tag_i64 env [I32Type] (get_n ^^ BitTagged.tag) @@ -3477,22 +3468,6 @@ module Object = struct module FieldEnv = Env.Make(String) - (* This is for static objects *) - let vanilla_lit env (fs : (string * int32) list) : int32 = - let (hashes, ptrs) = fs - |> List.map (fun (n, ptr) -> (Mo_types.Hash.hash n,ptr)) - |> List.sort compare - |> List.split - in - - let hash_ptr = E.add_static env StaticBytes.[ i32s hashes ] in - - Tagged.shared_static_obj env Tagged.Object StaticBytes.[ - I32 (Int32.of_int (List.length fs)); - I32 hash_ptr; - i32s ptrs; - ] - (* This is for non-recursive objects, i.e. ObjNewE *) (* The instructions in the field already create the indirection if needed *) let lit_raw env (fs : (string * (unit -> G.t)) list ) = @@ -4011,12 +3986,6 @@ module Arr = struct | Type.Array element_type -> element_type | _ -> assert false - let vanilla_lit env ptrs = - Tagged.shared_static_obj env Tagged.Array StaticBytes.[ - I32 (Int32.of_int (List.length ptrs)); - i32s ptrs; - ] - (* Compile an array literal. *) let lit env element_instructions = Tagged.obj env Tagged.Array @@ -7644,7 +7613,7 @@ module StackRep = struct | Const.Lit (Const.Null) -> Opt.null_lit env | Const.Lit (Const.BigInt n) -> compile_unboxed_const (BigNum.vanilla_lit env n) (* TODO: Redesign for heap allocations *) | Const.Lit (Const.Word32 n) -> compile_unboxed_const (BoxedSmallWord.vanilla_lit env n) (* TODO: Redesign for heap allocations *) - | Const.Lit (Const.Word64 n) -> compile_unboxed_const (BoxedWord64.vanilla_lit env n) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n | Const.Lit (Const.Float64 f) -> compile_unboxed_const (Float.vanilla_lit env f) (* TODO: Redesign for heap allocations *) | Const.Opt c -> Opt.inject env (materialize_constant env (Const.get_value c)) | Const.Fun (get_fi, _) -> compile_unboxed_const (Closure.static_closure env (get_fi ())) (* TODO: Redesign for heap allocations *) From f2eb3dd429b3a85f67190199728503499d24d060 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:04:11 +0200 Subject: [PATCH 011/260] Remove redundant code --- src/codegen/compile.ml | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 45b5ad4f870..cbed72171b2 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7571,42 +7571,6 @@ module StackRep = struct | UnboxedTuple n -> G.table n (fun _ -> G.i Drop) | Const _ | Unreachable -> G.nop - (* (* Materializes a Const.lit: If necessary, puts - bytes into static memory, and returns a vanilla value. - *) - let materialize_lit env (lit : Const.lit) : int32 = - match lit with - | Const.Vanilla n -> n - | Const.Bool n -> Bool.vanilla_lit n - | Const.BigInt n -> BigNum.vanilla_lit env n - | Const.Word32 n -> BoxedSmallWord.vanilla_lit env n - | Const.Word64 n -> BoxedWord64.vanilla_lit env n - | Const.Float64 f -> Float.vanilla_lit env f - | Const.Blob t -> assert false (* TODO: Handle case, e.g. part of array literal *) - | Const.Null -> assert false (* TODO: Handle case, e.g. part of array literal *) - - let rec materialize_const_t env (p, cv) : int32 = - Lib.Promise.lazy_value p (fun () -> materialize_const_v env cv) - - and materialize_const_v env = function - | Const.Fun (get_fi, _) -> Closure.static_closure env (get_fi ()) - | Const.Message fi -> assert false - | Const.Obj fs -> - let fs' = List.map (fun (n, c) -> (n, materialize_const_t env c)) fs in - Object.vanilla_lit env fs' - | Const.Unit -> Tuple.unit_vanilla_lit - | Const.Array cs -> - let ptrs = List.map (materialize_const_t env) cs in - Arr.vanilla_lit env ptrs - | Const.Tag (i, c) -> - let ptr = materialize_const_t env c in - Variant.vanilla_lit env i ptr - | Const.Lit l -> materialize_lit env l - | Const.Opt c -> assert false TODO: Handle case, e.g. part of array literal *) - - (* let rec new_materialize_lazy env (promise, value) : G.t = - Lib.Promise.lazy_value promise (fun () -> new_materialize_constant env value) *) - let rec materialize_constant env = function | Const.Lit (Const.Bool b) -> Bool.lit b | Const.Lit (Const.Blob t) -> Blob.lit env t @@ -7645,11 +7609,6 @@ module StackRep = struct | Vanilla, UnboxedFloat64 -> Float.unbox env | Const (_, value), Vanilla -> materialize_constant env value - (* | Const (_, Const.Lit (Const.Bool b)), Vanilla -> Bool.lit b - | Const (_, Const.Lit (Const.Blob t)), Vanilla -> Blob.lit env t - | Const (_, Const.Lit (Const.Null)), Vanilla -> Opt.null_lit env - | Const (_, Const.Opt c), Vanilla -> Opt.inject env (materialize_const_t env c)) - | Const c, Vanilla -> compile_unboxed_const (materialize_const_t env c) *) | Const (_, Const.Lit (Const.Word32 n)), UnboxedWord32 -> compile_unboxed_const n | Const (_, Const.Lit (Const.Word64 n)), UnboxedWord64 -> compile_const_64 n | Const (_, Const.Lit (Const.Float64 f)), UnboxedFloat64 -> Float.compile_unboxed_const f From 41d9107526741d874df1de9d8dd7da55d348eda1 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:33:11 +0200 Subject: [PATCH 012/260] Simplify constant representation --- src/codegen/compile.ml | 103 ++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index cbed72171b2..d79c8b357e0 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -102,7 +102,7 @@ module Const = struct | Blob of string | Null - let lit_eq = function + let lit_eq l1 l2 = match l1, l2 with | Vanilla i, Vanilla j -> i = j | BigInt i, BigInt j -> Big_int.eq_big_int i j | Word32 i, Word32 j -> i = j @@ -146,22 +146,16 @@ module Const = struct type v = | Fun of (unit -> int32) * fun_rhs (* function pointer calculated upon first use *) | Message of int32 (* anonymous message, only temporary *) - | Obj of (string * t) list + | Obj of (string * v) list | Unit - | Array of t list (* also tuples, but not nullary *) - | Tag of (string * t) - | Opt of t + | Array of v list (* also tuples, but not nullary *) + | Tag of (string * v) + | Opt of v | Lit of lit - (* A constant known value together with a vanilla pointer. - Typically a static memory location, could be an unboxed scalar. - Filled on demand. - *) - and t = (int32 Lib.Promise.t * v) - - let t_of_v v = (Lib.Promise.make (), v) - - let get_value (_, v) = v + let eq v1 v2 = match v1, v2 with + | Lit l1, Lit l2 -> lit_eq l1 l2 + | _ -> v1 = v2 end (* Const *) @@ -181,7 +175,7 @@ module SR = struct | UnboxedWord32 | UnboxedFloat64 | Unreachable - | Const of Const.t + | Const of Const.v let unit = UnboxedTuple 0 @@ -190,16 +184,9 @@ module SR = struct (* Because t contains Const.t, and that contains Const.v, and that contains Const.lit, and that contains Big_int, we cannot just use normal `=`. So we have to write our own equality. - - This equalty is, I believe, used when joining branches. So for Const, we - just compare the promises, and do not descend into the Const.v. This is - conservative; the only downside is that if a branch returns different - Const.t with (semantically) the same Const.v we do not propagate that as - Const, but materialize before the branch. - Which is not really expected or important. *) let eq (t1 : t) (t2 : t) = match t1, t2 with - | Const (p1, _), Const (p2, _) -> p1 == p2 + | Const c1, Const c2 -> Const.eq c1 c2 | _ -> t1 = t2 let to_var_type : t -> value_type = function @@ -7579,11 +7566,11 @@ module StackRep = struct | Const.Lit (Const.Word32 n) -> compile_unboxed_const (BoxedSmallWord.vanilla_lit env n) (* TODO: Redesign for heap allocations *) | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n | Const.Lit (Const.Float64 f) -> compile_unboxed_const (Float.vanilla_lit env f) (* TODO: Redesign for heap allocations *) - | Const.Opt c -> Opt.inject env (materialize_constant env (Const.get_value c)) + | Const.Opt c -> Opt.inject env (materialize_constant env c) | Const.Fun (get_fi, _) -> compile_unboxed_const (Closure.static_closure env (get_fi ())) (* TODO: Redesign for heap allocations *) | Const.Message fi -> assert false | Const.Unit -> compile_unboxed_const Tuple.unit_vanilla_lit (* TODO: Redesign for heap allocations *) - | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env (Const.get_value c)) + | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env c) | Const.Lit (Const.Vanilla n) -> assert false | Const.Array cs -> assert false @@ -7608,14 +7595,14 @@ module StackRep = struct | UnboxedFloat64, Vanilla -> Float.box env | Vanilla, UnboxedFloat64 -> Float.unbox env - | Const (_, value), Vanilla -> materialize_constant env value - | Const (_, Const.Lit (Const.Word32 n)), UnboxedWord32 -> compile_unboxed_const n - | Const (_, Const.Lit (Const.Word64 n)), UnboxedWord64 -> compile_const_64 n - | Const (_, Const.Lit (Const.Float64 f)), UnboxedFloat64 -> Float.compile_unboxed_const f + | Const value, Vanilla -> materialize_constant env value + | Const Const.Lit (Const.Word32 n), UnboxedWord32 -> compile_unboxed_const n + | Const Const.Lit (Const.Word64 n), UnboxedWord64 -> compile_const_64 n + | Const Const.Lit (Const.Float64 f), UnboxedFloat64 -> Float.compile_unboxed_const f | Const c, UnboxedTuple 0 -> G.nop - | Const (_, Const.Array cs), UnboxedTuple n -> + | Const Const.Array cs, UnboxedTuple n -> assert (n = List.length cs); - G.concat_map (fun c -> materialize_constant env (Const.get_value c)) cs + G.concat_map (fun c -> materialize_constant env c) cs | _, _ -> Printf.eprintf "Unknown stack_rep conversion %s -> %s\n" (to_string sr_in) (to_string sr_out); @@ -7637,8 +7624,8 @@ module VarEnv = struct (* A static variable accessed by an index via the runtime system, refers to a MutBox, belonging to the GC root set *) | Static of int32 - (* Not materialized (yet), statically known constant, static location on demand *) - | Const of Const.t + (* Constant literals can reside in dynamic heap *) + | Const of Const.v (* public method *) | PublicMethod of int32 * string @@ -7917,7 +7904,7 @@ end (* Var *) module Internals = struct let call_prelude_function env ae var = match VarEnv.lookup_var ae var with - | Some (VarEnv.Const (_, Const.Fun (mk_fi, _))) -> + | Some (VarEnv.Const Const.Fun (mk_fi, _)) -> compile_unboxed_zero ^^ (* A dummy closure *) G.i (Call (nr (mk_fi ()))) | _ -> assert false @@ -8008,13 +7995,13 @@ module FuncDec = struct if Type.is_shared_sort sort then begin let (fi, fill) = E.reserve_fun pre_env name in - ( Const.t_of_v (Const.Message fi), fun env ae -> + ( Const.Message fi, fun env ae -> fill (compile_const_message env ae sort control args mk_body ret_tys at) ) end else begin assert (control = Type.Returns); let lf = E.make_lazy_function pre_env name in - ( Const.t_of_v (Const.Fun ((fun () -> Lib.AllocOnUse.use lf), fun_rhs)), fun env ae -> + ( Const.Fun ((fun () -> Lib.AllocOnUse.use lf), fun_rhs), fun env ae -> let restore_no_env _env ae _ = ae, unmodified in Lib.AllocOnUse.def lf (lazy (compile_local_function env ae restore_no_env args mk_body ret_tys at)) ) @@ -8654,7 +8641,7 @@ let const_lit_of_lit : Ir.lit -> Const.lit = function | FloatLit f -> Const.Float64 f let const_of_lit lit = - Const.t_of_v (Const.Lit (const_lit_of_lit lit)) + Const.Lit (const_lit_of_lit lit) let compile_lit lit = SR.Const (const_of_lit lit), G.nop @@ -9347,7 +9334,7 @@ and compile_prim_invocation (env : E.t) ae p es at = (* we duplicate this pattern match to emulate pattern guards *) let call_as_prim = match fun_sr, sort with - | SR.Const (_, Const.Fun (mk_fi, Const.PrimWrapper prim)), _ -> + | SR.Const Const.Fun (mk_fi, Const.PrimWrapper prim), _ -> begin match n_args, e2.it with | 0, _ -> true | 1, _ -> true @@ -9357,7 +9344,7 @@ and compile_prim_invocation (env : E.t) ae p es at = | _ -> false in begin match fun_sr, sort with - | SR.Const (_, Const.Fun (mk_fi, Const.PrimWrapper prim)), _ when call_as_prim -> + | SR.Const Const.Fun (mk_fi, Const.PrimWrapper prim), _ when call_as_prim -> assert (sort = Type.Local); (* Handle argument tuples *) begin match n_args, e2.it with @@ -9376,7 +9363,7 @@ and compile_prim_invocation (env : E.t) ae p es at = (* ugly case; let's just call this as a function for now *) raise (Invalid_argument "call_as_prim was true?") end - | SR.Const (_, Const.Fun (mk_fi, _)), _ -> + | SR.Const Const.Fun (mk_fi, _), _ -> assert (sort = Type.Local); StackRep.of_arity return_arity, @@ -9457,7 +9444,7 @@ and compile_prim_invocation (env : E.t) ae p es at = | DotPrim name, [e] -> let sr, code1 = compile_exp env ae e in begin match sr with - | SR.Const (_, Const.Obj fs) -> + | SR.Const Const.Obj fs -> let c = List.assoc name fs in SR.Const c, code1 | _ -> @@ -10779,7 +10766,7 @@ and compile_dec env pre_ae how v2en dec : VarEnv.t * G.t * (VarEnv.t -> scope_wr | LetD ({it = VarP v; _}, e) when E.NameEnv.mem v v2en -> let (const, fill) = compile_const_exp env pre_ae e in let fi = match const with - | (_, Const.Message fi) -> fi + | Const.Message fi -> fi | _ -> assert false in let pre_ae1 = VarEnv.add_local_public_method pre_ae v (fi, (E.NameEnv.find v v2en)) e.note.Note.typ in G.( pre_ae1, nop, (fun ae -> fill env ae; nop), unmodified) @@ -10849,7 +10836,7 @@ and compile_decs env ae decs captured_in_body : VarEnv.t * scope_wrap = (* This compiles expressions determined to be const as per the analysis in ir_passes/const.ml. See there for more details. *) -and compile_const_exp env pre_ae exp : Const.t * (E.t -> VarEnv.t -> unit) = +and compile_const_exp env pre_ae exp : Const.v * (E.t -> VarEnv.t -> unit) = match exp.it with | FuncE (name, sort, control, typ_binds, args, res_tys, e) -> let fun_rhs = @@ -10905,34 +10892,34 @@ and compile_const_exp env pre_ae exp : Const.t * (E.t -> VarEnv.t -> unit) = | _ -> fatal "compile_const_exp/ObjE: \"%s\" not found" f.it.var in f.it.name, st) fs in - (Const.t_of_v (Const.Obj static_fs), fun _ _ -> ()) + (Const.Obj static_fs), fun _ _ -> () | PrimE (DotPrim name, [e]) -> let (object_ct, fill) = compile_const_exp env pre_ae e in let fs = match object_ct with - | _, Const.Obj fs -> fs + | Const.Obj fs -> fs | _ -> fatal "compile_const_exp/DotE: not a static object" in let member_ct = List.assoc name fs in (member_ct, fill) | PrimE (ProjPrim i, [e]) -> let (object_ct, fill) = compile_const_exp env pre_ae e in let cs = match object_ct with - | _, Const.Array cs -> cs + | Const.Array cs -> cs | _ -> fatal "compile_const_exp/ProjE: not a static tuple" in (List.nth cs i, fill) - | LitE l -> Const.(t_of_v (Lit (const_lit_of_lit l))), (fun _ _ -> ()) - | PrimE (TupPrim, []) -> Const.t_of_v Const.Unit, (fun _ _ -> ()) + | LitE l -> Const.(Lit (const_lit_of_lit l)), (fun _ _ -> ()) + | PrimE (TupPrim, []) -> Const.Unit, (fun _ _ -> ()) | PrimE (ArrayPrim (Const, _), es) | PrimE (TupPrim, es) -> let (cs, fills) = List.split (List.map (compile_const_exp env pre_ae) es) in - Const.(t_of_v (Array cs)), + (Const.Array cs), (fun env ae -> List.iter (fun fill -> fill env ae) fills) | PrimE (TagPrim i, [e]) -> let (arg_ct, fill) = compile_const_exp env pre_ae e in - Const.(t_of_v (Tag (i, arg_ct))), + (Const.Tag (i, arg_ct)), fill | PrimE (OptPrim, [e]) -> let (arg_ct, fill) = compile_const_exp env pre_ae e in - Const.(t_of_v (Opt arg_ct)), + (Const.Opt arg_ct), fill | _ -> assert false @@ -10958,7 +10945,7 @@ and destruct_const_pat ae pat const : VarEnv.t option = match pat.it with | WildP -> Some ae | VarP v -> Some (VarEnv.add_local_const ae v const pat.note) | ObjP pfs -> - let fs = match const with (_, Const.Obj fs) -> fs | _ -> assert false in + let fs = match const with Const.Obj fs -> fs | _ -> assert false in List.fold_left (fun ae (pf : pat_field) -> match ae, List.find_opt (fun (n, _) -> pf.it.name = n) fs with | None, _ -> None @@ -10970,26 +10957,26 @@ and destruct_const_pat ae pat const : VarEnv.t option = match pat.it with if l = None then destruct_const_pat ae p2 const else l | TupP ps -> - let cs = match const with (_, Const.Array cs) -> cs | (_, Const.Unit) -> [] | _ -> assert false in + let cs = match const with Const.Array cs -> cs | Const.Unit -> [] | _ -> assert false in let go ae p c = match ae with | Some ae -> destruct_const_pat ae p c | _ -> None in List.fold_left2 go (Some ae) ps cs | LitP lp -> begin match const with - | (_, Const.Lit lc) when Const.lit_eq (const_lit_of_lit lp, lc) -> Some ae + | Const.Lit lc when Const.lit_eq (const_lit_of_lit lp) lc -> Some ae | _ -> None end | OptP p -> begin match const with - | (_, Const.Opt c) -> destruct_const_pat ae p c - | (_, Const.(Lit Null)) -> None + | Const.Opt c -> destruct_const_pat ae p c + | Const.(Lit Null) -> None | _ -> assert false end | TagP (i, p) -> match const with - | (_, Const.Tag (ic, c)) when i = ic -> destruct_const_pat ae p c - | (_, Const.Tag _) -> None + | Const.Tag (ic, c) when i = ic -> destruct_const_pat ae p c + | Const.Tag _ -> None | _ -> assert false and compile_const_dec env pre_ae dec : (VarEnv.t -> VarEnv.t) * (E.t -> VarEnv.t -> unit) = From 0d8bd41f7a4ec2fb55f550779029f2c9d411de11 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:36:11 +0200 Subject: [PATCH 013/260] Place static Word32 in dynamic heap --- src/codegen/compile.ml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index d79c8b357e0..646f704b03f 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2224,14 +2224,6 @@ module BoxedSmallWord = struct let payload_field env = Tagged.header_size env - let vanilla_lit env i = - if BitTagged.can_tag_const (Int64.of_int (Int32.to_int i)) - then BitTagged.tag_const (Int64.of_int (Int32.to_int i)) - else - Tagged.shared_static_obj env Tagged.Bits32 StaticBytes.[ - I32 i - ] - let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i32" in let size = if !Flags.gc_strategy = Flags.Incremental then 3l else 2l in @@ -2241,6 +2233,11 @@ module BoxedSmallWord = struct get_i ^^ Tagged.allocation_barrier env + let lit env i = + if BitTagged.can_tag_const (Int64.of_int (Int32.to_int i)) + then compile_unboxed_const (BitTagged.tag_const (Int64.of_int (Int32.to_int i))) + else compile_box env (compile_unboxed_const i) + let box env = Func.share_code1 env "box_i32" ("n", I32Type) [I32Type] (fun env get_n -> get_n ^^ compile_shrU_const 30l ^^ G.i (Unary (Wasm.Values.I32 I32Op.Popcnt)) ^^ @@ -7563,7 +7560,7 @@ module StackRep = struct | Const.Lit (Const.Blob t) -> Blob.lit env t | Const.Lit (Const.Null) -> Opt.null_lit env | Const.Lit (Const.BigInt n) -> compile_unboxed_const (BigNum.vanilla_lit env n) (* TODO: Redesign for heap allocations *) - | Const.Lit (Const.Word32 n) -> compile_unboxed_const (BoxedSmallWord.vanilla_lit env n) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.Word32 n) -> BoxedSmallWord.lit env n | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n | Const.Lit (Const.Float64 f) -> compile_unboxed_const (Float.vanilla_lit env f) (* TODO: Redesign for heap allocations *) | Const.Opt c -> Opt.inject env (materialize_constant env c) From 99124a0cea30980330b5cc1ee39425ff83d238e0 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:43:02 +0200 Subject: [PATCH 014/260] Code refactoring --- src/codegen/compile.ml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 646f704b03f..4525258a8cd 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2106,6 +2106,15 @@ module BoxedWord64 = struct let payload_field = Tagged.header_size + let lit env i = + if BitTagged.can_tag_const i + then + compile_unboxed_const (BitTagged.tag_const i) + else + Tagged.obj env Tagged.Bits64 [ + compile_const_64 i + ] + let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i64" in let size = if !Flags.gc_strategy = Flags.Incremental then 4l else 3l in @@ -2115,11 +2124,6 @@ module BoxedWord64 = struct get_i ^^ Tagged.allocation_barrier env - let lit env i = - if BitTagged.can_tag_const i - then compile_unboxed_const (BitTagged.tag_const i) - else compile_box env (compile_const_64 i) - let box env = Func.share_code1 env "box_i64" ("n", I64Type) [I32Type] (fun env get_n -> get_n ^^ BitTagged.if_can_tag_i64 env [I32Type] (get_n ^^ BitTagged.tag) @@ -2224,6 +2228,15 @@ module BoxedSmallWord = struct let payload_field env = Tagged.header_size env + let lit env i = + if BitTagged.can_tag_const (Int64.of_int (Int32.to_int i)) + then + compile_unboxed_const (BitTagged.tag_const (Int64.of_int (Int32.to_int i))) + else + Tagged.obj env Tagged.Bits32 [ + compile_unboxed_const i + ] + let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i32" in let size = if !Flags.gc_strategy = Flags.Incremental then 3l else 2l in @@ -2233,11 +2246,6 @@ module BoxedSmallWord = struct get_i ^^ Tagged.allocation_barrier env - let lit env i = - if BitTagged.can_tag_const (Int64.of_int (Int32.to_int i)) - then compile_unboxed_const (BitTagged.tag_const (Int64.of_int (Int32.to_int i))) - else compile_box env (compile_unboxed_const i) - let box env = Func.share_code1 env "box_i32" ("n", I32Type) [I32Type] (fun env get_n -> get_n ^^ compile_shrU_const 30l ^^ G.i (Unary (Wasm.Values.I32 I32Op.Popcnt)) ^^ From 9fa9ae277f1506b9d5f3ffefaa109cbc1d31b957 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:55:17 +0200 Subject: [PATCH 015/260] Place static BigNum in dynamic heap --- src/codegen/compile.ml | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 4525258a8cd..e4ee0f93c3e 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2653,7 +2653,7 @@ sig val compile_load_from_data_buf : E.t -> G.t -> bool -> G.t (* literals *) - val vanilla_lit : E.t -> Big_int.big_int -> int32 + val lit : E.t -> Big_int.big_int -> G.t (* arithmetic *) val compile_abs : E.t -> G.t @@ -2963,10 +2963,10 @@ module MakeCompact (Num : BigNumType) : BigNumType = struct (get_n ^^ compile_unboxed_const 0l ^^ G.i (Compare (Wasm.Values.I32 I32Op.LtS))) (get_n ^^ Num.compile_is_negative env) - let vanilla_lit env = function + let lit env = function | n when Big_int.is_int_big_int n && BitTagged.can_tag_const (Big_int.int64_of_big_int n) -> - BitTagged.tag_const (Big_int.int64_of_big_int n) - | n -> Num.vanilla_lit env n + compile_unboxed_const (BitTagged.tag_const (Big_int.int64_of_big_int n)) + | n -> Num.lit env n let compile_neg env = Func.share_code1 env "B_neg" ("n", I32Type) [I32Type] (fun env get_n -> @@ -3317,7 +3317,7 @@ module BigNumLibtommath : BigNumType = struct | false -> get_data_buf ^^ E.call_import env "rts" "bigint_leb128_decode" | true -> get_data_buf ^^ E.call_import env "rts" "bigint_sleb128_decode" - let vanilla_lit env n = + let lit env n = (* See enum mp_sign *) let sign = if Big_int.sign_big_int n >= 0 then 0l else 1l in @@ -3331,22 +3331,19 @@ module BigNumLibtommath : BigNumType = struct then [] else let (a, b) = Big_int.quomod_big_int n twoto28 in - [ Big_int.int32_of_big_int b ] @ go a + [ compile_unboxed_const (Big_int.int32_of_big_int b) ] @ go a in go n in (* how many 32 bit digits *) let size = Int32.of_int (List.length limbs) in (* cf. mp_int in tommath.h *) - let ptr = Tagged.shared_static_obj env Tagged.BigInt StaticBytes.[ - I32 size; (* used *) - I32 size; (* size; relying on Heap.word_size == size_of(mp_digit) *) - I32 sign; - I32 0l; (* dp; this will be patched in BigInt::mp_int_ptr in the RTS when used *) - i32s limbs - - ] in - ptr + Tagged.obj env Tagged.BigInt ([ + compile_unboxed_const size; (* used *) + compile_unboxed_const size; (* size; relying on Heap.word_size == size_of(mp_digit) *) + compile_unboxed_const sign; + compile_unboxed_const 0l; (* dp; this will be patched in BigInt::mp_int_ptr in the RTS when used *) + ] @ limbs) let assert_nonneg env = Func.share_code1 env "assert_nonneg" ("n", I32Type) [I32Type] (fun env get_n -> @@ -4819,7 +4816,7 @@ module Cycles = struct let (set_val, get_val) = new_local env "cycles" in set_val ^^ get_val ^^ - compile_unboxed_const (BigNum.vanilla_lit env (Big_int.power_int_positive_int 2 128)) ^^ + BigNum.lit env (Big_int.power_int_positive_int 2 128) ^^ BigNum.compile_relop env Lt ^^ E.else_trap_with env "cycles out of bounds" ^^ @@ -7567,7 +7564,7 @@ module StackRep = struct | Const.Lit (Const.Bool b) -> Bool.lit b | Const.Lit (Const.Blob t) -> Blob.lit env t | Const.Lit (Const.Null) -> Opt.null_lit env - | Const.Lit (Const.BigInt n) -> compile_unboxed_const (BigNum.vanilla_lit env n) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.BigInt n) -> BigNum.lit env n | Const.Lit (Const.Word32 n) -> BoxedSmallWord.lit env n | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n | Const.Lit (Const.Float64 f) -> compile_unboxed_const (Float.vanilla_lit env f) (* TODO: Redesign for heap allocations *) From d0e43084a8b51672b05de79e58e9f43199494aa7 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 14:57:07 +0200 Subject: [PATCH 016/260] Place static Float64 in dynamic heap --- src/codegen/compile.ml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index e4ee0f93c3e..a395535511b 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -52,7 +52,7 @@ module StaticBytes = struct type t_ = | I32 of int32 - | I64 of int64 + (* | I64 of int64 *) | Seq of t | Bytes of string @@ -62,7 +62,7 @@ module StaticBytes = struct let rec add : Buffer.t -> t_ -> unit = fun buf -> function | I32 i -> Buffer.add_int32_le buf i - | I64 i -> Buffer.add_int64_le buf i + (* | I64 i -> Buffer.add_int64_le buf i *) | Seq xs -> List.iter (add buf) xs | Bytes b -> Buffer.add_string buf b @@ -2474,9 +2474,9 @@ module Float = struct let compile_unboxed_const f = G.i (Const (nr (Wasm.Values.F64 f))) - let vanilla_lit env f = - Tagged.shared_static_obj env Tagged.Bits64 StaticBytes.[ - I64 (Wasm.F64.to_bits f) + let lit env f = + Tagged.obj env Tagged.Bits64 [ + compile_const_64 (Wasm.F64.to_bits f) ] let box env = Func.share_code1 env "box_f64" ("f", F64Type) [I32Type] (fun env get_f -> @@ -7567,7 +7567,7 @@ module StackRep = struct | Const.Lit (Const.BigInt n) -> BigNum.lit env n | Const.Lit (Const.Word32 n) -> BoxedSmallWord.lit env n | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n - | Const.Lit (Const.Float64 f) -> compile_unboxed_const (Float.vanilla_lit env f) (* TODO: Redesign for heap allocations *) + | Const.Lit (Const.Float64 f) -> Float.lit env f | Const.Opt c -> Opt.inject env (materialize_constant env c) | Const.Fun (get_fi, _) -> compile_unboxed_const (Closure.static_closure env (get_fi ())) (* TODO: Redesign for heap allocations *) | Const.Message fi -> assert false From db6f1cba6953e10acd09643032d749cb445a6bbc Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 15:00:15 +0200 Subject: [PATCH 017/260] Place static closure in dynamic heap --- src/codegen/compile.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index a395535511b..784feec5fb4 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2078,10 +2078,10 @@ module Closure = struct G.i (CallIndirect (nr ty)) ^^ FakeMultiVal.load env (Lib.List.make n_res I32Type) - let static_closure env fi : int32 = - Tagged.shared_static_obj env Tagged.Closure StaticBytes.[ - I32 (E.add_fun_ptr env fi); - I32 0l + let alloc env fi = + Tagged.obj env Tagged.Closure [ + compile_unboxed_const (E.add_fun_ptr env fi); + compile_unboxed_const 0l ] end (* Closure *) @@ -7569,7 +7569,7 @@ module StackRep = struct | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n | Const.Lit (Const.Float64 f) -> Float.lit env f | Const.Opt c -> Opt.inject env (materialize_constant env c) - | Const.Fun (get_fi, _) -> compile_unboxed_const (Closure.static_closure env (get_fi ())) (* TODO: Redesign for heap allocations *) + | Const.Fun (get_fi, _) -> Closure.alloc env (get_fi ()) | Const.Message fi -> assert false | Const.Unit -> compile_unboxed_const Tuple.unit_vanilla_lit (* TODO: Redesign for heap allocations *) | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env c) From 09c066398ce13484eef39316ffabd09c678a6f80 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 15:01:53 +0200 Subject: [PATCH 018/260] Code refactoring --- src/codegen/compile.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 784feec5fb4..83b62fab09d 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7571,7 +7571,7 @@ module StackRep = struct | Const.Opt c -> Opt.inject env (materialize_constant env c) | Const.Fun (get_fi, _) -> Closure.alloc env (get_fi ()) | Const.Message fi -> assert false - | Const.Unit -> compile_unboxed_const Tuple.unit_vanilla_lit (* TODO: Redesign for heap allocations *) + | Const.Unit -> Tuple.compile_unit | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env c) | Const.Lit (Const.Vanilla n) -> assert false From 8f92638ceed01ce80cd59cfbaf3576a3a4aa95f7 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 15:03:07 +0200 Subject: [PATCH 019/260] Code refactoring --- src/codegen/compile.ml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 83b62fab09d..fb0649423a2 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7573,10 +7573,7 @@ module StackRep = struct | Const.Message fi -> assert false | Const.Unit -> Tuple.compile_unit | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env c) - - | Const.Lit (Const.Vanilla n) -> assert false - | Const.Array cs -> assert false - | Const.Obj l -> assert false + | _ -> assert false let adjust env (sr_in : t) sr_out = if eq sr_in sr_out From b7a43804b9a77e6c6890536907d5577d591f7cbd Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 15:26:27 +0200 Subject: [PATCH 020/260] Simplify blob/text data segments --- src/codegen/compile.ml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index fb0649423a2..412cecb3b31 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -567,7 +567,7 @@ module E = struct let ptr = add_mutable_static_bytes env b in env.static_strings := StringEnv.add b ptr !(env.static_strings); ptr - + let object_pool_find (env: t) (key: string) : int32 option = StringEnv.find_opt key !(env.object_pool) @@ -1574,7 +1574,7 @@ module Tagged = struct | Bits32 (* Contains a 32 bit unsigned number *) | BigInt | Concat (* String concatenation, used by rts/text.c *) - | Null (* For opt. Static singleton! *) + | Null (* For opt. Singleton in persistent heap *) | OneWordFiller (* Only used by the RTS *) | FreeSpace (* Only used by the RTS *) | ArraySliceMinimum (* Used by the GC for incremental array marking *) @@ -3626,14 +3626,11 @@ module Blob = struct BigNum.from_word32 env ) - let static_lit env s = - Tagged.shared_static_obj env Tagged.Blob StaticBytes.[ - I32 (Int32.of_int (String.length s)); - Bytes s; - ] + let static_data env s = + compile_unboxed_const (Int32.add ptr_unskew (E.add_static env StaticBytes.[Bytes s])) let lit_ptr_len env s = - compile_unboxed_const (Int32.add ptr_unskew (E.add_static env StaticBytes.[Bytes s])) ^^ + static_data env s ^^ compile_unboxed_const (Int32.of_int (String.length s)) let alloc env = @@ -3652,7 +3649,7 @@ module Blob = struct let (set_new_blob, get_new_blob) = new_local env "new_blob" in compile_unboxed_const blob_length ^^ alloc env ^^ set_new_blob ^^ get_new_blob ^^ payload_ptr_unskewed env ^^ (* target address *) - compile_unboxed_const (static_lit env s) ^^ payload_ptr_unskewed env ^^ (* source address *) + static_data env s ^^ (* source address *) compile_unboxed_const blob_length ^^ (* copy length *) Heap.memcpy env ^^ get_new_blob From ec9a29bf8bc63109ddc1070857a1fc1faf5e410b Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 11 Aug 2023 16:05:56 +0200 Subject: [PATCH 021/260] Simplify code --- src/codegen/compile.ml | 66 +++--------------------------------------- 1 file changed, 4 insertions(+), 62 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 412cecb3b31..ad5dfe72bb3 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -279,13 +279,6 @@ module E = struct named_imports : int32 NameEnv.t ref; built_in_funcs : lazy_function NameEnv.t ref; static_strings : int32 StringEnv.t ref; - (* Pool for shared static objects. Their lookup needs to be specifically - handled by using the tag and the payload without the forwarding pointer. - This is because the forwarding pointer depends on the allocation adddress. - The lookup is different to `static_string` that has no such - allocation-dependent content and can thus be immediately looked up by - the string value. *) - object_pool : int32 StringEnv.t ref; end_of_static_memory : int32 ref; (* End of statically allocated memory *) static_memory : (int32 * string) list ref; (* Content of static memory *) static_memory_frozen : bool ref; @@ -334,7 +327,6 @@ module E = struct named_imports = ref NameEnv.empty; built_in_funcs = ref NameEnv.empty; static_strings = ref StringEnv.empty; - object_pool = ref StringEnv.empty; end_of_static_memory = ref dyn_mem; static_memory = ref []; static_memory_frozen = ref false; @@ -535,10 +527,6 @@ module E = struct env.end_of_static_memory := Int32.add ptr aligned; ptr - let write_static_memory (env : t) ptr data = - env.static_memory := !(env.static_memory) @ [ (ptr, data) ]; - () - let add_mutable_static_bytes (env : t) data : int32 = let ptr = reserve_static_memory env (Int32.of_int (String.length data)) in env.static_memory := !(env.static_memory) @ [ (ptr, data) ]; @@ -568,13 +556,6 @@ module E = struct env.static_strings := StringEnv.add b ptr !(env.static_strings); ptr - let object_pool_find (env: t) (key: string) : int32 option = - StringEnv.find_opt key !(env.object_pool) - - let object_pool_add (env: t) (key: string) (ptr : int32) : unit = - env.object_pool := StringEnv.add key ptr !(env.object_pool); - () - let add_static_unskewed (env : t) (data : StaticBytes.t) : int32 = Int32.add (add_static env data) ptr_unskew @@ -676,17 +657,6 @@ let compile_eq64_const i = compile_const_64 i ^^ G.i (Compare (Wasm.Values.I64 I64Op.Eq)) -(* more random utilities *) - -let bytes_of_int32 (i : int32) : string = - let b = Buffer.create 4 in - let i = Int32.to_int i in - Buffer.add_char b (Char.chr (i land 0xff)); - Buffer.add_char b (Char.chr ((i lsr 8) land 0xff)); - Buffer.add_char b (Char.chr ((i lsr 16) land 0xff)); - Buffer.add_char b (Char.chr ((i lsr 24) land 0xff)); - Buffer.contents b - (* A common variant of todo *) let todo_trap env fn se = todo fn se (E.trap_with env ("TODO: " ^ fn)) @@ -1839,34 +1809,6 @@ module Tagged = struct get_object ^^ allocation_barrier env - let new_static_obj env tag payload = - let payload = StaticBytes.as_bytes payload in - let header_size = Int32.(mul Heap.word_size (header_size env)) in - let size = Int32.(add header_size (Int32.of_int (String.length payload))) in - let unskewed_ptr = E.reserve_static_memory env size in - let skewed_ptr = Int32.(add unskewed_ptr ptr_skew) in - let tag = bytes_of_int32 (int_of_tag tag) in - let forward = bytes_of_int32 skewed_ptr in (* forwarding pointer *) - (if !Flags.gc_strategy = Flags.Incremental then - let incremental_gc_data = tag ^ forward ^ payload in - E.write_static_memory env unskewed_ptr incremental_gc_data - else - let non_incremental_gc_data = tag ^ payload in - E.write_static_memory env unskewed_ptr non_incremental_gc_data - ); - skewed_ptr - - let shared_static_obj env tag payload = - let tag_word = bytes_of_int32 (int_of_tag tag) in - let payload_bytes = StaticBytes.as_bytes payload in - let key = tag_word ^ payload_bytes in - match E.object_pool_find env key with - | Some ptr -> ptr (* no forwarding pointer dereferencing needed as static objects do not move *) - | None -> - let ptr = new_static_obj env tag payload in - E.object_pool_add env key ptr; - ptr - end (* Tagged *) module MutBox = struct @@ -6019,11 +5961,11 @@ module MakeSerialization (Strm : Stream) = struct (* This value is returned by deserialize_go if deserialization fails in a way that should be recoverable by opt parsing. - By virtue of being a deduped static value, it can be detected by pointer - comparison. + It is an (invalid) sentinel pointer value (in skewed format) and can be used for pointer comparison. + It will be never placed on the heap and must not be dereferenced. + If unskewed, it refers to the unallocated last Wasm memory page. *) - let coercion_error_value env : int32 = - Tagged.shared_static_obj env Tagged.CoercionFailure [] + let coercion_error_value env = 0xffff_fffdl (* See Note [Candid subtype checks] *) let with_rel_buf_opt env extended get_typtbl_size1 f = From 5c1d5aaeaf8bc319bd7b7ac51e6e80e83d92a800 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Mon, 14 Aug 2023 09:14:59 +0200 Subject: [PATCH 022/260] Fix typo --- rts/motoko-rts/src/gc/incremental.rs | 14 +++++++------- rts/motoko-rts/src/gc/incremental/barriers.rs | 8 ++++---- rts/motoko-rts/src/persistence.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 2a016d4aec3..344ff51cd21 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -11,7 +11,7 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ memory::Memory, - persistence::{get_incremenmtal_gc_state, initialize_memory, HEAP_START}, + persistence::{get_incremental_gc_state, initialize_memory, HEAP_START}, types::*, visitor::visit_pointer_fields, }; @@ -43,12 +43,12 @@ pub mod time; unsafe fn initialize_incremental_gc(mem: &mut M) { println!(100, "INITIALIZE INCREMENTAL GC"); initialize_memory(mem); - IncrementalGC::::initialize(mem, get_incremenmtal_gc_state(), HEAP_START); + IncrementalGC::::initialize(mem, get_incremental_gc_state(), HEAP_START); } #[ic_mem_fn(ic_only)] unsafe fn schedule_incremental_gc(mem: &mut M) { - let state = get_incremenmtal_gc_state(); + let state = get_incremental_gc_state(); assert!(state.phase != Phase::Stop); let running = state.phase != Phase::Pause; if running || should_start() { @@ -59,7 +59,7 @@ unsafe fn schedule_incremental_gc(mem: &mut M) { #[ic_mem_fn(ic_only)] unsafe fn incremental_gc(mem: &mut M) { use self::roots::root_set; - let state = get_incremenmtal_gc_state(); + let state = get_incremental_gc_state(); if state.phase == Phase::Pause { record_gc_start::(); } @@ -392,12 +392,12 @@ unsafe fn count_allocation(state: &mut State) { /// the compiler must not schedule the GC during stabilization anyway. #[no_mangle] pub unsafe extern "C" fn stop_gc_on_upgrade() { - get_incremenmtal_gc_state().phase = Phase::Stop; + get_incremental_gc_state().phase = Phase::Stop; } pub unsafe fn get_partitioned_heap() -> &'static mut PartitionedHeap { - debug_assert!(get_incremenmtal_gc_state() + debug_assert!(get_incremental_gc_state() .partitioned_heap .is_initialized()); - &mut get_incremenmtal_gc_state().partitioned_heap + &mut get_incremental_gc_state().partitioned_heap } diff --git a/rts/motoko-rts/src/gc/incremental/barriers.rs b/rts/motoko-rts/src/gc/incremental/barriers.rs index ca8f351bbce..ce24ae2cdec 100644 --- a/rts/motoko-rts/src/gc/incremental/barriers.rs +++ b/rts/motoko-rts/src/gc/incremental/barriers.rs @@ -6,7 +6,7 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ memory::Memory, - persistence::get_incremenmtal_gc_state, + persistence::get_incremental_gc_state, types::{is_skewed, Value}, }; @@ -14,7 +14,7 @@ use super::{count_allocation, post_allocation_barrier, pre_write_barrier, Phase} #[no_mangle] pub unsafe extern "C" fn running_gc() -> bool { - get_incremenmtal_gc_state().phase != Phase::Pause + get_incremental_gc_state().phase != Phase::Pause } /// Write a potential pointer value with a pre-update barrier and resolving pointer forwarding. @@ -30,7 +30,7 @@ pub unsafe fn write_with_barrier(mem: &mut M, location: *mut Value, v debug_assert!(!is_skewed(location as u32)); debug_assert_ne!(location, core::ptr::null_mut()); - let state = get_incremenmtal_gc_state(); + let state = get_incremental_gc_state(); pre_write_barrier(mem, state, *location); *location = value.forward_if_possible(); } @@ -47,7 +47,7 @@ pub unsafe fn write_with_barrier(mem: &mut M, location: *mut Value, v /// * Keep track of concurrent allocations to adjust the GC increment time limit. #[no_mangle] pub unsafe extern "C" fn allocation_barrier(new_object: Value) -> Value { - let state = get_incremenmtal_gc_state(); + let state = get_incremental_gc_state(); if state.phase != Phase::Pause { post_allocation_barrier(state, new_object); count_allocation(state); diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index cebd977d7a4..b6aac045f35 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -146,7 +146,7 @@ pub(crate) unsafe fn null_singleton_location() -> *mut Value { } // GC root pointer required for GC marking and updating. -pub(crate) unsafe fn get_incremenmtal_gc_state() -> &'static mut State { +pub(crate) unsafe fn get_incremental_gc_state() -> &'static mut State { let metadata = PersistentMetadata::get(); &mut (*metadata).incremental_gc_state } From ebba6fb85fa2e6eb22d4eef4956a8e2d62258ec6 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Mon, 14 Aug 2023 09:58:51 +0200 Subject: [PATCH 023/260] Adjust RTS unit testing --- rts/motoko-rts-tests/src/gc.rs | 31 ++++++++------ .../src/gc/incremental/roots.rs | 11 +++-- rts/motoko-rts/src/gc/incremental.rs | 40 ++++++++++++++----- rts/motoko-rts/src/gc/incremental/barriers.rs | 5 ++- .../src/gc/incremental/mark_bitmap.rs | 5 ++- .../src/gc/incremental/mark_stack.rs | 1 + .../src/gc/incremental/partitioned_heap.rs | 37 +++++++++++++++++ .../gc/incremental/phases/mark_increment.rs | 2 + rts/motoko-rts/src/gc/incremental/roots.rs | 13 +++--- rts/motoko-rts/src/lib.rs | 1 + rts/motoko-rts/src/persistence.rs | 2 + 11 files changed, 113 insertions(+), 35 deletions(-) diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs index faacd4301c8..82485952eff 100644 --- a/rts/motoko-rts-tests/src/gc.rs +++ b/rts/motoko-rts-tests/src/gc.rs @@ -168,9 +168,11 @@ fn initialize_gc(_heap: &mut MotokoHeap) {} #[incremental_gc] fn initialize_gc(heap: &mut MotokoHeap) { - use motoko_rts::gc::incremental::{get_partitioned_heap, IncrementalGC}; + use motoko_rts::gc::incremental::{ + get_incremental_gc_state, get_partitioned_heap, IncrementalGC, + }; unsafe { - IncrementalGC::initialize(heap, heap.heap_base_address()); + IncrementalGC::initialize(heap, get_incremental_gc_state(), heap.heap_base_address()); let allocation_size = heap.heap_ptr_address() - heap.heap_base_address(); // Synchronize the partitioned heap with one big combined allocation by starting from the base pointer as the heap pointer. @@ -188,11 +190,13 @@ fn reset_gc() {} #[incremental_gc] fn reset_gc() { use crate::memory::TestMemory; - use motoko_rts::gc::incremental::{partitioned_heap::PARTITION_SIZE, IncrementalGC}; + use motoko_rts::gc::incremental::{ + get_incremental_gc_state, partitioned_heap::PARTITION_SIZE, IncrementalGC, + }; let mut memory = TestMemory::new(Words(PARTITION_SIZE as u32)); unsafe { - IncrementalGC::initialize(&mut memory, 0); + IncrementalGC::initialize(&mut memory, get_incremental_gc_state(), 0); } } @@ -534,19 +538,22 @@ impl GC { #[incremental_gc] fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool { - let static_roots = Value::from_ptr(heap.static_root_array_address()); - let continuation_table_ptr_address = heap.continuation_table_ptr_address() as *mut Value; + let static_root = heap.static_root_array_address() as *mut Value; + let continuation_table_location = heap.continuation_table_ptr_address() as *mut Value; + let unused_root = &mut Value::from_scalar(0) as *mut Value; match self { GC::Incremental => unsafe { - use motoko_rts::gc::incremental::{incremental_gc_state, IncrementalGC}; + use motoko_rts::gc::incremental::{get_incremental_gc_state, IncrementalGC}; const INCREMENTS_UNTIL_COMPLETION: usize = 16; for _ in 0..INCREMENTS_UNTIL_COMPLETION { - let roots = motoko_rts::gc::incremental::roots::Roots { - static_roots, - continuation_table_location: continuation_table_ptr_address, - }; - IncrementalGC::instance(heap, incremental_gc_state()) + let roots = [ + static_root, + continuation_table_location, + unused_root, + unused_root, + ]; + IncrementalGC::instance(heap, get_incremental_gc_state()) .empty_call_stack_increment(roots); } false diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs index ddcfd3e03bd..9f34b110454 100644 --- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs +++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs @@ -65,13 +65,16 @@ unsafe fn check_visit_continuation_table(heap: &MotokoHeap, continuation_ids: &[ } unsafe fn get_roots(heap: &MotokoHeap) -> Roots { - let static_roots = Value::from_ptr(heap.static_root_array_address()); + let static_root = heap.static_root_array_address() as *mut Value; let continuation_table_location = heap.continuation_table_ptr_address() as *mut Value; + let unused_root = &mut Value::from_scalar(0) as *mut Value; assert_ne!(continuation_table_location, null_mut()); - Roots { - static_roots, + [ + static_root, continuation_table_location, - } + unused_root, + unused_root, + ] } fn object_id(heap: &MotokoHeap, address: usize) -> u32 { diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 344ff51cd21..1d80b03fcb1 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -9,12 +9,7 @@ use motoko_rts_macros::ic_mem_fn; -use crate::{ - memory::Memory, - persistence::{get_incremental_gc_state, initialize_memory, HEAP_START}, - types::*, - visitor::visit_pointer_fields, -}; +use crate::{memory::Memory, types::*, visitor::visit_pointer_fields}; use self::{ partitioned_heap::{PartitionedHeap, PartitionedHeapIterator}, @@ -41,7 +36,8 @@ pub mod time; #[ic_mem_fn(ic_only)] unsafe fn initialize_incremental_gc(mem: &mut M) { - println!(100, "INITIALIZE INCREMENTAL GC"); + use crate::persistence::{initialize_memory, HEAP_START}; + initialize_memory(mem); IncrementalGC::::initialize(mem, get_incremental_gc_state(), HEAP_START); } @@ -104,6 +100,7 @@ unsafe fn record_gc_start() { #[cfg(feature = "ic")] unsafe fn record_gc_stop() { use crate::memory::ic::{self, partitioned_memory}; + use crate::persistence::HEAP_START; let heap_size = partitioned_memory::get_heap_size(); let static_size = Bytes(HEAP_START as u32); @@ -134,6 +131,7 @@ const INCREMENT_ALLOCATION_FACTOR: usize = 10; // Additional time factor per con // Performance note: Storing the phase-specific state in the enum would be nicer but it is much slower. #[derive(PartialEq)] +#[repr(C)] enum Phase { Pause, // Inactive, waiting for the next GC run. Mark, // Incremental marking. @@ -143,6 +141,8 @@ enum Phase { } /// GC state retained over multiple GC increments. +/// Use a long-term representation by relying on C layout. +#[repr(C)] pub struct State { phase: Phase, partitioned_heap: PartitionedHeap, @@ -188,7 +188,6 @@ impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { /// * The mark phase can only be started on an empty call stack. /// * The update phase can only be completed on an empty call stack. pub unsafe fn empty_call_stack_increment(&mut self, roots: Roots) { - println!(100, "GC INCREMENT"); assert!(self.state.phase != Phase::Stop); if self.pausing() { self.start_marking(roots); @@ -396,8 +395,27 @@ pub unsafe extern "C" fn stop_gc_on_upgrade() { } pub unsafe fn get_partitioned_heap() -> &'static mut PartitionedHeap { - debug_assert!(get_incremental_gc_state() - .partitioned_heap - .is_initialized()); + debug_assert!(get_incremental_gc_state().partitioned_heap.is_initialized()); &mut get_incremental_gc_state().partitioned_heap } + +#[cfg(feature = "ic")] +pub unsafe fn get_incremental_gc_state() -> &'static mut State { + crate::persistence::get_incremental_gc_state() +} + +// For RTS unit testing only. +#[cfg(not(feature = "ic"))] +static mut TEST_GC_STATE: State = State { + phase: Phase::Pause, + partitioned_heap: partitioned_heap::UNINITIALIZED_HEAP, + allocation_count: 0, + mark_state: None, + iterator_state: None, +}; + +// For RTS unit testing only. +#[cfg(not(feature = "ic"))] +pub unsafe fn get_incremental_gc_state() -> &'static mut State { + &mut TEST_GC_STATE +} diff --git a/rts/motoko-rts/src/gc/incremental/barriers.rs b/rts/motoko-rts/src/gc/incremental/barriers.rs index ce24ae2cdec..091c255892a 100644 --- a/rts/motoko-rts/src/gc/incremental/barriers.rs +++ b/rts/motoko-rts/src/gc/incremental/barriers.rs @@ -6,11 +6,12 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ memory::Memory, - persistence::get_incremental_gc_state, types::{is_skewed, Value}, }; -use super::{count_allocation, post_allocation_barrier, pre_write_barrier, Phase}; +use super::{ + count_allocation, get_incremental_gc_state, post_allocation_barrier, pre_write_barrier, Phase, +}; #[no_mangle] pub unsafe extern "C" fn running_gc() -> bool { diff --git a/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs b/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs index 8afc38e73d9..d6f5cde880f 100644 --- a/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs +++ b/rts/motoko-rts/src/gc/incremental/mark_bitmap.rs @@ -31,11 +31,13 @@ const BITMAP_FRACTION: usize = (WORD_SIZE * u8::BITS) as usize; pub const BITMAP_SIZE: usize = PARTITION_SIZE / BITMAP_FRACTION; /// Partition-associated mark bitmap. +/// Use a long-term representation by relying on C layout. +#[repr(C)] pub struct MarkBitmap { pointer: *mut u8, } -const DEFAULT_MARK_BITMAP: MarkBitmap = MarkBitmap { +pub(crate) const DEFAULT_MARK_BITMAP: MarkBitmap = MarkBitmap { pointer: null_mut(), }; @@ -97,6 +99,7 @@ impl MarkBitmap { /// Adopted and adjusted from `mark_compact/bitmap.rs`. /// The iterator separates advancing `next()` from inspection `current_marked_offset()` /// to better support the incremental evacuation and update GC increments. +#[repr(C)] pub struct BitmapIterator { /// Start address of the mark bitmap. Must be 64-bit-aligned. bitmap_pointer: *mut u8, diff --git a/rts/motoko-rts/src/gc/incremental/mark_stack.rs b/rts/motoko-rts/src/gc/incremental/mark_stack.rs index b5d249c7245..8e192e93598 100644 --- a/rts/motoko-rts/src/gc/incremental/mark_stack.rs +++ b/rts/motoko-rts/src/gc/incremental/mark_stack.rs @@ -31,6 +31,7 @@ use core::ptr::null_mut; use crate::memory::{alloc_blob, Memory}; use crate::types::{size_of, Blob, Value}; +#[repr(C)] pub struct MarkStack { last: *mut StackTable, top: usize, // index of next free entry in the last stack table diff --git a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs index 4984dd2dc7d..2a48e1f5e6f 100644 --- a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs +++ b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs @@ -73,6 +73,8 @@ const MAX_PARTITIONS: usize = (WASM_MEMORY_BYTE_SIZE.0 / PARTITION_SIZE as u64) pub const SURVIVAL_RATE_THRESHOLD: f64 = 0.85; /// Heap partition of size `PARTITION_SIZE`. +/// Use a long-term representation by relying on C layout. +#[repr(C)] pub struct Partition { index: usize, // Index of the partition `0..MAX_PARTITIONS`. free: bool, // Denotes a free partition (which may still contain static space). @@ -86,6 +88,22 @@ pub struct Partition { update: bool, // Specifies whether the pointers in the partition have to be updated. } +/// For RTS unit testing only. +/// Optimization: Avoiding `Option` or `Lazy`. +#[cfg(not(feature = "ic"))] +const UNINITIALIZED_PARTITION: Partition = Partition { + index: usize::MAX, + free: false, + large_content: false, + marked_size: 0, + static_size: 0, + dynamic_size: 0, + bitmap: super::mark_bitmap::DEFAULT_MARK_BITMAP, + temporary: false, + evacuate: false, + update: false, +}; + impl Partition { pub fn get_index(&self) -> usize { self.index @@ -222,6 +240,8 @@ impl Partition { /// Iterates over all partitions and their contained marked objects, by skipping /// free partitions, the subsequent partitions of large objects, and unmarked objects. +/// Use a long-term representation by relying on C layout. +#[repr(C)] pub struct PartitionedHeapIterator { partition_index: usize, bitmap_iterator: Option, @@ -330,6 +350,8 @@ impl PartitionedHeapIterator { } /// Partitioned heap used by the incremental GC. +/// Use a long-term representation by relying on C layout. +#[repr(C)] pub struct PartitionedHeap { partitions: [Partition; MAX_PARTITIONS], heap_base: usize, @@ -342,6 +364,21 @@ pub struct PartitionedHeap { precomputed_heap_size: usize, // Occupied heap size, excluding the dynamic heap in the allocation partition. } +/// For RTS unit testing only. +/// Optimization: Avoiding `Option` or `LazyCell`. +#[cfg(not(feature = "ic"))] +pub const UNINITIALIZED_HEAP: PartitionedHeap = PartitionedHeap { + partitions: [UNINITIALIZED_PARTITION; MAX_PARTITIONS], + heap_base: 0, + allocation_index: 0, + free_partitions: 0, + evacuating: false, + reclaimed: 0, + bitmap_allocation_pointer: 0, + gc_running: false, + precomputed_heap_size: 0, +}; + impl PartitionedHeap { pub unsafe fn new(mem: &mut M, heap_base: usize) -> PartitionedHeap { let allocation_index = heap_base / PARTITION_SIZE; diff --git a/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs b/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs index 298a4562711..4580fb32ebe 100644 --- a/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs +++ b/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs @@ -12,6 +12,8 @@ use crate::{ visitor::visit_pointer_fields, }; +/// Use a long-term representation by relying on C layout. +#[repr(C)] pub struct MarkState { mark_stack: MarkStack, complete: bool, diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs index d1fd77f7a51..4de6a99d73f 100644 --- a/rts/motoko-rts/src/gc/incremental/roots.rs +++ b/rts/motoko-rts/src/gc/incremental/roots.rs @@ -1,12 +1,11 @@ use motoko_rts_macros::ic_mem_fn; -use crate::{memory::Memory, types::Value, visitor::pointer_to_dynamic_heap}; - -use super::barriers::write_with_barrier; +use crate::{types::Value, visitor::pointer_to_dynamic_heap}; /// Root referring to all canister variables. /// This root 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); /// GC root set. @@ -40,17 +39,21 @@ pub unsafe fn visit_roots( } } +#[cfg(feature = "ic")] unsafe fn static_root_location() -> *mut Value { &mut STATIC_ROOT as *mut Value } -#[ic_mem_fn] -pub unsafe fn set_static_root(mem: &mut M, value: Value) { +#[ic_mem_fn(ic_only)] +pub unsafe fn set_static_root(mem: &mut M, value: Value) { + use super::barriers::write_with_barrier; + let location = &mut STATIC_ROOT as *mut Value; write_with_barrier(mem, location, value); } #[no_mangle] +#[cfg(feature = "ic")] pub unsafe extern "C" fn get_static_root() -> Value { assert!(STATIC_ROOT.is_ptr()); STATIC_ROOT diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs index 2a2adf34d79..af5c503f6f9 100644 --- a/rts/motoko-rts/src/lib.rs +++ b/rts/motoko-rts/src/lib.rs @@ -32,6 +32,7 @@ pub mod leb128; mod mem_utils; pub mod memory; #[incremental_gc] +#[cfg(feature = "ic")] pub mod persistence; pub mod principal_id; mod static_checks; diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index b6aac045f35..06af1dc9126 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -86,6 +86,7 @@ impl PersistentMetadata { /// Initialize fresh peristent memory after the canister installation or /// reuse the persistent memory on a canister upgrade. +#[cfg(feature = "ic")] pub unsafe fn initialize_memory(mem: &mut M) { mem.grow_memory(HEAP_START as u64); let metadata = PersistentMetadata::get(); @@ -146,6 +147,7 @@ pub(crate) unsafe fn null_singleton_location() -> *mut Value { } // GC root pointer required for GC marking and updating. +#[cfg(feature = "ic")] pub(crate) unsafe fn get_incremental_gc_state() -> &'static mut State { let metadata = PersistentMetadata::get(); &mut (*metadata).incremental_gc_state From a1ba8032311f471fcd3e1cd7e6a23c6b8ecc838d Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Mon, 14 Aug 2023 10:04:30 +0200 Subject: [PATCH 024/260] Guard against static heap pointers --- rts/motoko-rts/src/visitor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rts/motoko-rts/src/visitor.rs b/rts/motoko-rts/src/visitor.rs index 7c05492e269..47108bc6935 100644 --- a/rts/motoko-rts/src/visitor.rs +++ b/rts/motoko-rts/src/visitor.rs @@ -129,5 +129,7 @@ pub unsafe fn visit_pointer_fields( pub unsafe fn pointer_to_dynamic_heap(field_addr: *mut Value, heap_base: usize) -> bool { // NB. pattern matching on `field_addr.get()` generates inefficient code let field_value = (*field_addr).get_raw(); - is_ptr(field_value) && unskew(field_value as usize) >= heap_base + // NOTE: Pointers to static space is no longer allowed. + debug_assert!(!is_ptr(field_value) || unskew(field_value as usize) >= heap_base); + is_ptr(field_value) } From 39ecec0ab8084774bc2b9f4b3d5dcf914bee9c6d Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Mon, 14 Aug 2023 16:06:04 +0200 Subject: [PATCH 025/260] Remove static heap in GC unit test --- rts/motoko-rts-tests/src/gc.rs | 107 ++++++-- rts/motoko-rts-tests/src/gc/heap.rs | 256 +++++++++--------- .../src/gc/incremental/roots.rs | 4 +- rts/motoko-rts/src/debug.rs | 8 +- 4 files changed, 221 insertions(+), 154 deletions(-) diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs index 82485952eff..d5931ebcc2d 100644 --- a/rts/motoko-rts-tests/src/gc.rs +++ b/rts/motoko-rts-tests/src/gc.rs @@ -141,7 +141,8 @@ fn test_gc( &**heap.heap(), heap.heap_base_offset(), heap.heap_ptr_offset(), - heap.continuation_table_ptr_offset(), + heap.static_root_array_variable_offset(), + heap.continuation_table_variable_offset(), ); for round in 0..3 { @@ -149,7 +150,8 @@ fn test_gc( let heap_base_offset = heap.heap_base_offset(); let heap_ptr_offset = heap.heap_ptr_offset(); - let continuation_table_ptr_offset = heap.continuation_table_ptr_offset(); + let static_array_variable_offset = heap.static_root_array_variable_offset(); + let continuation_table_variable_offset = heap.continuation_table_variable_offset(); check_dynamic_heap( check_all_reclaimed, // check for unreachable objects refs, @@ -158,7 +160,8 @@ fn test_gc( &**heap.heap(), heap_base_offset, heap_ptr_offset, - continuation_table_ptr_offset, + static_array_variable_offset, + continuation_table_variable_offset, ); } } @@ -217,7 +220,8 @@ fn check_dynamic_heap( heap: &[u8], heap_base_offset: usize, heap_ptr_offset: usize, - continuation_table_ptr_offset: usize, + static_root_array_variable_offset: usize, + continuation_table_variable_offset: usize, ) { let incremental = cfg!(feature = "incremental_gc"); let objects_map: FxHashMap = objects @@ -231,8 +235,11 @@ fn check_dynamic_heap( // Maps objects to their addresses (not offsets!). Used when debugging duplicate objects. let mut seen: FxHashMap = Default::default(); - let continuation_table_addr = unskew_pointer(read_word(heap, continuation_table_ptr_offset)); - let continuation_table_offset = continuation_table_addr as usize - heap.as_ptr() as usize; + let static_root_array_address = unskew_pointer(read_word(heap, static_root_array_variable_offset)); + let static_root_array_offset = static_root_array_address as usize - heap.as_ptr() as usize; + + let continuation_table_address = unskew_pointer(read_word(heap, continuation_table_variable_offset)); + let continuation_table_offset = continuation_table_address as usize - heap.as_ptr() as usize; while offset < heap_ptr_offset { let object_offset = offset; @@ -240,6 +247,14 @@ fn check_dynamic_heap( // Address of the current object. Used for debugging. let address = offset as usize + heap.as_ptr() as usize; + if object_offset == static_root_array_offset { + check_static_root_array(object_offset, roots, heap); + offset += (size_of::() + Words(roots.len() as u32)) + .to_bytes() + .as_usize(); + continue; + } + if object_offset == continuation_table_offset { check_continuation_table(object_offset, continuation_table, heap); offset += (size_of::() + Words(continuation_table.len() as u32)) @@ -269,7 +284,10 @@ fn check_dynamic_heap( let is_forwarded = forward != make_pointer(address as u32); - if incremental && tag == TAG_BLOB { + if tag == TAG_MUTBOX { + // MutBoxes of static root array, will be scanned indirectly when checking the static root array. + offset += WORD_SIZE; + } else if incremental && tag == TAG_BLOB { assert!(!is_forwarded); // in-heap mark stack blobs let length = read_word(heap, offset); @@ -278,7 +296,7 @@ fn check_dynamic_heap( if incremental { assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN); } else { - assert_eq!(tag, TAG_ARRAY); + assert!(tag == TAG_ARRAY); } if is_forwarded { @@ -318,12 +336,11 @@ fn check_dynamic_heap( for field_idx in 1..n_fields { let field = read_word(heap, offset); offset += WORD_SIZE; + // Get index of the object pointed by the field let pointee_address = field.wrapping_add(1); // unskew - let pointee_offset = (pointee_address as usize) - (heap.as_ptr() as usize); - let pointee_idx_offset = - pointee_offset as usize + size_of::().to_bytes().as_usize(); // skip array header (incl. length) - let pointee_idx = get_scalar_value(read_word(heap, pointee_idx_offset)); + + let pointee_idx = read_object_id(pointee_address, heap); let expected_pointee_idx = object_expected_pointees[(field_idx - 1) as usize]; assert_eq!( @@ -418,6 +435,48 @@ fn compute_reachable_objects( closure } +fn check_static_root_array(mut offset: usize, roots: &[ObjectIdx], heap: &[u8]) { + let incremental = cfg!(feature = "incremental_gc"); + + let array_address = heap.as_ptr() as usize + offset; + assert_eq!(read_word(heap, offset), TAG_ARRAY); + offset += WORD_SIZE; + + if incremental { + assert_eq!(read_word(heap, offset), make_pointer(array_address as u32)); + offset += WORD_SIZE; + } + + assert_eq!(read_word(heap, offset), roots.len() as u32); + offset += WORD_SIZE; + + for obj in roots.iter() { + let mutbox_address = unskew_pointer(read_word(heap, offset)); + offset += WORD_SIZE; + + let object_address = unskew_pointer(read_mutbox_field(mutbox_address, heap)); + let idx = read_object_id(object_address, heap); + assert_eq!(idx, *obj); + } +} + +fn read_mutbox_field(mutbox_address: u32, heap: &[u8]) -> u32 { + let incremental = cfg!(feature = "incremental_gc"); + + let mut mutbox_offset = mutbox_address as usize - heap.as_ptr() as usize; + + let mutbox_tag = read_word(heap, mutbox_offset); + assert_eq!(mutbox_tag, TAG_MUTBOX); + mutbox_offset += WORD_SIZE; + + if incremental { + assert_eq!(read_word(heap, mutbox_offset), make_pointer(mutbox_address)); + mutbox_offset += WORD_SIZE; + } + + read_word(heap, mutbox_offset) +} + fn check_continuation_table(mut offset: usize, continuation_table: &[ObjectIdx], heap: &[u8]) { let incremental = cfg!(feature = "incremental_gc"); @@ -437,20 +496,28 @@ fn check_continuation_table(mut offset: usize, continuation_table: &[ObjectIdx], let ptr = unskew_pointer(read_word(heap, offset)); offset += WORD_SIZE; - // Skip object header for idx - let idx_address = ptr as usize + size_of::().to_bytes().as_usize(); - let idx = get_scalar_value(read_word(heap, idx_address - heap.as_ptr() as usize)); - + let idx = read_object_id(ptr, heap); assert_eq!(idx, *obj); } } +fn read_object_id(object_address: u32, heap: &[u8]) -> ObjectIdx { + let incremental = cfg!(feature = "incremental_gc"); + + let tag = read_word(heap, object_address as usize - heap.as_ptr() as usize); + assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN && incremental); + + // Skip object header for idx + let idx_address = object_address as usize + size_of::().to_bytes().as_usize(); + get_scalar_value(read_word(heap, idx_address - heap.as_ptr() as usize)) +} + impl GC { #[non_incremental_gc] fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool { let heap_base = heap.heap_base_address(); - let static_roots = Value::from_ptr(heap.static_root_array_address()); - let continuation_table_ptr_address = heap.continuation_table_ptr_address() as *mut Value; + let static_roots = Value::from_ptr(heap.static_root_array_variable_address()); + let continuation_table_ptr_address = heap.continuation_table_variable_address() as *mut Value; let heap_1 = heap.clone(); let heap_2 = heap.clone(); @@ -538,8 +605,8 @@ impl GC { #[incremental_gc] fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool { - let static_root = heap.static_root_array_address() as *mut Value; - let continuation_table_location = heap.continuation_table_ptr_address() as *mut Value; + let static_root = heap.static_root_array_variable_address() as *mut Value; + let continuation_table_location = heap.continuation_table_variable_address() as *mut Value; let unused_root = &mut Value::from_scalar(0) as *mut Value; match self { diff --git a/rts/motoko-rts-tests/src/gc/heap.rs b/rts/motoko-rts-tests/src/gc/heap.rs index b687d97045a..33417f62fd9 100644 --- a/rts/motoko-rts-tests/src/gc/heap.rs +++ b/rts/motoko-rts-tests/src/gc/heap.rs @@ -90,19 +90,24 @@ impl MotokoHeap { self.inner.borrow().heap_base_address() } - /// Get the address of the static root array - pub fn static_root_array_address(&self) -> usize { - self.inner.borrow().static_root_array_address() + /// Get the offset of the variable pointing to the static root array. + pub fn static_root_array_variable_offset(&self) -> usize { + self.inner.borrow().static_root_array_variable_offset } - /// Get the offset of the continuation table pointer - pub fn continuation_table_ptr_offset(&self) -> usize { - self.inner.borrow().continuation_table_ptr_offset + /// Get the address of the variable pointing to the static root array. + pub fn static_root_array_variable_address(&self) -> usize { + self.inner.borrow().static_root_array_variable_address() } - /// Get the address of the continuation table pointer - pub fn continuation_table_ptr_address(&self) -> usize { - self.inner.borrow().continuation_table_ptr_address() + /// Get the offset of the variable pointing to the continuation table. + pub fn continuation_table_variable_offset(&self) -> usize { + self.inner.borrow().continuation_table_variable_offset + } + + /// Get the address of the variable pointing to the continuation table. + pub fn continuation_table_variable_address(&self) -> usize { + self.inner.borrow().continuation_table_variable_address() } /// Get the heap as an array. Use `offset` values returned by the methods above to read. @@ -117,8 +122,8 @@ impl MotokoHeap { motoko_rts::debug::dump_heap( self.heap_base_address() as u32, self.heap_ptr_address() as u32, - Value::from_ptr(self.static_root_array_address()), - self.continuation_table_ptr_address() as *mut Value, + self.static_root_array_variable_address() as *mut Value, + self.continuation_table_variable_address() as *mut Value, ); } } @@ -138,14 +143,15 @@ struct MotokoHeapInner { /// Where the dynamic heap ends, i.e. the heap pointer heap_ptr_offset: usize, - /// Offset of the static root array: an array of pointers below `heap_base` - static_root_array_offset: usize, + /// Offset of the static root array. + /// + /// Reminder: This location is in static memory and points to an array in the dynamic heap. + static_root_array_variable_offset: usize, /// Offset of the continuation table pointer. /// - /// Reminder: this location is in static heap and will have pointer to an array in dynamic - /// heap. - continuation_table_ptr_offset: usize, + /// Reminder: This location is in static memory and points to an array in the dynamic heap. + continuation_table_variable_offset: usize, } impl MotokoHeapInner { @@ -184,14 +190,14 @@ impl MotokoHeapInner { self._heap_ptr_last = self.address_to_offset(address); } - /// Get static root array address in the process's address space - fn static_root_array_address(&self) -> usize { - self.offset_to_address(self.static_root_array_offset) + /// Get the address of the variable pointing to the static root array. + fn static_root_array_variable_address(&self) -> usize { + self.offset_to_address(self.static_root_array_variable_offset) } - /// Get the address of the continuation table pointer - fn continuation_table_ptr_address(&self) -> usize { - self.offset_to_address(self.continuation_table_ptr_offset) + /// Get the address of the variable pointing to the continuation table. + fn continuation_table_variable_address(&self) -> usize { + self.offset_to_address(self.continuation_table_variable_offset) } fn new( @@ -210,31 +216,30 @@ impl MotokoHeapInner { ); } - // Each object will have array header plus one word for id per object + one word for each reference. Static heap will - // have an array (header + length) with one element, one MutBox for each root. +1 for - // continuation table pointer. - let static_heap_size_bytes = (size_of::().as_usize() - + roots.len() - + (roots.len() * size_of::().as_usize()) - + 1) + // Two pointers, one to the static root array, and the other to the continuation table. + let root_pointers_size_bytes = 2 * WORD_SIZE; + + // Each object will have array header plus one word for id per object + one word for each reference. + // The static root is an array (header + length) with one element, one MutBox for each static variable. + let static_root_set_size_bytes = (size_of::().as_usize() + roots.len() + + roots.len() * size_of::().as_usize()) * WORD_SIZE; - let dynamic_heap_size_without_continuation_table_bytes = { + let continuation_table_size_byes = (size_of::() + Words(continuation_table.len() as u32)).to_bytes().as_usize(); + + let dynamic_objects_size_bytes = { let object_headers_words = map.len() * (size_of::().as_usize() + 1); let references_words = map.iter().map(|(_, refs)| refs.len()).sum::(); (object_headers_words + references_words) * WORD_SIZE }; - let dynamic_heap_size_bytes = dynamic_heap_size_without_continuation_table_bytes - + (size_of::() + Words(continuation_table.len() as u32)) - .to_bytes() - .as_usize(); + let dynamic_heap_size_bytes = dynamic_objects_size_bytes + static_root_set_size_bytes + continuation_table_size_byes; - let total_heap_size_bytes = static_heap_size_bytes + dynamic_heap_size_bytes; + let total_heap_size_bytes = root_pointers_size_bytes + dynamic_heap_size_bytes; let heap_size = heap_size_for_gc( gc, - static_heap_size_bytes, + root_pointers_size_bytes, dynamic_heap_size_bytes, map.len(), ); @@ -244,33 +249,35 @@ impl MotokoHeapInner { let mut heap = vec![0u8; heap_size + 28]; // Align the dynamic heap starts at a 32-byte multiple. - let realign = (32 - (heap.as_ptr() as usize + static_heap_size_bytes) % 32) % 32; + let realign = (32 - (heap.as_ptr() as usize + root_pointers_size_bytes) % 32) % 32; assert_eq!(realign % 4, 0); // Maps `ObjectIdx`s into their offsets in the heap - let object_addrs: FxHashMap = create_dynamic_heap( + let (static_root_array_address, continuation_table_address) = create_dynamic_heap( map, + roots, continuation_table, - &mut heap[static_heap_size_bytes + realign..heap_size + realign], + &mut heap[root_pointers_size_bytes + realign..heap_size + realign], ); - // Closure table pointer is the last word in static heap - let continuation_table_ptr_offset = static_heap_size_bytes - WORD_SIZE; - create_static_heap( - roots, - &object_addrs, - continuation_table_ptr_offset, - static_heap_size_bytes + dynamic_heap_size_without_continuation_table_bytes, - &mut heap[realign..static_heap_size_bytes + realign], + // Root pointers in static memory space. + let static_root_array_variable_offset = root_pointers_size_bytes - 2 * WORD_SIZE; + let continuation_table_variable_offset = root_pointers_size_bytes - WORD_SIZE; + create_static_memory( + static_root_array_variable_offset, + continuation_table_variable_offset, + static_root_array_address, + continuation_table_address, + &mut heap[realign..root_pointers_size_bytes + realign], ); MotokoHeapInner { heap: heap.into_boxed_slice(), - heap_base_offset: static_heap_size_bytes + realign, - _heap_ptr_last: static_heap_size_bytes + realign, + heap_base_offset: root_pointers_size_bytes + realign, + _heap_ptr_last: root_pointers_size_bytes + realign, heap_ptr_offset: total_heap_size_bytes + realign, - static_root_array_offset: realign, - continuation_table_ptr_offset: continuation_table_ptr_offset + realign, + static_root_array_variable_offset: static_root_array_variable_offset + realign, + continuation_table_variable_offset: continuation_table_variable_offset + realign, } } @@ -387,13 +394,13 @@ fn heap_size_for_gc( /// Given a heap description (as a map from objects to objects), and the dynamic part of the heap /// (as an array), initialize the dynamic heap with objects. /// -/// Returns a mapping from object indices (`ObjectIdx`) to their addresses (see module -/// documentation for "offset" and "address" definitions). +/// Returns a pair containing the address of the static root array and the address of the continuation table. fn create_dynamic_heap( refs: &[(ObjectIdx, Vec)], + static_roots: &[ObjectIdx], continuation_table: &[ObjectIdx], dynamic_heap: &mut [u8], -) -> FxHashMap { +) -> (u32, u32) { let incremental = cfg!(feature = "incremental_gc"); let heap_start = dynamic_heap.as_ptr() as usize; @@ -405,7 +412,7 @@ fn create_dynamic_heap( let mut heap_offset = 0; for (obj, refs) in refs { object_addrs.insert(*obj, heap_start + heap_offset); - + // Store object header let address = u32::try_from(heap_start + heap_offset).unwrap(); write_word(dynamic_heap, heap_offset, TAG_ARRAY); @@ -427,7 +434,7 @@ fn create_dynamic_heap( // Store object value (idx) write_word(dynamic_heap, heap_offset, make_scalar(*obj)); heap_offset += WORD_SIZE; - + // Leave space for the fields heap_offset += refs.len() * WORD_SIZE; } @@ -448,19 +455,64 @@ fn create_dynamic_heap( } } - // Add the continuation table + // Add the static root table let n_objects = refs.len(); // fields+1 for the scalar field (idx) let n_fields: usize = refs.iter().map(|(_, fields)| fields.len() + 1).sum(); - let continuation_table_offset = (size_of::() * n_objects as u32) + let root_section_offset = (size_of::() * n_objects as u32) .to_bytes() .as_usize() + n_fields * WORD_SIZE; + let mut heap_offset = root_section_offset; + let mut root_mutboxes = vec![]; { - let mut heap_offset = continuation_table_offset; + for root_id in static_roots { + let mutbox_address = u32::try_from(heap_start + heap_offset).unwrap(); + root_mutboxes.push(mutbox_address); + write_word(dynamic_heap, heap_offset, TAG_MUTBOX); + heap_offset += WORD_SIZE; + + if incremental { + write_word( + dynamic_heap, + heap_offset, + make_pointer(mutbox_address), + ); + heap_offset += WORD_SIZE; + } + + let root_ptr = *object_addrs.get(root_id).unwrap(); + write_word(dynamic_heap, heap_offset, make_pointer(root_ptr as u32)); + heap_offset += WORD_SIZE; + } + } + let static_root_array_address = u32::try_from(heap_start + heap_offset).unwrap(); + { + write_word(dynamic_heap, heap_offset, TAG_ARRAY); + heap_offset += WORD_SIZE; + + if incremental { + write_word( + dynamic_heap, + heap_offset, + make_pointer(static_root_array_address), + ); + heap_offset += WORD_SIZE; + } - let continuation_table_address = u32::try_from(heap_start + heap_offset).unwrap(); + assert_eq!(static_roots.len(), root_mutboxes.len()); + write_word(dynamic_heap, heap_offset, root_mutboxes.len() as u32); + heap_offset += WORD_SIZE; + + for mutbox_address in root_mutboxes { + write_word(dynamic_heap, heap_offset, make_pointer(mutbox_address)); + heap_offset += WORD_SIZE; + } + } + + let continuation_table_address = u32::try_from(heap_start + heap_offset).unwrap(); + { write_word(dynamic_heap, heap_offset, TAG_ARRAY); heap_offset += WORD_SIZE; @@ -483,80 +535,28 @@ fn create_dynamic_heap( } } - object_addrs + (static_root_array_address, continuation_table_address) } -/// Given a root set (`roots`, may contain duplicates), a mapping from object indices to addresses -/// (`object_addrs`), and the static part of the heap, initialize the static heap with the static -/// root array. -fn create_static_heap( - roots: &[ObjectIdx], - object_addrs: &FxHashMap, - continuation_table_ptr_offset: usize, - continuation_table_offset: usize, +/// Static memory part containing the root pointers. +fn create_static_memory( + static_root_array_variable_offset: usize, + continuation_table_variable_offset: usize, + static_root_array_address: u32, + continuation_table_address: u32, heap: &mut [u8], ) { - let incremental = cfg!(feature = "incremental_gc"); - let root_addresses: Vec = roots - .iter() - .map(|obj| *object_addrs.get(obj).unwrap()) - .collect(); - - // Create static root array. Each element of the array is a MutBox pointing to the actual - // root. - let array_addr = u32::try_from(heap.as_ptr() as usize).unwrap(); - let mut offset = 0; - write_word(heap, offset, TAG_ARRAY); - offset += WORD_SIZE; - - if incremental { - write_word(heap, offset, make_pointer(array_addr)); - offset += WORD_SIZE; - } - - write_word(heap, offset, u32::try_from(roots.len()).unwrap()); - offset += WORD_SIZE; - - // Current offset in the heap for the next static roots array element - let mut root_addr_offset = size_of::().to_bytes().as_usize(); - assert_eq!(offset, root_addr_offset); - - // Current offset in the heap for the MutBox of the next root - let mut mutbox_offset = (size_of::().as_usize() + roots.len()) * WORD_SIZE; - - for root_address in root_addresses { - // Add a MutBox for the object - let mutbox_addr = heap.as_ptr() as usize + mutbox_offset; - let mutbox_ptr = make_pointer(u32::try_from(mutbox_addr).unwrap()); - - offset = mutbox_offset; - write_word(heap, offset, TAG_MUTBOX); - offset += WORD_SIZE; - - if incremental { - write_word(heap, offset, mutbox_ptr); - offset += WORD_SIZE; - } - - write_word( - heap, - offset, - make_pointer(u32::try_from(root_address).unwrap()), - ); - offset += WORD_SIZE; - - write_word(heap, root_addr_offset, mutbox_ptr); - - root_addr_offset += WORD_SIZE; - mutbox_offset += size_of::().to_bytes().as_usize(); - assert_eq!(offset, mutbox_offset); - } + // Write static array pointer as the second last word in static memory + write_word( + heap, + static_root_array_variable_offset, + make_pointer(static_root_array_address), + ); - // Write continuation table pointer as the last word in static heap - let continuation_table_ptr = continuation_table_offset as u32 + heap.as_ptr() as u32; + // Write continuation table pointer as the last word in static memory write_word( heap, - continuation_table_ptr_offset, - make_pointer(continuation_table_ptr), + continuation_table_variable_offset, + make_pointer(continuation_table_address), ); } diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs index 9f34b110454..f8be0beaa4f 100644 --- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs +++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs @@ -65,8 +65,8 @@ unsafe fn check_visit_continuation_table(heap: &MotokoHeap, continuation_ids: &[ } unsafe fn get_roots(heap: &MotokoHeap) -> Roots { - let static_root = heap.static_root_array_address() as *mut Value; - let continuation_table_location = heap.continuation_table_ptr_address() as *mut Value; + let static_root = heap.static_root_array_variable_address() as *mut Value; + let continuation_table_location = heap.continuation_table_variable_address() as *mut Value; let unused_root = &mut Value::from_scalar(0) as *mut Value; assert_ne!(continuation_table_location, null_mut()); [ diff --git a/rts/motoko-rts/src/debug.rs b/rts/motoko-rts/src/debug.rs index e5007115ab0..4c36a959bea 100644 --- a/rts/motoko-rts/src/debug.rs +++ b/rts/motoko-rts/src/debug.rs @@ -23,11 +23,11 @@ pub unsafe extern "C" fn print_value(value: Value) { pub unsafe fn dump_heap( heap_base: u32, hp: u32, - static_roots: Value, - continuation_table_loc: *mut Value, + static_root_location: *mut Value, + continuation_table_location: *mut Value, ) { - print_continuation_table(continuation_table_loc); - print_static_roots(static_roots); + print_continuation_table(continuation_table_location); + print_static_roots(*static_root_location); print_heap(heap_base, hp); } From 9b2df7ea042cdecaec61f4b9e8ba8f46ca23b916 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Mon, 14 Aug 2023 16:17:07 +0200 Subject: [PATCH 026/260] Adjust GC root set unit test --- rts/motoko-rts-tests/src/gc.rs | 13 +++++---- rts/motoko-rts-tests/src/gc/heap.rs | 29 ++++++++++--------- .../src/gc/incremental/roots.rs | 13 ++++++--- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs index d5931ebcc2d..0e74c6da215 100644 --- a/rts/motoko-rts-tests/src/gc.rs +++ b/rts/motoko-rts-tests/src/gc.rs @@ -220,7 +220,7 @@ fn check_dynamic_heap( heap: &[u8], heap_base_offset: usize, heap_ptr_offset: usize, - static_root_array_variable_offset: usize, + static_root_array_variable_offset: usize, continuation_table_variable_offset: usize, ) { let incremental = cfg!(feature = "incremental_gc"); @@ -235,10 +235,12 @@ fn check_dynamic_heap( // Maps objects to their addresses (not offsets!). Used when debugging duplicate objects. let mut seen: FxHashMap = Default::default(); - let static_root_array_address = unskew_pointer(read_word(heap, static_root_array_variable_offset)); + let static_root_array_address = + unskew_pointer(read_word(heap, static_root_array_variable_offset)); let static_root_array_offset = static_root_array_address as usize - heap.as_ptr() as usize; - let continuation_table_address = unskew_pointer(read_word(heap, continuation_table_variable_offset)); + let continuation_table_address = + unskew_pointer(read_word(heap, continuation_table_variable_offset)); let continuation_table_offset = continuation_table_address as usize - heap.as_ptr() as usize; while offset < heap_ptr_offset { @@ -464,7 +466,7 @@ fn read_mutbox_field(mutbox_address: u32, heap: &[u8]) -> u32 { let incremental = cfg!(feature = "incremental_gc"); let mut mutbox_offset = mutbox_address as usize - heap.as_ptr() as usize; - + let mutbox_tag = read_word(heap, mutbox_offset); assert_eq!(mutbox_tag, TAG_MUTBOX); mutbox_offset += WORD_SIZE; @@ -517,7 +519,8 @@ impl GC { fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool { let heap_base = heap.heap_base_address(); let static_roots = Value::from_ptr(heap.static_root_array_variable_address()); - let continuation_table_ptr_address = heap.continuation_table_variable_address() as *mut Value; + let continuation_table_ptr_address = + heap.continuation_table_variable_address() as *mut Value; let heap_1 = heap.clone(); let heap_2 = heap.clone(); diff --git a/rts/motoko-rts-tests/src/gc/heap.rs b/rts/motoko-rts-tests/src/gc/heap.rs index 33417f62fd9..d343b351d98 100644 --- a/rts/motoko-rts-tests/src/gc/heap.rs +++ b/rts/motoko-rts-tests/src/gc/heap.rs @@ -219,13 +219,17 @@ impl MotokoHeapInner { // Two pointers, one to the static root array, and the other to the continuation table. let root_pointers_size_bytes = 2 * WORD_SIZE; - // Each object will have array header plus one word for id per object + one word for each reference. - // The static root is an array (header + length) with one element, one MutBox for each static variable. - let static_root_set_size_bytes = (size_of::().as_usize() + roots.len() + // Each object will have array header plus one word for id per object + one word for each reference. + // The static root is an array (header + length) with one element, one MutBox for each static variable. + let static_root_set_size_bytes = (size_of::().as_usize() + + roots.len() + roots.len() * size_of::().as_usize()) * WORD_SIZE; - let continuation_table_size_byes = (size_of::() + Words(continuation_table.len() as u32)).to_bytes().as_usize(); + let continuation_table_size_byes = (size_of::() + + Words(continuation_table.len() as u32)) + .to_bytes() + .as_usize(); let dynamic_objects_size_bytes = { let object_headers_words = map.len() * (size_of::().as_usize() + 1); @@ -233,7 +237,8 @@ impl MotokoHeapInner { (object_headers_words + references_words) * WORD_SIZE }; - let dynamic_heap_size_bytes = dynamic_objects_size_bytes + static_root_set_size_bytes + continuation_table_size_byes; + let dynamic_heap_size_bytes = + dynamic_objects_size_bytes + static_root_set_size_bytes + continuation_table_size_byes; let total_heap_size_bytes = root_pointers_size_bytes + dynamic_heap_size_bytes; @@ -412,7 +417,7 @@ fn create_dynamic_heap( let mut heap_offset = 0; for (obj, refs) in refs { object_addrs.insert(*obj, heap_start + heap_offset); - + // Store object header let address = u32::try_from(heap_start + heap_offset).unwrap(); write_word(dynamic_heap, heap_offset, TAG_ARRAY); @@ -434,7 +439,7 @@ fn create_dynamic_heap( // Store object value (idx) write_word(dynamic_heap, heap_offset, make_scalar(*obj)); heap_offset += WORD_SIZE; - + // Leave space for the fields heap_offset += refs.len() * WORD_SIZE; } @@ -474,11 +479,7 @@ fn create_dynamic_heap( heap_offset += WORD_SIZE; if incremental { - write_word( - dynamic_heap, - heap_offset, - make_pointer(mutbox_address), - ); + write_word(dynamic_heap, heap_offset, make_pointer(mutbox_address)); heap_offset += WORD_SIZE; } @@ -504,13 +505,13 @@ fn create_dynamic_heap( assert_eq!(static_roots.len(), root_mutboxes.len()); write_word(dynamic_heap, heap_offset, root_mutboxes.len() as u32); heap_offset += WORD_SIZE; - + for mutbox_address in root_mutboxes { write_word(dynamic_heap, heap_offset, make_pointer(mutbox_address)); heap_offset += WORD_SIZE; } } - + let continuation_table_address = u32::try_from(heap_start + heap_offset).unwrap(); { write_word(dynamic_heap, heap_offset, TAG_ARRAY); diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs index f8be0beaa4f..73bd88e2288 100644 --- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs +++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs @@ -32,9 +32,14 @@ unsafe fn check_visit_static_roots(heap: &MotokoHeap, root_ids: &[ObjectIdx]) { |context, field| { let object = *field; let array = object.as_array(); - if array.len() == 1 { - let id = object_id(&heap, array as usize); - context.push(id); + if array.len() == root_ids.len() as u32 { + for index in 0..array.len() { + let mutbox_value = array.get(index); + let mutbox = mutbox_value.as_mutbox(); + let root_address = (*mutbox).field.get_ptr(); + let root_id = object_id(heap, root_address); + context.push(root_id); + } } }, ); @@ -51,7 +56,7 @@ unsafe fn check_visit_continuation_table(heap: &MotokoHeap, continuation_ids: &[ |context, field| { let object = *field; let array = object.as_array(); - if array.len() != 1 { + if array.len() == continuation_ids.len() as u32 { assert_eq!(context.len(), 0); for index in 0..array.len() { let element = array.get(index); From 9843b5f491377d6a8e57152587102a18ca69159a Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Mon, 14 Aug 2023 16:34:17 +0200 Subject: [PATCH 027/260] Do not stop GC on upgrade --- rts/motoko-rts/src/gc/incremental.rs | 16 ---------------- src/codegen/compile.ml | 1 - 2 files changed, 17 deletions(-) diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 1d80b03fcb1..422a45f7b13 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -45,7 +45,6 @@ unsafe fn initialize_incremental_gc(mem: &mut M) { #[ic_mem_fn(ic_only)] unsafe fn schedule_incremental_gc(mem: &mut M) { let state = get_incremental_gc_state(); - assert!(state.phase != Phase::Stop); let running = state.phase != Phase::Pause; if running || should_start() { incremental_gc(mem); @@ -137,7 +136,6 @@ enum Phase { Mark, // Incremental marking. Evacuate, // Incremental evacuation compaction. Update, // Incremental pointer updates. - Stop, // GC stopped on canister upgrade. } /// GC state retained over multiple GC increments. @@ -188,7 +186,6 @@ impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { /// * The mark phase can only be started on an empty call stack. /// * The update phase can only be completed on an empty call stack. pub unsafe fn empty_call_stack_increment(&mut self, roots: Roots) { - assert!(self.state.phase != Phase::Stop); if self.pausing() { self.start_marking(roots); } @@ -333,9 +330,6 @@ unsafe fn post_allocation_barrier(state: &mut State, new_object: Value) { /// The fields may still point to old object locations that are forwarded. /// * During the update phase /// - New objects do not need to be marked as they are allocated in non-evacuated partitions. -/// * When GC is stopped on canister upgrade: -/// - The GC will not resume and thus marking is irrelevant. -/// - This is necessary because the upgrade serialization alters tags to store other information. unsafe fn mark_new_allocation(state: &mut State, new_object: Value) { debug_assert!(state.phase == Phase::Mark || state.phase == Phase::Evacuate); let object = new_object.get_ptr() as *mut Obj; @@ -360,9 +354,6 @@ unsafe fn mark_new_allocation(state: &mut State, new_object: Value) { /// and pointer writes must be handled by barriers. /// - Allocation barrier: Resolve the forwarding for all pointers in the new allocation. /// - Write barrier: Resolve forwarding for the written pointer value. -/// * When the GC is stopped on canister upgrade: -/// - The GC will not resume and thus pointer updates are irrelevant. The runtime system -/// continues to resolve the forwarding for all remaining old pointers. unsafe fn update_new_allocation(state: &State, new_object: Value) { debug_assert!(state.phase == Phase::Update); if state.partitioned_heap.updates_needed() { @@ -387,13 +378,6 @@ unsafe fn count_allocation(state: &mut State) { } } -/// Stop the GC before performing upgrade. This is only a safe-guard since -/// the compiler must not schedule the GC during stabilization anyway. -#[no_mangle] -pub unsafe extern "C" fn stop_gc_on_upgrade() { - get_incremental_gc_state().phase = Phase::Stop; -} - pub unsafe fn get_partitioned_heap() -> &'static mut PartitionedHeap { debug_assert!(get_incremental_gc_state().partitioned_heap.is_initialized()); &mut get_incremental_gc_state().partitioned_heap diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index ad5dfe72bb3..9dd221c0202 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -867,7 +867,6 @@ module RTS = struct E.add_func_import env "rts" "incremental_gc" [] []; E.add_func_import env "rts" "write_with_barrier" [I32Type; I32Type] []; E.add_func_import env "rts" "allocation_barrier" [I32Type] [I32Type]; - E.add_func_import env "rts" "stop_gc_on_upgrade" [] []; E.add_func_import env "rts" "running_gc" [] [I32Type]; () From 5059daadcc08fdb272371554e8b7d3447f4d3f11 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 16 Aug 2023 10:38:39 +0200 Subject: [PATCH 028/260] Focus on the incremental GC --- bin/wrapper.sh | 8 +- default.nix | 30 - doc/md/compiler-ref.md | 4 - rts/Makefile | 45 +- rts/motoko-rts-macros/src/lib.rs | 31 -- rts/motoko-rts-tests/Cargo.toml | 3 - rts/motoko-rts-tests/src/gc.rs | 230 ++------ rts/motoko-rts-tests/src/gc/compacting.rs | 10 - .../src/gc/compacting/bitmap.rs | 165 ------ .../src/gc/compacting/mark_stack.rs | 106 ---- rts/motoko-rts-tests/src/gc/generational.rs | 10 - .../src/gc/generational/mark_stack.rs | 89 --- .../src/gc/generational/remembered_set.rs | 96 ---- rts/motoko-rts-tests/src/gc/heap.rs | 159 +----- .../src/gc/incremental/roots.rs | 4 +- rts/motoko-rts-tests/src/gc/utils.rs | 27 - rts/motoko-rts-tests/src/memory.rs | 5 - rts/motoko-rts-tests/src/stream.rs | 3 +- rts/motoko-rts/Cargo.toml | 3 - rts/motoko-rts/native/Cargo.toml | 3 - rts/motoko-rts/src/barriers.rs | 21 - rts/motoko-rts/src/gc.rs | 25 - rts/motoko-rts/src/gc/copying.rs | 216 -------- rts/motoko-rts/src/gc/generational.rs | 517 ------------------ .../src/gc/generational/mark_stack.rs | 74 --- .../src/gc/generational/remembered_set.rs | 275 ---------- .../src/gc/generational/sanity_checks.rs | 173 ------ .../src/gc/generational/write_barrier.rs | 46 -- rts/motoko-rts/src/gc/mark_compact.rs | 293 ---------- rts/motoko-rts/src/lib.rs | 8 - rts/motoko-rts/src/memory/ic.rs | 8 - rts/motoko-rts/src/memory/ic/linear_memory.rs | 74 --- rts/motoko-rts/src/static_checks.rs | 6 - rts/motoko-rts/src/types.rs | 47 +- src/codegen/compile.ml | 376 ++++--------- src/exes/moc.ml | 16 - src/js/common.ml | 4 - src/mo_config/flags.ml | 3 - src/pipeline/pipeline.ml | 8 +- src/rts/gen.sh | 7 +- src/rts/rts.ml | 7 +- src/rts/rts.mli | 7 +- 42 files changed, 200 insertions(+), 3042 deletions(-) delete mode 100644 rts/motoko-rts-tests/src/gc/compacting.rs delete mode 100644 rts/motoko-rts-tests/src/gc/compacting/bitmap.rs delete mode 100644 rts/motoko-rts-tests/src/gc/compacting/mark_stack.rs delete mode 100644 rts/motoko-rts-tests/src/gc/generational.rs delete mode 100644 rts/motoko-rts-tests/src/gc/generational/mark_stack.rs delete mode 100644 rts/motoko-rts-tests/src/gc/generational/remembered_set.rs delete mode 100644 rts/motoko-rts/src/gc/copying.rs delete mode 100644 rts/motoko-rts/src/gc/generational.rs delete mode 100644 rts/motoko-rts/src/gc/generational/mark_stack.rs delete mode 100644 rts/motoko-rts/src/gc/generational/remembered_set.rs delete mode 100644 rts/motoko-rts/src/gc/generational/sanity_checks.rs delete mode 100644 rts/motoko-rts/src/gc/generational/write_barrier.rs delete mode 100644 rts/motoko-rts/src/gc/mark_compact.rs delete mode 100644 rts/motoko-rts/src/memory/ic/linear_memory.rs diff --git a/bin/wrapper.sh b/bin/wrapper.sh index 45b7b341078..ffb6e673478 100755 --- a/bin/wrapper.sh +++ b/bin/wrapper.sh @@ -18,7 +18,7 @@ declare -A envs # list of expected environment variables with paths to products # Define build products here real[moc]=src/moc hint[moc]="make -C $rel_root/src moc" -envs[moc]="MOC_NON_INCREMENTAL_RELEASE_RTS MOC_NON_INCREMENTAL_DEBUG_RTS MOC_INCREMENTAL_RELEASE_RTS MOC_INCREMENTAL_DEBUG_RTS" +envs[moc]="MOC_RELEASE_RTS MOC_DEBUG_RTS" real[mo-ld]=src/mo-ld hint[mo-ld]="make -C $rel_root/src mo-ld" real[mo-doc]=src/mo-doc @@ -32,10 +32,8 @@ hint[candid-tests]="make -C $rel_root/src candid-tests" rts_hint="make -C $rel_root/rts" -real[MOC_NON_INCREMENTAL_RELEASE_RTS]=rts/mo-rts.wasm -real[MOC_NON_INCREMENTAL_DEBUG_RTS]=rts/mo-rts-debug.wasm -real[MOC_INCREMENTAL_RELEASE_RTS]=rts/mo-rts-incremental.wasm -real[MOC_INCREMENTAL_DEBUG_RTS]=rts/mo-rts-incremental-debug.wasm +real[MOC_RELEASE_RTS]=rts/mo-rts.wasm +real[MOC_DEBUG_RTS]=rts/mo-rts-debug.wasm for var in ${envs[moc]}; do hint[$var]=$rts_hint diff --git a/default.nix b/default.nix index d248d3f353c..8a88072fdcf 100644 --- a/default.nix +++ b/default.nix @@ -257,8 +257,6 @@ rec { mkdir -p $out/rts cp mo-rts.wasm $out/rts cp mo-rts-debug.wasm $out/rts - cp mo-rts-incremental.wasm $out/rts - cp mo-rts-incremental-debug.wasm $out/rts ''; # This needs to be self-contained. Remove mention of nix path in debug @@ -269,11 +267,6 @@ rec { -t ${rtsDeps} \ -t ${rustStdDeps} \ $out/rts/mo-rts.wasm $out/rts/mo-rts-debug.wasm - remove-references-to \ - -t ${nixpkgs.rustc-nightly} \ - -t ${rtsDeps} \ - -t ${rustStdDeps} \ - $out/rts/mo-rts-incremental.wasm $out/rts/mo-rts-incremental-debug.wasm ''; allowedRequisites = []; @@ -363,26 +356,6 @@ rec { (test_subdir dir deps).overrideAttrs { EXTRA_MOC_ARGS = "--sanity-checks"; }; - - generational_gc_subdir = dir: deps: - (test_subdir dir deps).overrideAttrs { - EXTRA_MOC_ARGS = "--generational-gc"; - }; - - snty_compacting_gc_subdir = dir: deps: - (test_subdir dir deps).overrideAttrs { - EXTRA_MOC_ARGS = "--sanity-checks --compacting-gc"; - }; - - snty_generational_gc_subdir = dir: deps: - (test_subdir dir deps).overrideAttrs { - EXTRA_MOC_ARGS = "--sanity-checks --generational-gc"; - }; - - snty_incremental_gc_subdir = dir: deps: - (test_subdir dir deps).overrideAttrs { - EXTRA_MOC_ARGS = "--sanity-checks --incremental-gc"; - }; perf_subdir = dir: deps: (test_subdir dir deps).overrideAttrs (args: { @@ -497,9 +470,6 @@ rec { ic-ref-run = test_subdir "run-drun" [ moc ic-ref-run ]; drun = test_subdir "run-drun" [ moc nixpkgs.drun ]; drun-dbg = snty_subdir "run-drun" [ moc nixpkgs.drun ]; - drun-compacting-gc = snty_compacting_gc_subdir "run-drun" [ moc nixpkgs.drun ] ; - drun-generational-gc = snty_generational_gc_subdir "run-drun" [ moc nixpkgs.drun ] ; - drun-incremental-gc = snty_incremental_gc_subdir "run-drun" [ moc nixpkgs.drun ] ; fail = test_subdir "fail" [ moc ]; repl = test_subdir "repl" [ moc ]; ld = test_subdir "ld" ([ mo-ld ] ++ ldTestDeps); diff --git a/doc/md/compiler-ref.md b/doc/md/compiler-ref.md index 27a9bcea383..29bd7b7d7ef 100644 --- a/doc/md/compiler-ref.md +++ b/doc/md/compiler-ref.md @@ -26,15 +26,11 @@ You can use the following options with the `moc` command. | `--args0 ` | Read additional `NUL` separated command line arguments from ``. | | `-c` | Compile to WebAssembly. | | `--check` | Performs type checking only. | -| `--compacting-gc` | Use compacting GC | -| `--copying-gc` | Use copying GC (default) | | `--debug` | Respects debug expressions in the source (the default). | | `--error-detail ` | Set level of error message detail for syntax errors, n in \[0..3\] (default 2). | -| `--generational-gc` | Use generational GC | | `-help`,`--help` | Displays usage information. | | `--hide-warnings` | Hides compiler warnings. | | `-Werror` | Treat warnings as errors. | -| `--incremental-gc` | Use incremental GC (only for beta-testing) | | `--idl` | Compile binary and emit Candid IDL specification to `.did` file. | | `-i` | Runs the compiler in an interactive read–eval–print loop (REPL) shell so you can evaluate program execution (implies -r). | | `--map` | Outputs a JavaScript source map. | diff --git a/rts/Makefile b/rts/Makefile index e27dfd06dbb..e4fc8b0cbd0 100644 --- a/rts/Makefile +++ b/rts/Makefile @@ -112,7 +112,7 @@ CLANG_FLAGS = \ .PHONY: all -all: mo-rts.wasm mo-rts-debug.wasm mo-rts-incremental.wasm mo-rts-incremental-debug.wasm +all: mo-rts.wasm mo-rts-debug.wasm _build: mkdir -p $@ @@ -226,28 +226,17 @@ RTS_RELEASE_BUILD=$(RTS_BUILD) --release RTS_DEBUG_TARGET=motoko-rts/target/wasm32-unknown-emscripten/debug/libmotoko_rts.a RTS_RELEASE_TARGET=motoko-rts/target/wasm32-unknown-emscripten/release/libmotoko_rts.a -RTS_RUST_NON_INCREMENTAL_WASM_A=_build/wasm/libmotoko_rts.a -RTS_RUST_NON_INCREMENTAL_DEBUG_WASM_A=_build/wasm/libmotoko_rts_debug.a +RTS_RUST_WASM_A=_build/wasm/libmotoko_rts.a +RTS_RUST_DEBUG_WASM_A=_build/wasm/libmotoko_rts_debug.a -RTS_RUST_INCREMENTAL_WASM_A=_build/wasm/libmotoko_rts_incremental.a -RTS_RUST_INCREMENTAL_DEBUG_WASM_A=_build/wasm/libmotoko_rts_incremental_debug.a - -$(RTS_RUST_NON_INCREMENTAL_WASM_A): $(RTS_DEPENDENCIES) +$(RTS_RUST_WASM_A): $(RTS_DEPENDENCIES) $(RTS_RELEASE_BUILD) cp $(RTS_RELEASE_TARGET) $@ -$(RTS_RUST_NON_INCREMENTAL_DEBUG_WASM_A): $(RTS_DEPENDENCIES) +$(RTS_RUST_DEBUG_WASM_A): $(RTS_DEPENDENCIES) $(RTS_DEBUG_BUILD) cp $(RTS_DEBUG_TARGET) $@ -$(RTS_RUST_INCREMENTAL_WASM_A): $(RTS_DEPENDENCIES) - $(RTS_RELEASE_BUILD) --features incremental_gc - cp $(RTS_RELEASE_TARGET) $@ - -$(RTS_RUST_INCREMENTAL_DEBUG_WASM_A): $(RTS_DEPENDENCIES) - $(RTS_DEBUG_BUILD) --features incremental_gc - cp $(RTS_DEBUG_TARGET) $@ - # # The test suite # @@ -258,16 +247,10 @@ TEST_RUN=wasmtime --disable-cache motoko-rts-tests/target/wasm32-wasi/debug/moto .PHONY: test -test: test-non-incremental test-incremental - -test-non-incremental: $(TEST_DEPENDENCIES) +test: $(TEST_DEPENDENCIES) $(TEST_BUILD) $(TEST_RUN) -test-incremental: $(TEST_DEPENDENCIES) - $(TEST_BUILD) --features incremental_gc - $(TEST_RUN) - # # Putting it all together # @@ -295,22 +278,12 @@ LINKER_OPTIONS=\ $(EXPORTED_SYMBOLS:%=--export=%) \ --whole-archive -mo-rts.wasm: $(RTS_RUST_NON_INCREMENTAL_WASM_A) $(WASM_A_DEPENDENCIES) - $(WASM_LD) -o $@ \ - $(LINKER_OPTIONS) \ - $+ - -mo-rts-debug.wasm: $(RTS_RUST_NON_INCREMENTAL_DEBUG_WASM_A) $(WASM_A_DEPENDENCIES) - $(WASM_LD) -o $@ \ - $(LINKER_OPTIONS) \ - $+ - -mo-rts-incremental.wasm: $(RTS_RUST_INCREMENTAL_WASM_A) $(WASM_A_DEPENDENCIES) +mo-rts.wasm: $(RTS_RUST_WASM_A) $(WASM_A_DEPENDENCIES) $(WASM_LD) -o $@ \ $(LINKER_OPTIONS) \ $+ -mo-rts-incremental-debug.wasm: $(RTS_RUST_INCREMENTAL_DEBUG_WASM_A) $(WASM_A_DEPENDENCIES) +mo-rts-debug.wasm: $(RTS_RUST_DEBUG_WASM_A) $(WASM_A_DEPENDENCIES) $(WASM_LD) -o $@ \ $(LINKER_OPTIONS) \ $+ @@ -325,8 +298,6 @@ clean: _build \ mo-rts.wasm \ mo-rts-debug.wasm \ - mo-rts-incremental.wasm \ - mo-rts-incremental-debug.wasm \ motoko-rts/target \ motoko-rts-tests/target \ motoko-rts-macros/target \ diff --git a/rts/motoko-rts-macros/src/lib.rs b/rts/motoko-rts-macros/src/lib.rs index e4dd417c849..49ac0e18d3e 100644 --- a/rts/motoko-rts-macros/src/lib.rs +++ b/rts/motoko-rts-macros/src/lib.rs @@ -136,34 +136,3 @@ pub fn ic_mem_fn(attr: TokenStream, input: TokenStream) -> TokenStream { ) .into() } - -/// Feature macro for incremental GC features, in particular forwarding pointers. -/// Equivalent to using the attribute `#[cfg(feature = "incremental_gc")]`. -#[proc_macro_attribute] -pub fn incremental_gc(attr: TokenStream, input: TokenStream) -> TokenStream { - assert!(attr.is_empty()); - let block = syn::parse_macro_input!(input as syn::Item); - quote!( - #[cfg(feature = "incremental_gc")] - #block - ) - .into() -} - -/// Feature macro for non-incremental GC features, in particular forwarding pointers. -/// Equivalent to using the attribute `#[cfg(not(feature = "incremental_gc"))]`. -#[proc_macro_attribute] -pub fn non_incremental_gc(attr: TokenStream, input: TokenStream) -> TokenStream { - assert!(attr.is_empty()); - let block = syn::parse_macro_input!(input as syn::Item); - quote!( - #[cfg(not(feature = "incremental_gc"))] - #block - ) - .into() -} - -#[proc_macro] -pub fn is_incremental_gc(_item: TokenStream) -> TokenStream { - "cfg!(feature = \"incremental_gc\")".parse().unwrap() -} diff --git a/rts/motoko-rts-tests/Cargo.toml b/rts/motoko-rts-tests/Cargo.toml index 3640bf9114c..3e28860e452 100644 --- a/rts/motoko-rts-tests/Cargo.toml +++ b/rts/motoko-rts-tests/Cargo.toml @@ -4,9 +4,6 @@ version = "0.1.0" authors = ["dfinity )], roots: &[ObjectIdx], continuation_table: &[ObjectIdx], ) { - let mut heap = MotokoHeap::new(refs, roots, continuation_table, gc); + let mut heap = MotokoHeap::new(refs, roots, continuation_table); initialize_gc(&mut heap); @@ -145,8 +126,8 @@ fn test_gc( heap.continuation_table_variable_offset(), ); - for round in 0..3 { - let check_all_reclaimed = gc.run(&mut heap, round); + for _ in 0..3 { + let check_all_reclaimed = run(&mut heap); let heap_base_offset = heap.heap_base_offset(); let heap_ptr_offset = heap.heap_ptr_offset(); @@ -166,10 +147,6 @@ fn test_gc( } } -#[non_incremental_gc] -fn initialize_gc(_heap: &mut MotokoHeap) {} - -#[incremental_gc] fn initialize_gc(heap: &mut MotokoHeap) { use motoko_rts::gc::incremental::{ get_incremental_gc_state, get_partitioned_heap, IncrementalGC, @@ -187,10 +164,6 @@ fn initialize_gc(heap: &mut MotokoHeap) { } } -#[non_incremental_gc] -fn reset_gc() {} - -#[incremental_gc] fn reset_gc() { use crate::memory::TestMemory; use motoko_rts::gc::incremental::{ @@ -223,7 +196,6 @@ fn check_dynamic_heap( static_root_array_variable_offset: usize, continuation_table_variable_offset: usize, ) { - let incremental = cfg!(feature = "incremental_gc"); let objects_map: FxHashMap = objects .iter() .map(|(obj, refs)| (*obj, refs.as_slice())) @@ -269,41 +241,29 @@ fn check_dynamic_heap( offset += WORD_SIZE; if tag == TAG_ONE_WORD_FILLER { - assert!(incremental); } else if tag == TAG_FREE_SPACE { - assert!(incremental); let words = read_word(heap, offset) as usize; offset += WORD_SIZE; offset += words * WORD_SIZE; } else { let forward; - if incremental { - forward = read_word(heap, offset); - offset += WORD_SIZE; - } else { - forward = make_pointer(address as u32); - } + forward = read_word(heap, offset); + offset += WORD_SIZE; let is_forwarded = forward != make_pointer(address as u32); if tag == TAG_MUTBOX { // MutBoxes of static root array, will be scanned indirectly when checking the static root array. offset += WORD_SIZE; - } else if incremental && tag == TAG_BLOB { + } else if tag == TAG_BLOB { assert!(!is_forwarded); // in-heap mark stack blobs let length = read_word(heap, offset); offset += WORD_SIZE + length as usize; } else { - if incremental { - assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN); - } else { - assert!(tag == TAG_ARRAY); - } - + assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN); + if is_forwarded { - assert!(incremental); - let forward_offset = forward as usize - heap.as_ptr() as usize; let length = read_word( heap, @@ -438,17 +398,13 @@ fn compute_reachable_objects( } fn check_static_root_array(mut offset: usize, roots: &[ObjectIdx], heap: &[u8]) { - let incremental = cfg!(feature = "incremental_gc"); - let array_address = heap.as_ptr() as usize + offset; assert_eq!(read_word(heap, offset), TAG_ARRAY); offset += WORD_SIZE; - if incremental { - assert_eq!(read_word(heap, offset), make_pointer(array_address as u32)); - offset += WORD_SIZE; - } - + assert_eq!(read_word(heap, offset), make_pointer(array_address as u32)); + offset += WORD_SIZE; + assert_eq!(read_word(heap, offset), roots.len() as u32); offset += WORD_SIZE; @@ -463,34 +419,26 @@ fn check_static_root_array(mut offset: usize, roots: &[ObjectIdx], heap: &[u8]) } fn read_mutbox_field(mutbox_address: u32, heap: &[u8]) -> u32 { - let incremental = cfg!(feature = "incremental_gc"); - let mut mutbox_offset = mutbox_address as usize - heap.as_ptr() as usize; let mutbox_tag = read_word(heap, mutbox_offset); assert_eq!(mutbox_tag, TAG_MUTBOX); mutbox_offset += WORD_SIZE; - if incremental { - assert_eq!(read_word(heap, mutbox_offset), make_pointer(mutbox_address)); - mutbox_offset += WORD_SIZE; - } + assert_eq!(read_word(heap, mutbox_offset), make_pointer(mutbox_address)); + mutbox_offset += WORD_SIZE; read_word(heap, mutbox_offset) } fn check_continuation_table(mut offset: usize, continuation_table: &[ObjectIdx], heap: &[u8]) { - let incremental = cfg!(feature = "incremental_gc"); - let table_addr = heap.as_ptr() as usize + offset; assert_eq!(read_word(heap, offset), TAG_ARRAY); offset += WORD_SIZE; - if incremental { - assert_eq!(read_word(heap, offset), make_pointer(table_addr as u32)); - offset += WORD_SIZE; - } - + assert_eq!(read_word(heap, offset), make_pointer(table_addr as u32)); + offset += WORD_SIZE; + assert_eq!(read_word(heap, offset), continuation_table.len() as u32); offset += WORD_SIZE; @@ -504,130 +452,32 @@ fn check_continuation_table(mut offset: usize, continuation_table: &[ObjectIdx], } fn read_object_id(object_address: u32, heap: &[u8]) -> ObjectIdx { - let incremental = cfg!(feature = "incremental_gc"); - let tag = read_word(heap, object_address as usize - heap.as_ptr() as usize); - assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN && incremental); + assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN); // Skip object header for idx let idx_address = object_address as usize + size_of::().to_bytes().as_usize(); get_scalar_value(read_word(heap, idx_address - heap.as_ptr() as usize)) } -impl GC { - #[non_incremental_gc] - fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool { - let heap_base = heap.heap_base_address(); - let static_roots = Value::from_ptr(heap.static_root_array_variable_address()); - let continuation_table_ptr_address = - heap.continuation_table_variable_address() as *mut Value; - - let heap_1 = heap.clone(); - let heap_2 = heap.clone(); - - match self { - GC::Copying => { - unsafe { - motoko_rts::gc::copying::copying_gc_internal( - heap, - heap_base, - // get_hp - || heap_1.heap_ptr_address(), - // set_hp - move |hp| heap_2.set_heap_ptr_address(hp as usize), - static_roots, - continuation_table_ptr_address, - // note_live_size - |_live_size| {}, - // note_reclaimed - |_reclaimed| {}, - ); - } - true - } - - GC::MarkCompact => { - unsafe { - motoko_rts::gc::mark_compact::compacting_gc_internal( - heap, - heap_base, - // get_hp - || heap_1.heap_ptr_address(), - // set_hp - move |hp| heap_2.set_heap_ptr_address(hp as usize), - static_roots, - continuation_table_ptr_address, - // note_live_size - |_live_size| {}, - // note_reclaimed - |_reclaimed| {}, - ); - } - true - } - - GC::Generational => { - use motoko_rts::gc::generational::{ - remembered_set::RememberedSet, - write_barrier::{LAST_HP, REMEMBERED_SET}, - GenerationalGC, Strategy, - }; - - let strategy = match _round { - 0 => Strategy::Young, - _ => Strategy::Full, - }; - unsafe { - REMEMBERED_SET = Some(RememberedSet::new(heap)); - LAST_HP = heap_1.last_ptr_address(); - - let limits = motoko_rts::gc::generational::Limits { - base: heap_base as usize, - last_free: heap_1.last_ptr_address(), - free: heap_1.heap_ptr_address(), - }; - let roots = motoko_rts::gc::generational::Roots { - static_roots, - continuation_table_ptr_loc: continuation_table_ptr_address, - }; - let gc_heap = motoko_rts::gc::generational::Heap { - mem: heap, - limits, - roots, - }; - let mut gc = GenerationalGC::new(gc_heap, strategy); - gc.run(); - let free = gc.heap.limits.free; - heap.set_last_ptr_address(free); - heap.set_heap_ptr_address(free); - } - _round >= 2 - } - } - } +fn run(heap: &mut MotokoHeap) -> bool { + let static_root = heap.static_root_array_variable_address() as *mut Value; + let continuation_table_location = heap.continuation_table_variable_address() as *mut Value; + let unused_root = &mut Value::from_scalar(0) as *mut Value; - #[incremental_gc] - fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool { - let static_root = heap.static_root_array_variable_address() as *mut Value; - let continuation_table_location = heap.continuation_table_variable_address() as *mut Value; - let unused_root = &mut Value::from_scalar(0) as *mut Value; - - match self { - GC::Incremental => unsafe { - use motoko_rts::gc::incremental::{get_incremental_gc_state, IncrementalGC}; - const INCREMENTS_UNTIL_COMPLETION: usize = 16; - for _ in 0..INCREMENTS_UNTIL_COMPLETION { - let roots = [ - static_root, - continuation_table_location, - unused_root, - unused_root, - ]; - IncrementalGC::instance(heap, get_incremental_gc_state()) - .empty_call_stack_increment(roots); - } - false - }, + unsafe { + use motoko_rts::gc::incremental::{get_incremental_gc_state, IncrementalGC}; + const INCREMENTS_UNTIL_COMPLETION: usize = 16; + for _ in 0..INCREMENTS_UNTIL_COMPLETION { + let roots = [ + static_root, + continuation_table_location, + unused_root, + unused_root, + ]; + IncrementalGC::instance(heap, get_incremental_gc_state()) + .empty_call_stack_increment(roots); } + false } } diff --git a/rts/motoko-rts-tests/src/gc/compacting.rs b/rts/motoko-rts-tests/src/gc/compacting.rs deleted file mode 100644 index 42cfa936cd2..00000000000 --- a/rts/motoko-rts-tests/src/gc/compacting.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod bitmap; -mod mark_stack; - -pub fn test() { - println!("Testing compacting GC components ..."); - unsafe { - bitmap::test(); - mark_stack::test(); - } -} diff --git a/rts/motoko-rts-tests/src/gc/compacting/bitmap.rs b/rts/motoko-rts-tests/src/gc/compacting/bitmap.rs deleted file mode 100644 index 6d3815f66cd..00000000000 --- a/rts/motoko-rts-tests/src/gc/compacting/bitmap.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::memory::TestMemory; - -use motoko_rts::constants::WORD_SIZE; -use motoko_rts::gc::mark_compact::bitmap::{ - alloc_bitmap, free_bitmap, get_bit, iter_bits, set_bit, BITMAP_ITER_END, -}; -use motoko_rts::memory::Memory; -use motoko_rts::types::{Bytes, Words}; - -use std::collections::HashSet; - -use proptest::strategy::Strategy; -use proptest::test_runner::{Config, TestCaseError, TestCaseResult, TestRunner}; - -pub unsafe fn test() { - println!(" Testing bitmap ..."); - println!(" Testing set_bit/get_bit"); - - { - let mut mem = TestMemory::new(Words(1024)); - test_set_get(&mut mem, vec![0, 33]).unwrap(); - } - - let mut proptest_runner = TestRunner::new(Config { - cases: 100, - failure_persistence: None, - ..Default::default() - }); - - proptest_runner - .run(&bit_index_vec_strategy(), |bits| { - // Max bit idx = 65,534, requires 2048 words. Add 2 words for Blob header (header + - // length). - let mut mem = TestMemory::new(Words(2051)); - test_set_get_proptest(&mut mem, bits) - }) - .unwrap(); - - println!(" Testing bit iteration"); - proptest_runner - .run(&bit_index_set_strategy(), |bits| { - // Same as above - let mut mem = TestMemory::new(Words(2051)); - test_bit_iter(&mut mem, bits) - }) - .unwrap(); -} - -/// Generates vectors of bit indices -fn bit_index_vec_strategy() -> impl Strategy> { - proptest::collection::vec(0u16..u16::MAX, 0..1_000) -} - -/// Same as `bit_index_vec_strategy`, but generates sets -fn bit_index_set_strategy() -> impl Strategy> { - proptest::collection::hash_set(0u16..u16::MAX, 0..1_000) -} - -fn test_set_get_proptest(mem: &mut M, bits: Vec) -> TestCaseResult { - test_set_get(mem, bits).map_err(|err| TestCaseError::Fail(err.into())) -} - -fn test_set_get(mem: &mut M, mut bits: Vec) -> Result<(), String> { - if bits.is_empty() { - return Ok(()); - } - - unsafe { - alloc_bitmap( - mem, - Bytes((u32::from(*bits.iter().max().unwrap()) + 1) * WORD_SIZE), - 0, - ); - - for bit in &bits { - set_bit(u32::from(*bit)); - if !get_bit(u32::from(*bit)) { - return Err("set-get error".to_string()); - } - } - - bits.sort(); - - let mut last_bit: Option = None; - for bit in bits { - // Bits from the last set bit up to current bit should be 0 - if let Some(last_bit) = last_bit { - for i in last_bit + 1..bit { - if get_bit(u32::from(i)) { - return Err(format!("get_bit({}) of unset bit is true", i)); - } - } - } - - // Current bit should be set - if !get_bit(u32::from(bit)) { - return Err("get_bit of set bit is false".to_string()); - } - - last_bit = Some(bit); - } - - free_bitmap() - } - - Ok(()) -} - -fn test_bit_iter(mem: &mut M, bits: HashSet) -> TestCaseResult { - // If the max bit is N, the heap size is at least N+1 words - let heap_size = Words(u32::from( - bits.iter().max().map(|max_bit| max_bit + 1).unwrap_or(0), - )) - .to_bytes(); - - unsafe { - alloc_bitmap(mem, heap_size, 0); - - for bit in bits.iter() { - set_bit(u32::from(*bit)); - } - - let mut bits_sorted = bits.into_iter().collect::>(); - bits_sorted.sort(); - - let mut bit_vec_iter = bits_sorted.into_iter(); - let mut bit_map_iter = iter_bits(); - - while let Some(vec_bit) = bit_vec_iter.next() { - match bit_map_iter.next() { - BITMAP_ITER_END => { - return Err(TestCaseError::Fail( - "bitmap iterator didn't yield but there are more bits".into(), - )); - } - map_bit => { - if map_bit != u32::from(vec_bit) { - return Err(TestCaseError::Fail( - format!( - "bitmap iterator yields {}, but actual bit is {}", - map_bit, vec_bit - ) - .into(), - )); - } - } - } - } - - let map_bit = bit_map_iter.next(); - if map_bit != BITMAP_ITER_END { - return Err(TestCaseError::Fail( - format!( - "bitmap iterator yields {}, but there are no more bits left", - map_bit - ) - .into(), - )); - } - - free_bitmap() - } - - Ok(()) -} diff --git a/rts/motoko-rts-tests/src/gc/compacting/mark_stack.rs b/rts/motoko-rts-tests/src/gc/compacting/mark_stack.rs deleted file mode 100644 index 87ab1c130a0..00000000000 --- a/rts/motoko-rts-tests/src/gc/compacting/mark_stack.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::memory::TestMemory; - -use motoko_rts::gc::mark_compact::mark_stack::{ - alloc_mark_stack, free_mark_stack, grow_stack, pop_mark_stack, push_mark_stack, - INIT_STACK_SIZE, STACK_BASE, STACK_PTR, STACK_TOP, -}; -use motoko_rts::memory::Memory; -use motoko_rts::types::*; - -use proptest::test_runner::{Config, TestCaseError, TestCaseResult, TestRunner}; - -pub unsafe fn test() { - println!(" Testing mark stack ..."); - - test_push_pop(); - test_grow_stack(); -} - -fn test_push_pop() { - println!(" Testing push/pop"); - - let mut proptest_runner = TestRunner::new(Config { - cases: 100, - failure_persistence: None, - ..Default::default() - }); - - proptest_runner - .run(&(0u32..1000u32), |n_objs| { - let mut mem = TestMemory::new(Words(1024 * 1024)); - test_(&mut mem, n_objs) - }) - .unwrap(); -} - -static TAGS: [Tag; 14] = [ - TAG_OBJECT, - TAG_OBJ_IND, - TAG_ARRAY, - TAG_BITS64, - TAG_MUTBOX, - TAG_CLOSURE, - TAG_SOME, - TAG_VARIANT, - TAG_BLOB, - TAG_FWD_PTR, - TAG_BITS32, - TAG_BIGINT, - TAG_CONCAT, - TAG_NULL, -]; - -fn test_(mem: &mut M, n_objs: u32) -> TestCaseResult { - let objs: Vec = (0..n_objs).collect(); - - unsafe { - alloc_mark_stack(mem); - - for obj in &objs { - push_mark_stack(mem, *obj as usize, TAGS[(*obj as usize) % TAGS.len()]); - } - - for obj in objs.iter().copied().rev() { - let popped = pop_mark_stack(); - if popped != Some((obj as usize, TAGS[(obj as usize) % TAGS.len()])) { - free_mark_stack(); - return Err(TestCaseError::Fail( - format!( - "Unexpected object popped, expected={:?}, popped={:?}", - obj, popped - ) - .into(), - )); - } - } - - free_mark_stack(); - } - - Ok(()) -} - -unsafe fn test_grow_stack() { - println!(" Testing grow_stack"); - - // Allow doubling twice - let mut mem = TestMemory::new( - size_of::() + INIT_STACK_SIZE + INIT_STACK_SIZE + INIT_STACK_SIZE * 2, - ); - - alloc_mark_stack(&mut mem); - - let mut current_size = INIT_STACK_SIZE.as_usize(); - assert_eq!(STACK_BASE.add(current_size), STACK_TOP); - assert_eq!(STACK_BASE, STACK_PTR); - - grow_stack(&mut mem); - current_size *= 2; - assert_eq!(STACK_BASE.add(current_size), STACK_TOP); - assert_eq!(STACK_BASE, STACK_PTR); - - grow_stack(&mut mem); - current_size *= 2; - assert_eq!(STACK_BASE.add(current_size), STACK_TOP); - assert_eq!(STACK_BASE, STACK_PTR); -} diff --git a/rts/motoko-rts-tests/src/gc/generational.rs b/rts/motoko-rts-tests/src/gc/generational.rs deleted file mode 100644 index 5a86ed7cf10..00000000000 --- a/rts/motoko-rts-tests/src/gc/generational.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod mark_stack; -mod remembered_set; - -pub fn test() { - println!("Testing generational GC ..."); - unsafe { - mark_stack::test(); - remembered_set::test(); - } -} diff --git a/rts/motoko-rts-tests/src/gc/generational/mark_stack.rs b/rts/motoko-rts-tests/src/gc/generational/mark_stack.rs deleted file mode 100644 index 959b913e87d..00000000000 --- a/rts/motoko-rts-tests/src/gc/generational/mark_stack.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::memory::TestMemory; - -use motoko_rts::gc::generational::mark_stack::{ - alloc_mark_stack, free_mark_stack, grow_stack, pop_mark_stack, push_mark_stack, - INIT_STACK_SIZE, STACK_BASE, STACK_PTR, STACK_TOP, -}; -use motoko_rts::memory::Memory; -use motoko_rts::types::*; - -use proptest::test_runner::{Config, TestCaseError, TestCaseResult, TestRunner}; - -pub unsafe fn test() { - println!(" Testing mark stack ..."); - - test_push_pop(); - test_grow_stack(); -} - -fn test_push_pop() { - println!(" Testing push/pop"); - - let mut proptest_runner = TestRunner::new(Config { - cases: 100, - failure_persistence: None, - ..Default::default() - }); - - proptest_runner - .run(&(0u32..1000u32), |n_objs| { - let mut mem = TestMemory::new(Words(1024 * 1024)); - test_(&mut mem, n_objs) - }) - .unwrap(); -} - -fn test_(mem: &mut M, n_objs: u32) -> TestCaseResult { - let objs: Vec = (0..n_objs).collect(); - - unsafe { - alloc_mark_stack(mem); - - for obj in &objs { - push_mark_stack(mem, *obj as usize); - } - - for obj in objs.iter().copied().rev() { - let popped = pop_mark_stack(); - if popped != Some(obj as usize) { - free_mark_stack(); - return Err(TestCaseError::Fail( - format!( - "Unexpected object popped, expected={:?}, popped={:?}", - obj, popped - ) - .into(), - )); - } - } - - free_mark_stack(); - } - - Ok(()) -} - -unsafe fn test_grow_stack() { - println!(" Testing grow_stack"); - - // Allow doubling twice - let mut mem = TestMemory::new( - size_of::() + INIT_STACK_SIZE + INIT_STACK_SIZE + INIT_STACK_SIZE * 2, - ); - - alloc_mark_stack(&mut mem); - - let mut current_size = INIT_STACK_SIZE.as_usize(); - assert_eq!(STACK_BASE.add(current_size), STACK_TOP); - assert_eq!(STACK_BASE, STACK_PTR); - - grow_stack(&mut mem); - current_size *= 2; - assert_eq!(STACK_BASE.add(current_size), STACK_TOP); - assert_eq!(STACK_BASE, STACK_PTR); - - grow_stack(&mut mem); - current_size *= 2; - assert_eq!(STACK_BASE.add(current_size), STACK_TOP); - assert_eq!(STACK_BASE, STACK_PTR); -} diff --git a/rts/motoko-rts-tests/src/gc/generational/remembered_set.rs b/rts/motoko-rts-tests/src/gc/generational/remembered_set.rs deleted file mode 100644 index c71de353b15..00000000000 --- a/rts/motoko-rts-tests/src/gc/generational/remembered_set.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::collections::HashSet; - -use crate::memory::TestMemory; -use motoko_rts::gc::generational::remembered_set::{ - RememberedSet, INITIAL_TABLE_LENGTH, OCCUPATION_THRESHOLD_PERCENT, -}; -use motoko_rts::types::{Value, Words}; - -const GROW_LIMIT: u32 = INITIAL_TABLE_LENGTH * OCCUPATION_THRESHOLD_PERCENT / 100; - -pub unsafe fn test() { - println!(" Testing remembered set ..."); - - test_remembered_set(0); - test_remembered_set(1); - test_remembered_set(INITIAL_TABLE_LENGTH / 2); - test_remembered_set(GROW_LIMIT - 1); - test_remembered_set(GROW_LIMIT); - test_remembered_set(GROW_LIMIT + 1); - test_remembered_set(INITIAL_TABLE_LENGTH); - test_remembered_set(2 * GROW_LIMIT - 1); - test_remembered_set(2 * GROW_LIMIT); - test_remembered_set(2 * GROW_LIMIT + 1); - test_remembered_set(128 * GROW_LIMIT); -} - -unsafe fn test_remembered_set(amount: u32) { - test_insert_iterate(amount); - test_duplicates(amount); - test_collisions(amount); -} - -unsafe fn test_insert_iterate(amount: u32) { - let mut mem = TestMemory::new(Words(2 * amount + 1024 * 1024)); - - let mut remembered_set = RememberedSet::new(&mut mem); - let mut test_set: HashSet = HashSet::new(); - // start at 1 since 0 is the null ptr and not stored in the remembered set - for value in 1..amount + 1 { - remembered_set.insert(&mut mem, Value::from_raw(value)); - test_set.insert(value); - } - - let mut iterator = remembered_set.iterate(); - for _ in 1..amount + 1 { - assert!(iterator.has_next()); - let value = iterator.current().get_raw(); - assert!(test_set.contains(&value)); - iterator.next(); - } - assert!(!iterator.has_next()); -} - -unsafe fn test_duplicates(amount: u32) { - let mut mem = TestMemory::new(Words(2 * amount + 1024 * 1024)); - - let mut remembered_set = RememberedSet::new(&mut mem); - // start at 1 since 0 is the null ptr and not stored in the remembered set - for value in 1..amount + 1 { - remembered_set.insert(&mut mem, Value::from_raw(value)); - } - - let count = remembered_set.count(); - for value in 1..amount + 1 { - remembered_set.insert(&mut mem, Value::from_raw(value)); - assert_eq!(remembered_set.count(), count); - } -} - -unsafe fn test_collisions(amount: u32) { - let mut mem = TestMemory::new(Words(2 * amount + 1024 * 1024)); - - let mut remembered_set = RememberedSet::new(&mut mem); - let mut test_set: HashSet = HashSet::new(); - - // start at 1 since 0 is the null ptr and not stored in the remembered set - for index in 1..amount + 1 { - const FACTOR: u32 = 1024 * 1024; - let value = if index <= u32::MAX / FACTOR { - index * FACTOR - } else { - index - }; - remembered_set.insert(&mut mem, Value::from_raw(value)); - test_set.insert(value); - } - - let mut iterator = remembered_set.iterate(); - for _ in 1..amount + 1 { - assert!(iterator.has_next()); - let value = iterator.current().get_raw(); - assert!(test_set.contains(&value)); - iterator.next(); - } - assert!(!iterator.has_next()); -} diff --git a/rts/motoko-rts-tests/src/gc/heap.rs b/rts/motoko-rts-tests/src/gc/heap.rs index d343b351d98..195101f8fb1 100644 --- a/rts/motoko-rts-tests/src/gc/heap.rs +++ b/rts/motoko-rts-tests/src/gc/heap.rs @@ -1,4 +1,4 @@ -use super::utils::{make_pointer, make_scalar, write_word, ObjectIdx, GC, WORD_SIZE}; +use super::utils::{make_pointer, make_scalar, write_word, ObjectIdx, WORD_SIZE}; use motoko_rts::memory::Memory; use motoko_rts::types::*; @@ -8,7 +8,8 @@ use std::convert::TryFrom; use std::rc::Rc; use fxhash::{FxHashMap, FxHashSet}; -use motoko_rts_macros::*; + +use motoko_rts::gc::incremental::partitioned_heap::PARTITION_SIZE; /// Represents Motoko heaps. Reference counted (implements `Clone`) so we can clone and move values /// of this type to GC callbacks. @@ -38,14 +39,12 @@ impl MotokoHeap { map: &[(ObjectIdx, Vec)], roots: &[ObjectIdx], continuation_table: &[ObjectIdx], - gc: GC, ) -> MotokoHeap { MotokoHeap { inner: Rc::new(RefCell::new(MotokoHeapInner::new( map, roots, continuation_table, - gc, ))), } } @@ -66,25 +65,6 @@ impl MotokoHeap { self.inner.borrow().heap_ptr_address() } - /// Update the heap pointer given as an address in the current process. - #[non_incremental_gc] - pub fn set_heap_ptr_address(&self, address: usize) { - self.inner.borrow_mut().set_heap_ptr_address(address) - } - - /// Get the last heap pointer, as address in the current process. The address can be used to mutate - /// the heap. - #[non_incremental_gc] - pub fn last_ptr_address(&self) -> usize { - self.inner.borrow().last_ptr_address() - } - - /// Update the last heap pointer given as an address in the current process. - #[non_incremental_gc] - pub fn set_last_ptr_address(&self, address: usize) { - self.inner.borrow_mut().set_last_ptr_address(address) - } - /// Get the beginning of dynamic heap, as an address in the current process pub fn heap_base_address(&self) -> usize { self.inner.borrow().heap_base_address() @@ -178,18 +158,6 @@ impl MotokoHeapInner { self.heap_ptr_offset = self.address_to_offset(address); } - /// Get last heap pointer (i.e. where the dynamic heap ends last GC run) in the process's address space - #[non_incremental_gc] - fn last_ptr_address(&self) -> usize { - self.offset_to_address(self._heap_ptr_last) - } - - /// Set last heap pointer - #[non_incremental_gc] - fn set_last_ptr_address(&mut self, address: usize) { - self._heap_ptr_last = self.address_to_offset(address); - } - /// Get the address of the variable pointing to the static root array. fn static_root_array_variable_address(&self) -> usize { self.offset_to_address(self.static_root_array_variable_offset) @@ -204,7 +172,6 @@ impl MotokoHeapInner { map: &[(ObjectIdx, Vec)], roots: &[ObjectIdx], continuation_table: &[ObjectIdx], - gc: GC, ) -> MotokoHeapInner { // Check test correctness: an object should appear at most once in `map` { @@ -242,12 +209,7 @@ impl MotokoHeapInner { let total_heap_size_bytes = root_pointers_size_bytes + dynamic_heap_size_bytes; - let heap_size = heap_size_for_gc( - gc, - root_pointers_size_bytes, - dynamic_heap_size_bytes, - map.len(), - ); + let heap_size = heap_size_for_gc(); // The Worst-case unalignment w.r.t. 32-byte alignment is 28 (assuming // that we have general word alignment). So we over-allocate 28 bytes. @@ -286,12 +248,6 @@ impl MotokoHeapInner { } } - #[non_incremental_gc] - unsafe fn alloc_words(&mut self, n: Words) -> Value { - self.linear_alloc_words(n) - } - - #[incremental_gc] unsafe fn alloc_words(&mut self, n: Words) -> Value { let mut dummy_memory = DummyMemory {}; let result = @@ -335,65 +291,9 @@ impl Memory for DummyMemory { } /// Compute the size of the heap to be allocated for the GC test. -#[non_incremental_gc] -fn heap_size_for_gc( - gc: GC, - static_heap_size_bytes: usize, - dynamic_heap_size_bytes: usize, - n_objects: usize, -) -> usize { - let total_heap_size_bytes = static_heap_size_bytes + dynamic_heap_size_bytes; - match gc { - GC::Copying => { - let to_space_bytes = dynamic_heap_size_bytes; - total_heap_size_bytes + to_space_bytes - } - GC::MarkCompact => { - let bitmap_size_bytes = { - let dynamic_heap_bytes = Bytes(dynamic_heap_size_bytes as u32); - // `...to_words().to_bytes()` below effectively rounds up heap size to word size - // then gets the bytes - let dynamic_heap_words = dynamic_heap_bytes.to_words(); - let mark_bit_bytes = dynamic_heap_words.to_bytes(); - - // The bitmap implementation rounds up to 64-bits to be able to read as many - // bits as possible in one instruction and potentially skip 64 words in the - // heap with single 64-bit comparison - (((mark_bit_bytes.as_u32() + 7) / 8) * 8) + size_of::().to_bytes().as_u32() - }; - // In the worst case the entire heap will be pushed to the mark stack, but in tests - // we limit the size - let mark_stack_words = n_objects.clamp( - motoko_rts::gc::mark_compact::mark_stack::INIT_STACK_SIZE.as_usize(), - super::utils::MAX_MARK_STACK_SIZE, - ) + size_of::().as_usize(); - - total_heap_size_bytes + bitmap_size_bytes as usize + (mark_stack_words * WORD_SIZE) - } - GC::Generational => { - const ROUNDS: usize = 3; - const REMEMBERED_SET_MAXIMUM_SIZE: usize = 1024 * 1024 * WORD_SIZE; - let size = heap_size_for_gc( - GC::MarkCompact, - static_heap_size_bytes, - dynamic_heap_size_bytes, - n_objects, - ); - size + ROUNDS * REMEMBERED_SET_MAXIMUM_SIZE - } - } -} - -#[incremental_gc] fn heap_size_for_gc( - gc: GC, - _static_heap_size_bytes: usize, - _dynamic_heap_size_bytes: usize, - _n_objects: usize, ) -> usize { - match gc { - GC::Incremental => 3 * motoko_rts::gc::incremental::partitioned_heap::PARTITION_SIZE, - } + 3 * PARTITION_SIZE } /// Given a heap description (as a map from objects to objects), and the dynamic part of the heap @@ -406,7 +306,6 @@ fn create_dynamic_heap( continuation_table: &[ObjectIdx], dynamic_heap: &mut [u8], ) -> (u32, u32) { - let incremental = cfg!(feature = "incremental_gc"); let heap_start = dynamic_heap.as_ptr() as usize; // Maps objects to their addresses @@ -423,11 +322,9 @@ fn create_dynamic_heap( write_word(dynamic_heap, heap_offset, TAG_ARRAY); heap_offset += WORD_SIZE; - if incremental { - write_word(dynamic_heap, heap_offset, make_pointer(address)); // forwarding pointer - heap_offset += WORD_SIZE; - } - + write_word(dynamic_heap, heap_offset, make_pointer(address)); // forwarding pointer + heap_offset += WORD_SIZE; + // Store length: idx + refs write_word( dynamic_heap, @@ -478,11 +375,9 @@ fn create_dynamic_heap( write_word(dynamic_heap, heap_offset, TAG_MUTBOX); heap_offset += WORD_SIZE; - if incremental { - write_word(dynamic_heap, heap_offset, make_pointer(mutbox_address)); - heap_offset += WORD_SIZE; - } - + write_word(dynamic_heap, heap_offset, make_pointer(mutbox_address)); + heap_offset += WORD_SIZE; + let root_ptr = *object_addrs.get(root_id).unwrap(); write_word(dynamic_heap, heap_offset, make_pointer(root_ptr as u32)); heap_offset += WORD_SIZE; @@ -493,15 +388,13 @@ fn create_dynamic_heap( write_word(dynamic_heap, heap_offset, TAG_ARRAY); heap_offset += WORD_SIZE; - if incremental { - write_word( - dynamic_heap, - heap_offset, - make_pointer(static_root_array_address), - ); - heap_offset += WORD_SIZE; - } - + write_word( + dynamic_heap, + heap_offset, + make_pointer(static_root_array_address), + ); + heap_offset += WORD_SIZE; + assert_eq!(static_roots.len(), root_mutboxes.len()); write_word(dynamic_heap, heap_offset, root_mutboxes.len() as u32); heap_offset += WORD_SIZE; @@ -517,15 +410,13 @@ fn create_dynamic_heap( write_word(dynamic_heap, heap_offset, TAG_ARRAY); heap_offset += WORD_SIZE; - if incremental { - write_word( - dynamic_heap, - heap_offset, - make_pointer(continuation_table_address), - ); - heap_offset += WORD_SIZE; - } - + write_word( + dynamic_heap, + heap_offset, + make_pointer(continuation_table_address), + ); + heap_offset += WORD_SIZE; + write_word(dynamic_heap, heap_offset, continuation_table.len() as u32); heap_offset += WORD_SIZE; diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs index 73bd88e2288..9a2c23e9783 100644 --- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs +++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs @@ -7,7 +7,7 @@ use motoko_rts::{ use crate::gc::{ heap::MotokoHeap, - utils::{ObjectIdx, GC, WORD_SIZE}, + utils::{ObjectIdx, WORD_SIZE}, }; pub unsafe fn test() { @@ -17,7 +17,7 @@ pub unsafe fn test() { let root_ids = [2, 4, 6, 8]; let continuation_ids = [3, 5, 7]; - let heap = MotokoHeap::new(&object_map, &root_ids, &continuation_ids, GC::Incremental); + let heap = MotokoHeap::new(&object_map, &root_ids, &continuation_ids); check_visit_static_roots(&heap, &root_ids); check_visit_continuation_table(&heap, &continuation_ids); } diff --git a/rts/motoko-rts-tests/src/gc/utils.rs b/rts/motoko-rts-tests/src/gc/utils.rs index a168e726f68..48d3e6e9063 100644 --- a/rts/motoko-rts-tests/src/gc/utils.rs +++ b/rts/motoko-rts-tests/src/gc/utils.rs @@ -1,5 +1,4 @@ use byteorder::{ReadBytesExt, WriteBytesExt, LE}; -use motoko_rts_macros::*; /// A unique object index, used in heap descriptions. /// @@ -10,32 +9,6 @@ pub type ObjectIdx = u32; /// Same as RTS `WORD_SIZE`, but `usize` pub const WORD_SIZE: usize = motoko_rts::constants::WORD_SIZE as usize; -// Max allowed size for the mark stack in mark-compact GC tests -#[non_incremental_gc] -pub const MAX_MARK_STACK_SIZE: usize = 100; - -/// Enum for the GC implementations. GC functions are generic so we can't put them into arrays or -/// other data types, we use this type instead. -#[non_incremental_gc] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum GC { - Copying, - MarkCompact, - Generational, -} - -#[incremental_gc] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum GC { - Incremental, -} - -#[non_incremental_gc] -pub static GC_IMPLS: [GC; 3] = [GC::Copying, GC::MarkCompact, GC::Generational]; - -#[incremental_gc] -pub static GC_IMPLS: [GC; 1] = [GC::Incremental]; - /// Read a little-endian (Wasm) word from given offset pub fn read_word(heap: &[u8], offset: usize) -> u32 { (&heap[offset..]).read_u32::().unwrap() diff --git a/rts/motoko-rts-tests/src/memory.rs b/rts/motoko-rts-tests/src/memory.rs index 351f260f449..9f725a015b8 100644 --- a/rts/motoko-rts-tests/src/memory.rs +++ b/rts/motoko-rts-tests/src/memory.rs @@ -1,6 +1,5 @@ use motoko_rts::memory::Memory; use motoko_rts::types::{Value, Words}; -use motoko_rts_macros::*; pub struct TestMemory { heap: Box<[u8]>, @@ -15,22 +14,18 @@ impl TestMemory { TestMemory { heap, hp } } - #[incremental_gc] pub fn heap_base(&self) -> usize { self.heap.as_ptr() as usize } - #[incremental_gc] pub fn heap_end(&self) -> usize { self.heap_base() + self.heap.len() } - #[incremental_gc] pub fn heap_pointer(&self) -> usize { self.hp } - #[incremental_gc] pub fn set_heap_pointer(&mut self, heap_pointer: usize) { assert!(heap_pointer >= self.heap_base()); assert!(heap_pointer <= self.heap_end()); diff --git a/rts/motoko-rts-tests/src/stream.rs b/rts/motoko-rts-tests/src/stream.rs index 3321ea7142f..f830ee10e81 100644 --- a/rts/motoko-rts-tests/src/stream.rs +++ b/rts/motoko-rts-tests/src/stream.rs @@ -4,7 +4,6 @@ use crate::memory::TestMemory; use motoko_rts::stream::alloc_stream; use motoko_rts::types::{size_of, Blob, Bytes, Stream, Value, Words}; -use motoko_rts_macros::is_incremental_gc; pub unsafe fn test() { println!("Testing streaming ..."); @@ -34,7 +33,7 @@ pub unsafe fn test() { println!(" Testing stream decay"); let blob = stream.as_stream().split(); assert_eq!(blob.as_blob().len(), Bytes(STREAM_SMALL_SIZE)); - const REMAINDER: u32 = if is_incremental_gc!() { 20 } else { 24 }; + const REMAINDER: u32 = 20; assert_eq!(stream.as_blob().len(), Bytes(REMAINDER)); println!(" Testing stream filling (blocks)"); diff --git a/rts/motoko-rts/Cargo.toml b/rts/motoko-rts/Cargo.toml index 3d1e3c26ac8..d29a7d4c6ae 100644 --- a/rts/motoko-rts/Cargo.toml +++ b/rts/motoko-rts/Cargo.toml @@ -25,9 +25,6 @@ ic = [] # This feature enables extensive memory sanity checks in the incremental GC. memory_check = [] -# Incremental GC, using the extended object header containing the forwarding pointer. -incremental_gc = [] - [dependencies] libc = { version = "0.2.139", default_features = false } motoko-rts-macros = { path = "../motoko-rts-macros" } diff --git a/rts/motoko-rts/native/Cargo.toml b/rts/motoko-rts/native/Cargo.toml index c72c906b9b0..5a31d3798f4 100644 --- a/rts/motoko-rts/native/Cargo.toml +++ b/rts/motoko-rts/native/Cargo.toml @@ -8,9 +8,6 @@ edition = "2018" crate-type = ["rlib"] path = "../src/lib.rs" -[features] -incremental_gc = [] - [dependencies] libc = { version = "0.2.112", default_features = false } motoko-rts-macros = { path = "../../motoko-rts-macros" } diff --git a/rts/motoko-rts/src/barriers.rs b/rts/motoko-rts/src/barriers.rs index 8c4adb9ee47..7a3e7759070 100644 --- a/rts/motoko-rts/src/barriers.rs +++ b/rts/motoko-rts/src/barriers.rs @@ -1,34 +1,13 @@ use crate::{memory::Memory, types::Value}; -use motoko_rts_macros::*; -#[incremental_gc] pub unsafe fn init_with_barrier(_mem: &mut M, location: *mut Value, value: Value) { *location = value.forward_if_possible(); } -#[non_incremental_gc] -pub unsafe fn init_with_barrier(mem: &mut M, location: *mut Value, value: Value) { - *location = value; - crate::gc::generational::write_barrier::post_write_barrier(mem, location as usize); -} - -#[incremental_gc] pub unsafe fn write_with_barrier(mem: &mut M, location: *mut Value, value: Value) { crate::gc::incremental::barriers::write_with_barrier(mem, location, value); } -#[non_incremental_gc] -pub unsafe fn write_with_barrier(mem: &mut M, location: *mut Value, value: Value) { - *location = value; - crate::gc::generational::write_barrier::post_write_barrier(mem, location as usize); -} - -#[incremental_gc] pub unsafe fn allocation_barrier(new_object: Value) -> Value { crate::gc::incremental::barriers::allocation_barrier(new_object) } - -#[non_incremental_gc] -pub unsafe fn allocation_barrier(new_object: Value) -> Value { - new_object -} diff --git a/rts/motoko-rts/src/gc.rs b/rts/motoko-rts/src/gc.rs index f3e1cde2ddd..a2b3e2acd74 100644 --- a/rts/motoko-rts/src/gc.rs +++ b/rts/motoko-rts/src/gc.rs @@ -1,26 +1 @@ -#[non_incremental_gc] -pub mod copying; -#[non_incremental_gc] -pub mod generational; -#[incremental_gc] pub mod incremental; -#[non_incremental_gc] -pub mod mark_compact; - -use motoko_rts_macros::*; - -#[cfg(feature = "ic")] -#[non_incremental_gc] -unsafe fn should_do_gc(max_live: crate::types::Bytes) -> bool { - use crate::memory::ic::linear_memory::{get_hp_unskewed, LAST_HP}; - - // A factor of last heap size. We allow at most this much allocation before doing GC. - const HEAP_GROWTH_FACTOR: f64 = 1.5; - - let heap_limit = core::cmp::min( - (f64::from(LAST_HP as u32) * HEAP_GROWTH_FACTOR) as u64, - (u64::from(LAST_HP as u32) + max_live.0) / 2, - ); - - u64::from(get_hp_unskewed() as u32) >= heap_limit -} diff --git a/rts/motoko-rts/src/gc/copying.rs b/rts/motoko-rts/src/gc/copying.rs deleted file mode 100644 index 8a2376a54a9..00000000000 --- a/rts/motoko-rts/src/gc/copying.rs +++ /dev/null @@ -1,216 +0,0 @@ -use crate::constants::WORD_SIZE; -use crate::mem_utils::{memcpy_bytes, memcpy_words}; -use crate::memory::Memory; -use crate::types::*; - -use motoko_rts_macros::ic_mem_fn; - -#[no_mangle] -#[cfg(feature = "ic")] -#[allow(unreachable_code)] -pub unsafe extern "C" fn initialize_copying_gc() { - panic!("Copying GC is not supported with the persistent heap"); - crate::memory::ic::linear_memory::initialize(); -} - -#[ic_mem_fn(ic_only)] -unsafe fn schedule_copying_gc(mem: &mut M) { - // Half of the heap. - // NB. This expression is evaluated in compile time to a constant. - let max_live: Bytes = - Bytes(u64::from((crate::constants::WASM_HEAP_SIZE / 2).as_u32()) * u64::from(WORD_SIZE)); - - if super::should_do_gc(max_live) { - copying_gc(mem); - } -} - -#[ic_mem_fn(ic_only)] -unsafe fn copying_gc(mem: &mut M) { - use crate::memory::ic::{self, linear_memory, HEAP_START}; - - copying_gc_internal( - mem, - HEAP_START, - // get_hp - || linear_memory::get_hp_unskewed(), - // set_hp - |hp| linear_memory::set_hp_unskewed(hp), - ic::linear_memory::get_static_roots(), - crate::continuation_table::continuation_table_loc(), - // note_live_size - |live_size| ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size), - // note_reclaimed - |reclaimed| linear_memory::RECLAIMED += Bytes(u64::from(reclaimed.as_u32())), - ); - - linear_memory::LAST_HP = linear_memory::get_hp_unskewed(); -} - -pub unsafe fn copying_gc_internal< - M: Memory, - GetHp: Fn() -> usize, - SetHp: FnMut(usize), - NoteLiveSize: Fn(Bytes), - NoteReclaimed: Fn(Bytes), ->( - mem: &mut M, - heap_base: usize, - get_hp: GetHp, - mut set_hp: SetHp, - static_roots: Value, - continuation_table_ptr_loc: *mut Value, - note_live_size: NoteLiveSize, - note_reclaimed: NoteReclaimed, -) { - let begin_from_space = heap_base as usize; - let end_from_space = get_hp(); - let begin_to_space = end_from_space; - - let static_roots = static_roots.as_array(); - - // Evacuate roots - evac_static_roots(mem, begin_from_space, begin_to_space, static_roots); - - if (*continuation_table_ptr_loc).is_ptr() { - evac( - mem, - begin_from_space, - begin_to_space, - continuation_table_ptr_loc as usize, - ); - } - - // Scavenge to-space - let mut p = begin_to_space; - while p < get_hp() { - let size = block_size(p); - scav(mem, begin_from_space, begin_to_space, Value::from_ptr(p)); - p += size.to_bytes().as_usize(); - } - - let end_to_space = get_hp(); - - // Note the stats - let new_live_size = end_to_space - begin_to_space; - note_live_size(Bytes(new_live_size as u32)); - - let reclaimed = (end_from_space - begin_from_space) - (end_to_space - begin_to_space); - note_reclaimed(Bytes(reclaimed as u32)); - - // Copy to-space to the beginning of from-space - memcpy_bytes( - begin_from_space, - begin_to_space, - Bytes((end_to_space - begin_to_space) as u32), - ); - - // Reset the heap pointer - let new_hp = begin_from_space + (end_to_space - begin_to_space); - set_hp(new_hp); -} - -/// Evacuate (copy) an object in from-space to to-space. -/// -/// Arguments: -/// -/// - begin_from_space: Where the dynamic heap starts. Used for two things: -/// -/// - An object is static if its address is below this value. These objects either don't point to -/// dynamic heap, or are listed in static_roots array. Objects in static_roots are scavenged -/// separately in `evac_static_roots` below. So we skip these objects here. -/// -/// - After all objects are evacuated we move to-space to from-space, to be able to do that the -/// pointers need to point to their (eventual) locations in from-space, which is calculated with -/// `address_in_to_space - begin_to_space + begin_from_space`. -/// -/// - begin_to_space: Where to-space starts. See above for how this is used. -/// -/// - ptr_loc: Location of the object to evacuate, e.g. an object field address. -/// -unsafe fn evac( - mem: &mut M, - begin_from_space: usize, - begin_to_space: usize, - ptr_loc: usize, -) { - // Field holds a skewed pointer to the object to evacuate - let ptr_loc = ptr_loc as *mut Value; - - // Check object alignment to avoid undefined behavior. See also static_checks module. - debug_assert_eq!((*ptr_loc).get_ptr() as u32 % WORD_SIZE, 0); - - // Update the field if the object is already evacuated - if (*ptr_loc).tag() == TAG_FWD_PTR { - let block = (*ptr_loc).get_ptr() as *const FwdPtr; - let fwd = (*block).fwd; - *ptr_loc = fwd; - return; - } - - let obj = (*ptr_loc).get_ptr() as *mut Obj; - - let obj_size = block_size(obj as usize); - - // Allocate space in to-space for the object - let obj_addr = mem.alloc_words(obj_size).get_ptr(); - - // Copy object to to-space - memcpy_words(obj_addr, obj as usize, obj_size); - - // Final location of the object after copying to-space back to from-space - let obj_loc = (obj_addr - begin_to_space) + begin_from_space; - - // Set forwarding pointer - let fwd = obj as *mut FwdPtr; - (*fwd).tag = TAG_FWD_PTR; - (*fwd).fwd = Value::from_ptr(obj_loc); - - // Update evacuated field - *ptr_loc = Value::from_ptr(obj_loc); - - // Update forwarding pointer - let to_space_obj = obj_addr as *mut Obj; - debug_assert!(obj_size.as_usize() > size_of::().as_usize()); - debug_assert!(to_space_obj.tag() >= TAG_OBJECT && to_space_obj.tag() <= TAG_NULL); -} - -unsafe fn scav( - mem: &mut M, - begin_from_space: usize, - begin_to_space: usize, - block: Value, -) { - if !block.is_obj() { - // Skip `OneWordFiller` and `FreeSpace` that have no regular object header. - return; - } - let obj = block.get_ptr() as *mut Obj; - - crate::visitor::visit_pointer_fields( - mem, - obj, - obj.tag(), - begin_from_space, - |mem, field_addr| { - evac(mem, begin_from_space, begin_to_space, field_addr as usize); - }, - |_, _, arr| arr.len(), - ); -} - -// We have a special evacuation routine for "static roots" array: we don't evacuate elements of -// "static roots", we just scavenge them. -unsafe fn evac_static_roots( - mem: &mut M, - begin_from_space: usize, - begin_to_space: usize, - roots: *mut Array, -) { - // The array and the objects pointed by the array are all static so we don't evacuate them. We - // only evacuate fields of objects in the array. - for i in 0..roots.len() { - let obj = roots.get(i); - scav(mem, begin_from_space, begin_to_space, obj); - } -} diff --git a/rts/motoko-rts/src/gc/generational.rs b/rts/motoko-rts/src/gc/generational.rs deleted file mode 100644 index b9083ac2872..00000000000 --- a/rts/motoko-rts/src/gc/generational.rs +++ /dev/null @@ -1,517 +0,0 @@ -//! Generational compacting GC. -//! Two generations: young and old. -//! Frequent collection of young generation, sporadic full collection (old + young). -//! Young generation collection requires an extra root set of old-to-young pointers. -//! A write barrier catches all pointers leading from old to young generation. -//! Compaction is based on the existing Motoko RTS threaded mark & compact GC. - -pub mod mark_stack; -pub mod remembered_set; -#[cfg(feature = "memory_check")] -mod sanity_checks; -pub mod write_barrier; - -use crate::gc::generational::mark_stack::{alloc_mark_stack, push_mark_stack}; -use crate::gc::mark_compact::bitmap::{ - alloc_bitmap, free_bitmap, get_bit, iter_bits, set_bit, BITMAP_ITER_END, -}; - -use crate::constants::WORD_SIZE; -use crate::mem_utils::memcpy_words; -use crate::memory::Memory; -use crate::types::*; -use crate::visitor::{pointer_to_dynamic_heap, visit_pointer_fields}; - -use motoko_rts_macros::ic_mem_fn; - -use self::mark_stack::{free_mark_stack, pop_mark_stack}; -use self::write_barrier::REMEMBERED_SET; - -#[ic_mem_fn(ic_only)] -#[allow(unreachable_code, unused_variables)] -unsafe fn initialize_generational_gc(mem: &mut M) { - panic!("Generational GC is not supported with the persistent heap"); - crate::memory::ic::linear_memory::initialize(); - write_barrier::init_generational_write_barrier(mem); -} - -#[ic_mem_fn(ic_only)] -unsafe fn schedule_generational_gc(mem: &mut M) { - let limits = get_limits(); - if decide_strategy(&limits).is_some() { - generational_gc(mem); - } -} - -#[ic_mem_fn(ic_only)] -unsafe fn generational_gc(mem: &mut M) { - use crate::memory::ic; - - let old_limits = get_limits(); - let roots = Roots { - static_roots: ic::linear_memory::get_static_roots(), - continuation_table_ptr_loc: crate::continuation_table::continuation_table_loc(), - }; - let heap = Heap { - mem, - limits: get_limits(), - roots, - }; - let strategy = decide_strategy(&heap.limits); - - let strategy = strategy.unwrap_or(Strategy::Young); - let mut gc = GenerationalGC::new(heap, strategy); - - #[cfg(feature = "memory_check")] - sanity_checks::verify_snapshot(&gc.heap, false); - - gc.run(); - - let new_limits = &gc.heap.limits; - set_limits(&gc.heap.limits); - update_statistics(&old_limits, new_limits); - update_strategy(strategy, new_limits); - - #[cfg(feature = "memory_check")] - sanity_checks::check_memory(&gc.heap.limits, &gc.heap.roots); - #[cfg(feature = "memory_check")] - sanity_checks::take_snapshot(&mut gc.heap); - - write_barrier::init_generational_write_barrier(gc.heap.mem); -} - -#[cfg(feature = "ic")] -unsafe fn get_limits() -> Limits { - use crate::memory::ic::{linear_memory, HEAP_START}; - assert!(linear_memory::LAST_HP >= HEAP_START); - Limits { - base: HEAP_START, - last_free: linear_memory::LAST_HP, - free: (linear_memory::get_hp_unskewed()), - } -} - -#[cfg(feature = "ic")] -unsafe fn set_limits(limits: &Limits) { - use crate::memory::ic::linear_memory; - linear_memory::set_hp_unskewed(limits.free); - linear_memory::LAST_HP = limits.free; -} - -#[cfg(feature = "ic")] -unsafe fn update_statistics(old_limits: &Limits, new_limits: &Limits) { - use crate::memory::ic::{self, linear_memory}; - let live_size = Bytes(new_limits.free as u32 - new_limits.base as u32); - ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size); - linear_memory::RECLAIMED += Bytes(old_limits.free as u64 - new_limits.free as u64); -} - -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum Strategy { - Young, - Full, -} - -#[cfg(feature = "ic")] -static mut OLD_GENERATION_THRESHOLD: usize = 32 * 1024 * 1024; - -#[cfg(feature = "ic")] -static mut PASSED_CRITICAL_LIMIT: bool = false; - -#[cfg(feature = "ic")] -const CRITICAL_MEMORY_LIMIT: usize = (4096 - 512) * 1024 * 1024; - -#[cfg(feature = "ic")] -unsafe fn decide_strategy(limits: &Limits) -> Option { - const YOUNG_GENERATION_THRESHOLD: usize = 8 * 1024 * 1024; - - assert!(limits.base <= limits.last_free); - let old_generation_size = limits.last_free - limits.base; - assert!(limits.last_free <= limits.free); - let young_generation_size = limits.free - limits.last_free; - - if limits.free >= CRITICAL_MEMORY_LIMIT && !PASSED_CRITICAL_LIMIT { - PASSED_CRITICAL_LIMIT = true; - Some(Strategy::Full) - } else if old_generation_size > OLD_GENERATION_THRESHOLD { - Some(Strategy::Full) - } else if young_generation_size > YOUNG_GENERATION_THRESHOLD { - Some(Strategy::Young) - } else { - None - } -} - -#[cfg(feature = "ic")] -unsafe fn update_strategy(strategy: Strategy, limits: &Limits) { - const GROWTH_RATE: f64 = 2.0; - if strategy == Strategy::Full { - OLD_GENERATION_THRESHOLD = ((limits.free - limits.base) as f64 * GROWTH_RATE) as usize; - if limits.free < CRITICAL_MEMORY_LIMIT { - PASSED_CRITICAL_LIMIT = false - } - } -} - -pub struct Heap<'a, M: Memory> { - pub mem: &'a mut M, - pub limits: Limits, - pub roots: Roots, -} - -pub struct Roots { - pub static_roots: Value, - pub continuation_table_ptr_loc: *mut Value, - // For possible future additional roots, please extend the functionality in: - // * `mark_root_set` - // * `thread_initial_phase` -} - -pub struct Limits { - pub base: usize, - pub last_free: usize, // this separates the old generation from the young generation - pub free: usize, -} - -pub struct GenerationalGC<'a, M: Memory> { - pub heap: Heap<'a, M>, - marked_space: usize, - strategy: Strategy, -} - -impl<'a, M: Memory> GenerationalGC<'a, M> { - pub fn new(heap: Heap, strategy: Strategy) -> GenerationalGC { - GenerationalGC { - heap, - marked_space: 0, - strategy, - } - } - - pub unsafe fn run(&mut self) { - self.alloc_mark_structures(); - self.mark_phase(); - self.compact_phase(); - self.free_mark_structures(); - } - - unsafe fn alloc_mark_structures(&mut self) { - const BITMAP_ALIGNMENT: usize = 8 * WORD_SIZE as usize; - let heap_prefix = match self.strategy { - Strategy::Young => self.heap.limits.last_free / BITMAP_ALIGNMENT * BITMAP_ALIGNMENT, - Strategy::Full => self.heap.limits.base, - }; - let heap_size = Bytes((self.heap.limits.free - heap_prefix) as u32); - alloc_bitmap(self.heap.mem, heap_size, heap_prefix as u32 / WORD_SIZE); - alloc_mark_stack(self.heap.mem); - } - - unsafe fn free_mark_structures(&mut self) { - free_mark_stack(); - free_bitmap(); - } - - unsafe fn mark_phase(&mut self) { - self.marked_space = 0; - self.mark_root_set(); - self.mark_all_reachable(); - } - - unsafe fn mark_root_set(&mut self) { - self.mark_static_roots(); - - let continuation_table = *self.heap.roots.continuation_table_ptr_loc; - if continuation_table.is_ptr() && continuation_table.get_ptr() >= self.generation_base() { - self.mark_object(continuation_table); - } - - if self.strategy == Strategy::Young { - self.mark_additional_young_root_set(); - } - } - - unsafe fn mark_static_roots(&mut self) { - let root_array = self.heap.roots.static_roots.as_array(); - for i in 0..root_array.len() { - let object = root_array.get(i).as_obj(); - assert_eq!(object.tag(), TAG_MUTBOX); - assert!((object as usize) < self.heap.limits.base); - self.mark_root_mutbox_fields(object as *mut MutBox); - } - } - - unsafe fn mark_additional_young_root_set(&mut self) { - let mut iterator = REMEMBERED_SET.as_ref().unwrap().iterate(); - while iterator.has_next() { - let location = iterator.current().get_raw() as *mut Value; - let value = *location; - // Check whether the location still refers to young object as this may have changed - // due to subsequent writes to that location after the write barrier recording. - if value.points_to_or_beyond(self.heap.limits.last_free) { - self.mark_object(value); - } - iterator.next(); - } - } - - unsafe fn mark_object(&mut self, object: Value) { - let pointer = object.get_ptr() as u32; - assert!(pointer >= self.generation_base() as u32); - assert_eq!(pointer % WORD_SIZE, 0); - - let obj_idx = pointer / WORD_SIZE; - if get_bit(obj_idx) { - return; - } - set_bit(obj_idx); - - push_mark_stack(self.heap.mem, pointer as usize); - self.marked_space += block_size(pointer as usize).to_bytes().as_usize(); - } - - unsafe fn mark_all_reachable(&mut self) { - while let Some(obj) = pop_mark_stack() { - self.mark_fields(obj as *mut Obj); - } - } - - unsafe fn mark_fields(&mut self, object: *mut Obj) { - visit_pointer_fields( - self, - object, - object.tag(), - self.generation_base(), - |gc, field_address| { - let field_value = *field_address; - gc.mark_object(field_value); - - // Should become a debug assertion in future. - gc.barrier_coverage_check(field_address); - }, - |gc, slice_start, array| { - const SLICE_INCREMENT: u32 = 255; - debug_assert!(SLICE_INCREMENT >= TAG_ARRAY_SLICE_MIN); - if array.len() - slice_start > SLICE_INCREMENT { - let new_start = slice_start + SLICE_INCREMENT; - // Remember to visit the array suffix later, store the next visit offset in the tag. - (*array).header.tag = new_start; - push_mark_stack(gc.heap.mem, array as usize); - new_start - } else { - // No further visits of this array. Restore the tag. - (*array).header.tag = TAG_ARRAY; - array.len() - } - }, - ); - } - - unsafe fn barrier_coverage_check(&self, field_address: *mut Value) { - if self.strategy == Strategy::Full - && (field_address as usize) >= self.heap.limits.base - && (field_address as usize) < self.heap.limits.last_free - && (*field_address).points_to_or_beyond(self.heap.limits.last_free) - { - assert!(REMEMBERED_SET - .as_ref() - .unwrap() - .contains(Value::from_raw(field_address as u32))); - } - } - - unsafe fn mark_root_mutbox_fields(&mut self, mutbox: *mut MutBox) { - let field_address = &mut (*mutbox).field; - if pointer_to_dynamic_heap(field_address, self.generation_base()) { - self.mark_object(*field_address); - } - } - - unsafe fn compact_phase(&mut self) { - if self.is_compaction_beneficial() { - self.thread_initial_phase(); - self.move_phase(); - } - } - - fn is_compaction_beneficial(&self) -> bool { - // Returns false if the survival rate is f64::INF for an empty generation. - const SURVIVAL_THRESHOLD: f64 = 0.95; - self.survival_rate() < SURVIVAL_THRESHOLD - } - - fn generation_base(&self) -> usize { - match self.strategy { - Strategy::Young => self.heap.limits.last_free, - Strategy::Full => self.heap.limits.base, - } - } - - fn generation_size(&self) -> usize { - self.heap.limits.free - self.generation_base() - } - - fn survival_rate(&self) -> f64 { - // Returns f64::INF if the generation size is zero, e.g. on forced GC. - self.marked_space as f64 / self.generation_size() as f64 - } - - unsafe fn thread_initial_phase(&mut self) { - self.thread_all_backward_pointers(); - - // For static roots, also forward pointers are threaded. - // Therefore, this must happen after the heap traversal for backwards pointer threading. - self.thread_static_roots(); - - let continuation_table = *self.heap.roots.continuation_table_ptr_loc; - if continuation_table.is_ptr() && continuation_table.get_ptr() >= self.generation_base() { - self.thread(self.heap.roots.continuation_table_ptr_loc); - } - - // For the young generation GC run, the forward pointers from the old generation must be threaded too. - if self.strategy == Strategy::Young { - self.thread_old_generation_pointers(); - } - } - - unsafe fn thread_static_roots(&self) { - let root_array = self.heap.roots.static_roots.as_array(); - for i in 0..root_array.len() { - let object = root_array.get(i).as_obj(); - assert_eq!(object.tag(), TAG_MUTBOX); - assert!((object as usize) < self.heap.limits.base); - self.thread_root_mutbox_fields(object as *mut MutBox); - } - } - - unsafe fn thread_root_mutbox_fields(&self, mutbox: *mut MutBox) { - let field_addr = &mut (*mutbox).field; - if pointer_to_dynamic_heap(field_addr, self.generation_base()) { - self.thread(field_addr); - } - } - - unsafe fn thread_all_backward_pointers(&mut self) { - let mut bitmap_iter = iter_bits(); - let mut bit = bitmap_iter.next(); - while bit != BITMAP_ITER_END { - let object = (bit * WORD_SIZE) as *mut Obj; - self.thread_backward_pointer_fields(object); - bit = bitmap_iter.next(); - } - } - - unsafe fn thread_backward_pointer_fields(&mut self, object: *mut Obj) { - debug_assert!(object.tag() < TAG_ARRAY_SLICE_MIN); - visit_pointer_fields( - &mut (), - object, - object.tag(), - self.generation_base(), - |_, field_address| { - let field_value = *field_address; - // Thread if backwards or self pointer - if field_value.get_ptr() <= object as usize { - (&self).thread(field_address); - } - }, - |_, _, array| array.len(), - ); - } - - // Thread forward pointers in old generation leading to young generation - unsafe fn thread_old_generation_pointers(&mut self) { - let mut iterator = REMEMBERED_SET.as_ref().unwrap().iterate(); - while iterator.has_next() { - let location = iterator.current().get_raw() as *mut Value; - assert!( - (location as usize) >= self.heap.limits.base - && (location as usize) < self.heap.limits.last_free - ); - let value = *location; - // value in the location may have changed since recording by the write barrer - if value.points_to_or_beyond(self.heap.limits.last_free) { - self.thread(location); - } - iterator.next(); - } - } - - unsafe fn move_phase(&mut self) { - REMEMBERED_SET = None; // no longer valid when the moving phase starts - let mut free = self.heap.limits.base; - - let mut bitmap_iter = iter_bits(); - if self.strategy == Strategy::Young { - free = self.heap.limits.last_free; - } - let mut bit = bitmap_iter.next(); - while bit != BITMAP_ITER_END { - let old_pointer = (bit * WORD_SIZE) as *mut Obj; - let new_pointer = free; - - // Unthread backwards pointers as well as forward pointers of static objects. - // In the case of a young collection, also unthread forward pointers of old objects. - self.unthread(old_pointer, new_pointer); - - // Move the object - let object_size = block_size(old_pointer as usize); - if new_pointer as usize != old_pointer as usize { - memcpy_words(new_pointer as usize, old_pointer as usize, object_size); - debug_assert!(object_size.as_usize() > size_of::().as_usize()); - - // Update forwarding pointer - let new_obj = new_pointer as *mut Obj; - debug_assert!(new_obj.tag() >= TAG_OBJECT && new_obj.tag() <= TAG_NULL); - } - - free += object_size.to_bytes().as_usize(); - - // Thread forward pointers of the object, even if not moved - self.thread_forward_pointers(new_pointer as *mut Obj); - - bit = bitmap_iter.next(); - } - - self.heap.limits.free = free; - } - - /// Thread forward pointers in object - unsafe fn thread_forward_pointers(&mut self, object: *mut Obj) { - visit_pointer_fields( - &mut (), - object, - object.tag(), - self.generation_base(), - |_, field_address| { - if (*field_address).get_ptr() > object as usize { - (&self).thread(field_address) - } - }, - |_, _, array| array.len(), - ); - } - - unsafe fn thread(&self, field: *mut Value) { - let pointed = (*field).get_ptr() as *mut Obj; - assert!(self.should_be_threaded(pointed)); - let pointed_header = pointed.tag(); - *field = Value::from_raw(pointed_header); - (*pointed).tag = field as u32; - } - - unsafe fn unthread(&self, object: *mut Obj, new_location: usize) { - assert!(self.should_be_threaded(object)); - let mut header = object.tag(); - while header & 0b1 == 0 { - let tmp = (header as *const Obj).tag(); - (*(header as *mut Value)) = Value::from_ptr(new_location); - header = tmp; - } - assert!(header >= TAG_OBJECT && header <= TAG_NULL); - (*object).tag = header; - } - - unsafe fn should_be_threaded(&self, object: *mut Obj) -> bool { - object as usize >= self.generation_base() - } -} diff --git a/rts/motoko-rts/src/gc/generational/mark_stack.rs b/rts/motoko-rts/src/gc/generational/mark_stack.rs deleted file mode 100644 index 3bc6134e533..00000000000 --- a/rts/motoko-rts/src/gc/generational/mark_stack.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! A stack for marking heap objects (for GC). Adopted from mark & compact GC. -//! Simplified to only store object pointers without tags. - -use crate::memory::{alloc_blob, Memory}; -use crate::types::{Blob, Words}; - -use core::ptr::null_mut; - -/// Initial stack size -pub const INIT_STACK_SIZE: Words = Words(64); - -/// Pointer to the `blob` object for the mark stack. Used to get the capacity of the stack. -static mut STACK_BLOB_PTR: *mut Blob = null_mut(); - -/// Bottom of the mark stack -pub static mut STACK_BASE: *mut usize = null_mut(); - -/// Top of the mark stack -pub static mut STACK_TOP: *mut usize = null_mut(); - -/// Next free slot in the mark stack -pub static mut STACK_PTR: *mut usize = null_mut(); - -/// Allocate the mark stack at the start of each GC run -pub unsafe fn alloc_mark_stack(mem: &mut M) { - assert!(STACK_BLOB_PTR.is_null()); - - // Allocating an actual object here to not break dump_heap - // No post allocation barrier as this RTS-internal blob will be collected by the GC. - STACK_BLOB_PTR = alloc_blob(mem, INIT_STACK_SIZE.to_bytes()).get_ptr() as *mut Blob; - STACK_BASE = STACK_BLOB_PTR.payload_addr() as *mut usize; - STACK_PTR = STACK_BASE; - STACK_TOP = STACK_BASE.add(INIT_STACK_SIZE.as_usize()); -} - -/// Deallocate the mark stack after each GC run -pub unsafe fn free_mark_stack() { - STACK_BLOB_PTR = null_mut(); - STACK_BASE = null_mut(); - STACK_PTR = null_mut(); - STACK_TOP = null_mut(); -} - -/// Doubles the stack size -pub unsafe fn grow_stack(mem: &mut M) { - let stack_cap: Words = STACK_BLOB_PTR.len().to_words(); - let p = mem.alloc_words(stack_cap).get_ptr() as *mut usize; - - // Make sure nothing was allocated after the stack - assert_eq!(STACK_TOP, p); - - let new_cap: Words = stack_cap * 2; - (*STACK_BLOB_PTR).len = new_cap.to_bytes(); - STACK_TOP = STACK_BASE.add(new_cap.as_usize()); -} - -/// Push a new unskewed object pointer to be marked later -pub unsafe fn push_mark_stack(mem: &mut M, object: usize) { - if STACK_PTR == STACK_TOP { - grow_stack(mem); - } - *STACK_PTR = object; - STACK_PTR = STACK_PTR.add(1); -} - -/// Pop a unskewed object pointer if existend to be visited next -pub unsafe fn pop_mark_stack() -> Option { - if STACK_PTR == STACK_BASE { - return None; - } - STACK_PTR = STACK_PTR.sub(1); - let object = *STACK_PTR; - return Some(object); -} diff --git a/rts/motoko-rts/src/gc/generational/remembered_set.rs b/rts/motoko-rts/src/gc/generational/remembered_set.rs deleted file mode 100644 index 03c1cdc13a0..00000000000 --- a/rts/motoko-rts/src/gc/generational/remembered_set.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! Remembered set. -//! Serves for recording pointer locations trapped by the write barrier. -//! -//! Hash-set implementation. Linked-list collision handling. -//! -//! Hash function = (ptr / WORD_SIZE) % TABLE_SIZE -//! -//! Hash-table (length N): -//! ---------------------------------- -//! | entry[0] | collision_ptr[0] | -//! ---------------------------------- -//! | entry[1] | collision_ptr[1] | -//! ---------------------------------- -//! | ... | -//! ---------------------------------- -//! | entry[N-1] | collision_ptr[N-1] | -//! ---------------------------------- -//! -//! Per collision a new linked list node is appended: -//! -//! Collision node -//! ------------------------------ -//! prev_collision_ptr --> | entry | next_collision_ptr | -//! ------------------------------ -//! -//! Amortized hash-table growth when exceeding a defined threshold. -//! -//! Growth factor 2 for faster bitwise modulo calculation. -//! -//! NOTE: Remembered set structure is not recorded by write barriers -//! as it is discarded by each GC run. -//! -//! NOTE: The table must be blobs, as their entries must not be -//! analyzed by the GC. - -use core::mem::size_of; -use core::ptr::null_mut; - -use crate::constants::WORD_SIZE; -use crate::memory::{alloc_blob, Memory}; -use crate::types::{block_size, Blob, Bytes, Value}; - -pub struct RememberedSet { - hash_table: *mut Blob, - count: u32, // contained entries -} - -#[repr(C)] -struct HashEntry { - pub value: Value, - pub next_collision_ptr: *mut CollisionNode, -} - -#[repr(C)] -struct CollisionNode { - pub header: Blob, - pub entry: HashEntry, -} - -pub struct RememberedSetIterator { - hash_table: *mut Blob, - hash_index: u32, - current_entry: *mut HashEntry, -} - -pub const INITIAL_TABLE_LENGTH: u32 = 1024; -const GROWTH_FACTOR: u32 = 2; -pub const OCCUPATION_THRESHOLD_PERCENT: u32 = 65; - -impl RememberedSet { - pub unsafe fn new(mem: &mut M) -> RememberedSet { - let hash_table = new_table(mem, INITIAL_TABLE_LENGTH); - RememberedSet { - hash_table, - count: 0, - } - } - - pub unsafe fn insert(&mut self, mem: &mut M, value: Value) { - debug_assert!(!is_null_ptr_value(value)); - let index = self.hash_index(value); - let entry = table_get(self.hash_table, index); - if is_null_ptr_value((*entry).value) { - debug_assert_eq!((*entry).next_collision_ptr, null_mut()); - table_set(self.hash_table, index, value); - } else { - let mut current = entry; - while (*current).value.get_raw() != value.get_raw() - && (*current).next_collision_ptr != null_mut() - { - let next_node = (*current).next_collision_ptr; - current = &mut (*next_node).entry; - debug_assert!(!is_null_ptr_value((*current).value)); - } - if (*current).value.get_raw() == value.get_raw() { - // duplicate - return; - } - debug_assert!(!is_null_ptr_value((*current).value)); - (*current).next_collision_ptr = new_collision_node(mem, value); - } - self.count += 1; - if self.count > table_length(self.hash_table) * OCCUPATION_THRESHOLD_PERCENT / 100 { - self.grow(mem); - } - } - - // Only used for debug assertions (barrier coverage check). - pub unsafe fn contains(&self, value: Value) -> bool { - debug_assert!(!is_null_ptr_value(value)); - let index = self.hash_index(value); - let entry = table_get(self.hash_table, index); - if !is_null_ptr_value((*entry).value) { - let mut current = entry; - while (*current).value.get_raw() != value.get_raw() - && (*current).next_collision_ptr != null_mut() - { - let next_node = (*current).next_collision_ptr; - current = &mut (*next_node).entry; - debug_assert!(!is_null_ptr_value((*current).value)); - } - if (*current).value.get_raw() == value.get_raw() { - return true; - } - } - false - } - - pub unsafe fn hash_index(&self, value: Value) -> u32 { - // Future optimization: Use bitwise modulo, check for power of 2 - let raw = value.get_raw(); - let length = table_length(self.hash_table); - debug_assert_eq!((raw / WORD_SIZE) % length, (raw / WORD_SIZE) & (length - 1)); - (raw / WORD_SIZE) & (length - 1) - } - - pub unsafe fn iterate(&self) -> RememberedSetIterator { - RememberedSetIterator::init(self) - } - - pub fn count(&self) -> u32 { - self.count - } - - unsafe fn grow(&mut self, mem: &mut M) { - let old_count = self.count; - let mut iterator = self.iterate(); - let new_length = table_length(self.hash_table) * GROWTH_FACTOR; - self.hash_table = new_table(mem, new_length); - self.count = 0; - while iterator.has_next() { - let value = iterator.current(); - debug_assert!(!is_null_ptr_value(value)); - self.insert(mem, value); - iterator.next(); - } - assert_eq!(self.count, old_count); - } -} - -impl RememberedSetIterator { - pub unsafe fn init(remembered_set: &RememberedSet) -> RememberedSetIterator { - let mut first_entry = table_get(remembered_set.hash_table, 0); - if is_null_ptr_value((*first_entry).value) { - first_entry = null_mut() - } - let mut iterator = RememberedSetIterator { - hash_table: remembered_set.hash_table, - hash_index: 0, - current_entry: first_entry, - }; - iterator.skip_free(); - iterator - } - - unsafe fn skip_free(&mut self) { - let length = table_length(self.hash_table); - if self.hash_index == length { - return; - } - if self.current_entry != null_mut() { - debug_assert!(!is_null_ptr_value((*self.current_entry).value)); - return; - } - self.hash_index += 1; - while self.hash_index < length - && is_null_ptr_value((*table_get(self.hash_table, self.hash_index)).value) - { - debug_assert_eq!( - (*table_get(self.hash_table, self.hash_index)).next_collision_ptr, - null_mut() - ); - self.hash_index += 1 - } - if self.hash_index < length { - self.current_entry = table_get(self.hash_table, self.hash_index); - debug_assert!(!is_null_ptr_value((*self.current_entry).value)); - } else { - self.current_entry = null_mut(); - } - } - - pub unsafe fn has_next(&self) -> bool { - self.current_entry != null_mut() - } - - pub unsafe fn current(&self) -> Value { - debug_assert!(self.has_next()); - debug_assert!(!is_null_ptr_value((*self.current_entry).value)); - (*self.current_entry).value - } - - pub unsafe fn next(&mut self) { - debug_assert!(self.has_next()); - let next_node = (*self.current_entry).next_collision_ptr; - if next_node == null_mut() { - self.current_entry = null_mut() - } else { - self.current_entry = &mut (*next_node).entry as *mut HashEntry; - } - self.skip_free() - } -} - -unsafe fn new_table(mem: &mut M, size: u32) -> *mut Blob { - // No post allocation barrier as this RTS-internal blob will be collected by the GC. - let table = alloc_blob(mem, Bytes(size * size_of::() as u32)).as_blob_mut(); - for index in 0..size { - table_set(table, index, null_ptr_value()); - } - table -} - -unsafe fn new_collision_node(mem: &mut M, value: Value) -> *mut CollisionNode { - debug_assert!(!is_null_ptr_value(value)); - // No post allocation barrier as this RTS-internal blob will be collected by the GC. - let node = - alloc_blob(mem, Bytes(size_of::() as u32)).as_blob_mut() as *mut CollisionNode; - (*node).entry = HashEntry { - value, - next_collision_ptr: null_mut(), - }; - node -} - -unsafe fn table_get(table: *mut Blob, index: u32) -> *mut HashEntry { - debug_assert!(table != null_mut()); - let entry = - (table.payload_addr() as u32 + index * size_of::() as u32) as *mut HashEntry; - debug_assert!( - entry as u32 + size_of::() as u32 - <= table as u32 + block_size(table as usize).to_bytes().as_u32() - ); - entry -} - -unsafe fn table_set(table: *mut Blob, index: u32, value: Value) { - let entry = table_get(table, index); - (*entry).value = value; - (*entry).next_collision_ptr = null_mut(); -} - -unsafe fn table_length(table: *mut Blob) -> u32 { - debug_assert!(table != null_mut()); - debug_assert!(table.len().as_u32() % size_of::() as u32 == 0); - table.len().as_u32() / size_of::() as u32 -} - -unsafe fn null_ptr_value() -> Value { - Value::from_raw((null_mut() as *mut usize) as u32) -} - -unsafe fn is_null_ptr_value(value: Value) -> bool { - value.get_raw() as *mut usize == null_mut() -} diff --git a/rts/motoko-rts/src/gc/generational/sanity_checks.rs b/rts/motoko-rts/src/gc/generational/sanity_checks.rs deleted file mode 100644 index 2fd5531af9b..00000000000 --- a/rts/motoko-rts/src/gc/generational/sanity_checks.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Extensive sanity checks for generational GC features. -//! * Write barrier coverage by memory snapshot comparisons. -//! * Memory sanity check, including a full heap scan. -#![allow(dead_code)] - -use core::ptr::null_mut; - -use super::write_barrier::REMEMBERED_SET; -use super::{Heap, Limits, Roots}; -use crate::mem_utils::memcpy_bytes; -use crate::memory::{alloc_blob, Memory}; -use crate::types::*; -use crate::visitor::{pointer_to_dynamic_heap, visit_pointer_fields}; - -static mut SNAPSHOT: *mut Blob = null_mut(); - -/// Take a memory snapshot. To be initiated after GC run. -pub unsafe fn take_snapshot(heap: &mut Heap) { - let length = Bytes(heap.limits.free as u32); - // No post allocation barrier as this RTS-internal blob will be collected by the GC. - let blob = alloc_blob(heap.mem, length).get_ptr() as *mut Blob; - memcpy_bytes(blob.payload_addr() as usize, 0, length); - SNAPSHOT = blob; -} - -/// Verify write barrier coverage by comparing the memory against the previous snapshot. -/// To be initiated before the next GC run. No effect if no snapshpot has been taken. -pub unsafe fn verify_snapshot(heap: &Heap, verify_roots: bool) { - if SNAPSHOT.is_null() { - return; - } - assert!(heap.limits.base <= heap.limits.free); - if verify_roots { - verify_static_roots(heap.roots.static_roots.as_array(), heap.limits.free); - } - verify_heap(&heap.limits); - SNAPSHOT = null_mut(); -} - -unsafe fn verify_static_roots(static_roots: *mut Array, last_free: usize) { - for index in 0..static_roots.len() { - let current = static_roots.get(index).as_obj(); - assert_eq!(current.tag(), TAG_MUTBOX); // check tag - let mutbox = current as *mut MutBox; - let current_field = &mut (*mutbox).field; - if relevant_field(current_field, last_free) { - verify_field(current_field); - } - } -} - -unsafe fn verify_heap(limits: &Limits) { - assert!(SNAPSHOT.len().as_usize() <= limits.free); - let mut pointer = limits.base; - while pointer < SNAPSHOT.len().as_usize() { - if Value::from_ptr(pointer).is_obj() { - let current = pointer as *mut Obj; - let previous = (SNAPSHOT.payload_addr() as usize + pointer) as *mut Obj; - assert!(current.tag() == previous.tag()); - visit_pointer_fields( - &mut (), - current, - current.tag(), - 0, - |_, current_field| { - if relevant_field(current_field, limits.last_free) { - verify_field(current_field); - } - }, - |_, slice_start, arr| { - assert!(slice_start == 0); - arr.len() - }, - ); - } - pointer += block_size(pointer).to_bytes().as_usize(); - } -} - -unsafe fn relevant_field(current_field: *mut Value, last_free: usize) -> bool { - if (current_field as usize) < last_free { - let value = *current_field; - value.is_ptr() && value.get_ptr() as usize >= last_free - } else { - false - } -} - -unsafe fn verify_field(current_field: *mut Value) { - let memory_copy = SNAPSHOT.payload_addr() as usize; - let previous_field = (memory_copy + current_field as usize) as *mut Value; - if *previous_field != *current_field && !recorded(current_field as u32) { - panic!("Missing write barrier at {:#x}", current_field as usize); - } -} - -unsafe fn recorded(value: u32) -> bool { - match &REMEMBERED_SET { - None => panic!("No remembered set"), - Some(remembered_set) => remembered_set.contains(Value::from_raw(value)), - } -} - -pub struct MemoryChecker<'a> { - limits: &'a Limits, - roots: &'a Roots, -} - -pub unsafe fn check_memory(limits: &Limits, roots: &Roots) { - let checker = MemoryChecker { limits, roots }; - checker.check_memory(); -} - -impl<'a> MemoryChecker<'a> { - unsafe fn check_memory(&self) { - self.check_static_roots(); - if (*self.roots.continuation_table_ptr_loc).is_ptr() { - self.check_object(*self.roots.continuation_table_ptr_loc); - } - self.check_heap(); - } - - unsafe fn check_static_roots(&self) { - let root_array = self.roots.static_roots.as_array(); - for i in 0..root_array.len() { - let obj = root_array.get(i).as_obj(); - assert_eq!(obj.tag(), TAG_MUTBOX); - assert!((obj as usize) < self.limits.base); - let mutbox = obj as *mut MutBox; - let field_addr = &mut (*mutbox).field; - if pointer_to_dynamic_heap(field_addr, self.limits.base as usize) { - let object = *field_addr; - self.check_object(object); - } - } - } - - unsafe fn check_object(&self, object: Value) { - self.check_object_header(object); - visit_pointer_fields( - &mut (), - object.as_obj(), - object.tag(), - 0, - |_, field_address| { - // Ignore null pointers used in text_iter. - if (*field_address).get_ptr() as *mut Obj != null_mut() { - (&self).check_object_header(*field_address); - } - }, - |_, _, arr| arr.len(), - ); - } - - unsafe fn check_object_header(&self, object: Value) { - assert!(object.is_ptr()); - let pointer = object.get_ptr(); - assert!(pointer < self.limits.free); - let tag = object.tag(); - assert!(tag >= TAG_OBJECT && tag <= TAG_NULL); - } - - unsafe fn check_heap(&self) { - let mut pointer = self.limits.base; - while pointer < self.limits.free { - let block = Value::from_ptr(pointer as usize); - if block.tag() != TAG_ONE_WORD_FILLER { - self.check_object(block); - } - pointer += block_size(pointer as usize).to_bytes().as_usize(); - } - } -} diff --git a/rts/motoko-rts/src/gc/generational/write_barrier.rs b/rts/motoko-rts/src/gc/generational/write_barrier.rs deleted file mode 100644 index 53fb3a87723..00000000000 --- a/rts/motoko-rts/src/gc/generational/write_barrier.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Write barrier, used for generational GC - -use super::remembered_set::RememberedSet; -use crate::memory::Memory; -use crate::types::Value; -use motoko_rts_macros::ic_mem_fn; - -pub static mut REMEMBERED_SET: Option = None; -pub static mut HEAP_BASE: usize = 0; -pub static mut LAST_HP: usize = 0; - -#[cfg(feature = "ic")] -/// (Re-)initialize the write barrier for generational GC. -pub(crate) unsafe fn init_generational_write_barrier(mem: &mut M) { - use crate::memory::ic::{linear_memory, HEAP_START}; - REMEMBERED_SET = Some(RememberedSet::new(mem)); - HEAP_BASE = HEAP_START; - LAST_HP = linear_memory::LAST_HP; -} - -/// Write barrier to be called AFTER the pointer store, used for the generational GC. -/// `location`: location of modified pointer (address of object field or array element). -/// -/// As the barrier is called after the write, `*location` refers to the NEW value. -/// No effect if the write barrier is deactivated. -#[ic_mem_fn] -pub unsafe fn post_write_barrier(mem: &mut M, location: usize) { - // Must be an unskewed address. - debug_assert_eq!(location & 0b1, 0); - // Checks have been optimized according to the frequency of occurrence. - // Only record locations inside old generation. Static roots are anyway marked by GC. - if location < LAST_HP { - // Nested ifs are more efficient when counting instructions on IC (explicit return counts as an instruction). - let value = *(location as *mut Value); - if value.points_to_or_beyond(LAST_HP) { - #[allow(clippy::collapsible_if)] - if location >= HEAP_BASE { - // Trap pointers that lead from old generation (or static roots) to young generation. - REMEMBERED_SET - .as_mut() - .unwrap() - .insert(mem, Value::from_raw(location as u32)); - } - } - } -} diff --git a/rts/motoko-rts/src/gc/mark_compact.rs b/rts/motoko-rts/src/gc/mark_compact.rs deleted file mode 100644 index c6716cfc2cf..00000000000 --- a/rts/motoko-rts/src/gc/mark_compact.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! Implements threaded compaction as described in "High-Performance Garbage Collection for -//! Memory-Constrained Environments" section 5.1.2, which is an improved version of the original -//! threaded compaction algorithm described in The Garbage Collection Handbook section 3.3. - -pub mod bitmap; -pub mod mark_stack; - -use bitmap::{alloc_bitmap, free_bitmap, get_bit, iter_bits, set_bit, BITMAP_ITER_END}; -use mark_stack::{alloc_mark_stack, free_mark_stack, pop_mark_stack, push_mark_stack}; - -use crate::constants::WORD_SIZE; -use crate::mem_utils::memcpy_words; -use crate::memory::Memory; -use crate::types::*; -use crate::visitor::{pointer_to_dynamic_heap, visit_pointer_fields}; - -use motoko_rts_macros::ic_mem_fn; - -#[no_mangle] -#[cfg(feature = "ic")] -#[allow(unreachable_code)] -pub unsafe extern "C" fn initialize_compacting_gc() { - panic!("Compacting GC is not supported with the persistent heap"); - crate::memory::ic::linear_memory::initialize(); -} - -#[ic_mem_fn(ic_only)] -unsafe fn schedule_compacting_gc(mem: &mut M) { - // 512 MiB slack for mark stack + allocation area for the next message - let slack: u64 = 512 * 1024 * 1024; - let heap_size_bytes: u64 = - u64::from(crate::constants::WASM_HEAP_SIZE.as_u32()) * u64::from(WORD_SIZE); - // Larger than necessary to keep things simple - let max_bitmap_size_bytes = heap_size_bytes / 32; - // NB. `max_live` is evaluated in compile time to a constant - let max_live: Bytes = Bytes(heap_size_bytes - slack - max_bitmap_size_bytes); - - if super::should_do_gc(max_live) { - compacting_gc(mem); - } -} - -#[ic_mem_fn(ic_only)] -unsafe fn compacting_gc(mem: &mut M) { - use crate::memory::ic::{self, linear_memory, HEAP_START}; - - compacting_gc_internal( - mem, - HEAP_START, - // get_hp - || linear_memory::get_hp_unskewed(), - // set_hp - |hp| linear_memory::set_hp_unskewed(hp), - ic::linear_memory::get_static_roots(), - crate::continuation_table::continuation_table_loc(), - // note_live_size - |live_size| ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size), - // note_reclaimed - |reclaimed| linear_memory::RECLAIMED += Bytes(u64::from(reclaimed.as_u32())), - ); - - linear_memory::LAST_HP = linear_memory::get_hp_unskewed(); -} - -pub unsafe fn compacting_gc_internal< - M: Memory, - GetHp: Fn() -> usize, - SetHp: Fn(usize), - NoteLiveSize: Fn(Bytes), - NoteReclaimed: Fn(Bytes), ->( - mem: &mut M, - heap_base: usize, - get_hp: GetHp, - set_hp: SetHp, - static_roots: Value, - continuation_table_ptr_loc: *mut Value, - note_live_size: NoteLiveSize, - note_reclaimed: NoteReclaimed, -) { - let old_hp = get_hp() as u32; - - assert_eq!(heap_base % 32, 0); - - mark_compact( - mem, - set_hp, - heap_base, - old_hp, - static_roots, - continuation_table_ptr_loc, - ); - - let reclaimed = old_hp - (get_hp() as u32); - note_reclaimed(Bytes(reclaimed)); - - let live = get_hp() - heap_base; - note_live_size(Bytes(live as u32)); -} - -unsafe fn mark_compact( - mem: &mut M, - set_hp: SetHp, - heap_base: usize, - heap_end: u32, - static_roots: Value, - continuation_table_ptr_loc: *mut Value, -) { - let mem_size = Bytes(heap_end - heap_base as u32); - - alloc_bitmap(mem, mem_size, heap_base as u32 / WORD_SIZE); - alloc_mark_stack(mem); - - mark_static_roots(mem, static_roots, heap_base); - - if (*continuation_table_ptr_loc).is_ptr() { - mark_object(mem, *continuation_table_ptr_loc); - // Similar to `mark_root_mutbox_fields`, `continuation_table_ptr_loc` is in static heap so - // it will be readable when we unthread the continuation table - thread(continuation_table_ptr_loc); - } - - mark_stack(mem, heap_base); - - update_refs(set_hp, heap_base); - - free_mark_stack(); - free_bitmap(); -} - -unsafe fn mark_static_roots(mem: &mut M, static_roots: Value, heap_base: usize) { - let root_array = static_roots.as_array(); - - // Static objects are not in the dynamic heap so don't need marking. - for i in 0..root_array.len() { - let obj = root_array.get(i).as_obj(); - // Root array should only have pointers to other static MutBoxes - debug_assert_eq!(obj.tag(), TAG_MUTBOX); // check tag - debug_assert!((obj as usize) < heap_base); // check that MutBox is static - mark_root_mutbox_fields(mem, obj as *mut MutBox, heap_base); - } -} - -unsafe fn mark_object(mem: &mut M, obj: Value) { - let obj_tag = obj.tag(); - let obj = obj.get_ptr() as u32; - - // Check object alignment to avoid undefined behavior. See also static_checks module. - debug_assert_eq!(obj % WORD_SIZE, 0); - - let obj_idx = obj / WORD_SIZE; - - if get_bit(obj_idx) { - // Already marked - return; - } - - set_bit(obj_idx); - push_mark_stack(mem, obj as usize, obj_tag); -} - -unsafe fn mark_stack(mem: &mut M, heap_base: usize) { - while let Some((obj, tag)) = pop_mark_stack() { - mark_fields(mem, obj as *mut Obj, tag, heap_base) - } -} - -unsafe fn mark_fields(mem: &mut M, obj: *mut Obj, obj_tag: Tag, heap_base: usize) { - visit_pointer_fields( - mem, - obj, - obj_tag, - heap_base, - |mem, field_addr| { - let field_value = *field_addr; - mark_object(mem, field_value); - - // Thread if backwards or self pointer - if field_value.get_ptr() <= obj as usize { - thread(field_addr); - } - }, - |mem, slice_start, arr| { - const SLICE_INCREMENT: u32 = 127; - debug_assert!(SLICE_INCREMENT >= TAG_ARRAY_SLICE_MIN); - if arr.len() - slice_start > SLICE_INCREMENT { - let new_start = slice_start + SLICE_INCREMENT; - // push an entire (suffix) array slice - push_mark_stack(mem, arr as usize, new_start); - new_start - } else { - arr.len() - } - }, - ); -} - -/// Specialized version of `mark_fields` for root `MutBox`es. -unsafe fn mark_root_mutbox_fields(mem: &mut M, mutbox: *mut MutBox, heap_base: usize) { - let field_addr = &mut (*mutbox).field; - if pointer_to_dynamic_heap(field_addr, heap_base) { - mark_object(mem, *field_addr); - // It's OK to thread forward pointers here as the static objects won't be moved, so we will - // be able to unthread objects pointed by these fields later. - thread(field_addr); - } -} - -/// Linearly scan the heap, for each live object: -/// -/// - Mark step threads all backwards pointers and pointers from roots, so unthread to update those -/// pointers to the objects new location. -/// -/// - Move the object -/// -/// - Thread forward pointers of the object -/// -unsafe fn update_refs(set_hp: SetHp, heap_base: usize) { - let mut free = heap_base; - - let mut bitmap_iter = iter_bits(); - let mut bit = bitmap_iter.next(); - while bit != BITMAP_ITER_END { - let p = (bit * WORD_SIZE) as *mut Obj; - let p_new = free; - - // Update backwards references to the object's new location and restore object header - unthread(p, p_new); - - // Move the object - let p_size_words = block_size(p as usize); - if p_new as usize != p as usize { - memcpy_words(p_new as usize, p as usize, p_size_words); - - debug_assert!(p_size_words.as_usize() > size_of::().as_usize()); - // Update forwarding pointer - let new_obj = p_new as *mut Obj; - debug_assert!(new_obj.tag() >= TAG_OBJECT && new_obj.tag() <= TAG_NULL); - } - - free += p_size_words.to_bytes().as_usize(); - - // Thread forward pointers of the object - thread_fwd_pointers(p_new as *mut Obj, heap_base); - - bit = bitmap_iter.next(); - } - - set_hp(free); -} - -/// Thread forward pointers in object -unsafe fn thread_fwd_pointers(obj: *mut Obj, heap_base: usize) { - visit_pointer_fields( - &mut (), - obj, - obj.tag(), - heap_base, - |_, field_addr| { - if (*field_addr).get_ptr() > obj as usize { - thread(field_addr) - } - }, - |_, _, arr| arr.len(), - ); -} - -/// Thread a pointer field -unsafe fn thread(field: *mut Value) { - // Store pointed object's header in the field, field address in the pointed object's header - let pointed = (*field).as_obj(); - let pointed_header = pointed.tag(); - *field = Value::from_raw(pointed_header); - (*pointed).tag = field as u32; -} - -/// Unthread all references at given header, replacing with `new_loc`. Restores object header. -unsafe fn unthread(obj: *mut Obj, new_loc: usize) { - let mut header = obj.tag(); - - // All objects and fields are word-aligned, and tags have the lowest bit set, so use the lowest - // bit to distinguish a header (tag) from a field address. - while header & 0b1 == 0 { - let tmp = (header as *const Obj).tag(); - (*(header as *mut Value)) = Value::from_ptr(new_loc); - header = tmp; - } - - // At the end of the chain is the original header for the object - debug_assert!(header >= TAG_OBJECT && header <= TAG_NULL); - - (*obj).tag = header; -} diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs index af5c503f6f9..0dcde17bb0a 100644 --- a/rts/motoko-rts/src/lib.rs +++ b/rts/motoko-rts/src/lib.rs @@ -31,7 +31,6 @@ mod idl; pub mod leb128; mod mem_utils; pub mod memory; -#[incremental_gc] #[cfg(feature = "ic")] pub mod persistence; pub mod principal_id; @@ -53,13 +52,6 @@ unsafe fn version(mem: &mut M) -> types::Value { text::text_of_str(mem, "0.1") } -#[non_incremental_gc] -#[ic_mem_fn(ic_only)] -unsafe fn alloc_words(mem: &mut M, n: types::Words) -> types::Value { - mem.alloc_words(n) -} - -#[incremental_gc] #[ic_mem_fn(ic_only)] unsafe fn alloc_words(mem: &mut M, n: types::Words) -> types::Value { crate::gc::incremental::get_partitioned_heap().allocate(mem, n) diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index 1fe07f0ef7a..135a0f4ad3f 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -1,8 +1,5 @@ // This module is only enabled when compiling the RTS for IC or WASI. -#[non_incremental_gc] -pub mod linear_memory; -#[incremental_gc] pub mod partitioned_memory; use super::Memory; @@ -10,11 +7,6 @@ use crate::constants::WASM_PAGE_SIZE; use crate::rts_trap_with; use crate::types::Bytes; use core::arch::wasm32; -use motoko_rts_macros::*; - -// TODO: Remove once the classical GCs have been removed. -#[non_incremental_gc] -pub const HEAP_START: usize = 4 * 1024 * 1024 + 128 * 1024; /// Maximum live data retained in a GC. pub(crate) static mut MAX_LIVE: Bytes = Bytes(0); diff --git a/rts/motoko-rts/src/memory/ic/linear_memory.rs b/rts/motoko-rts/src/memory/ic/linear_memory.rs deleted file mode 100644 index 18262afae7f..00000000000 --- a/rts/motoko-rts/src/memory/ic/linear_memory.rs +++ /dev/null @@ -1,74 +0,0 @@ -use core::arch::wasm32; - -use super::{IcMemory, Memory, HEAP_START}; -use crate::types::*; - -/// Amount of garbage collected so far. -pub(crate) static mut RECLAIMED: Bytes = Bytes(0); - -// Heap pointer (skewed) -extern "C" { - fn setHP(new_hp: usize); - fn getHP() -> u32; -} - -pub(crate) fn get_static_roots() -> Value { - unimplemented!() -} - -pub(crate) unsafe fn set_hp_unskewed(new_hp: usize) { - setHP(skew(new_hp)) -} -pub(crate) unsafe fn get_hp_unskewed() -> usize { - unskew(getHP() as usize) -} - -/// Heap pointer after last GC -pub(crate) static mut LAST_HP: usize = 0; - -pub(crate) unsafe fn initialize() { - LAST_HP = HEAP_START; - set_hp_unskewed(LAST_HP); -} - -#[no_mangle] -unsafe extern "C" fn get_reclaimed() -> Bytes { - RECLAIMED -} - -#[no_mangle] -pub unsafe extern "C" fn get_total_allocations() -> Bytes { - Bytes(u64::from(get_heap_size().as_u32())) + get_reclaimed() -} - -#[no_mangle] -pub unsafe extern "C" fn get_heap_size() -> Bytes { - Bytes((get_hp_unskewed() - HEAP_START) as u32) -} - -impl Memory for IcMemory { - #[inline] - unsafe fn alloc_words(&mut self, n: Words) -> Value { - let bytes = n.to_bytes(); - let delta = u64::from(bytes.as_u32()); - - // Update heap pointer - let old_hp = u64::from(getHP()); - let new_hp = old_hp + delta; - - // Grow memory if needed - if new_hp > ((wasm32::memory_size(0) as u64) << 16) { - self.grow_memory(new_hp) - } - - debug_assert!(new_hp <= u64::from(core::u32::MAX)); - setHP(new_hp as usize); - - Value::from_raw(old_hp as u32) - } - - #[inline(never)] - unsafe fn grow_memory(&mut self, ptr: u64) { - super::grow_memory(ptr); - } -} diff --git a/rts/motoko-rts/src/static_checks.rs b/rts/motoko-rts/src/static_checks.rs index 9ea55c1f79c..ccf20b1d90d 100644 --- a/rts/motoko-rts/src/static_checks.rs +++ b/rts/motoko-rts/src/static_checks.rs @@ -1,7 +1,6 @@ //! Compile-time assertions to make sure object layouts are as expected use crate::types::*; -use motoko_rts_macros::*; use core::mem::{align_of, size_of}; @@ -11,13 +10,8 @@ use core::mem::{align_of, size_of}; const WORD_SIZE: usize = crate::constants::WORD_SIZE as usize; #[allow(unused)] -#[incremental_gc] const HEADER_SIZE: usize = 2 * WORD_SIZE; -#[allow(unused)] -#[non_incremental_gc] -const HEADER_SIZE: usize = WORD_SIZE; - // We cannot use `assert_eq` below as `assert_eq` is not const yet // Check platform word size diff --git a/rts/motoko-rts/src/types.rs b/rts/motoko-rts/src/types.rs index 76e4cef973c..4efb03104a5 100644 --- a/rts/motoko-rts/src/types.rs +++ b/rts/motoko-rts/src/types.rs @@ -24,8 +24,6 @@ use crate::memory::Memory; use crate::tommath_bindings::{mp_digit, mp_int}; use core::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use core::ptr::null; -use motoko_rts_macros::is_incremental_gc; -use motoko_rts_macros::*; use crate::constants::WORD_SIZE; use crate::rts_trap_with; @@ -268,22 +266,16 @@ impl Value { /// Check that the forwarding pointer is valid. #[inline] pub unsafe fn check_forwarding_pointer(self) { - if is_incremental_gc!() { - debug_assert!( - self.forward().get_ptr() == self.get_ptr() - || self.forward().forward().get_ptr() == self.forward().get_ptr() - ); - } + debug_assert!( + self.forward().get_ptr() == self.get_ptr() + || self.forward().forward().get_ptr() == self.forward().get_ptr() + ); } /// Check whether the object's forwarding pointer refers to a different location. pub unsafe fn is_forwarded(self) -> bool { - if is_incremental_gc!() { - self.check_forwarding_pointer(); - self.forward().get_ptr() != self.get_ptr() - } else { - false - } + self.check_forwarding_pointer(); + self.forward().get_ptr() != self.get_ptr() } /// Get the object tag. No forwarding. Can be applied to any block, regular objects @@ -294,7 +286,6 @@ impl Value { } /// Get the forwarding pointer. Used by the incremental GC. - #[incremental_gc] pub unsafe fn forward(self) -> Value { debug_assert!(self.is_obj()); debug_assert!(self.get_ptr() as *const Obj != null()); @@ -302,15 +293,9 @@ impl Value { (*obj).forward } - /// Get the forwarding pointer. Used by the incremental GC. - #[non_incremental_gc] - pub unsafe fn forward(self) -> Value { - self - } - /// Resolve forwarding if the value is a pointer. Otherwise, return the same value. pub unsafe fn forward_if_possible(self) -> Value { - if is_incremental_gc!() && self.is_ptr() && self.get_ptr() as *const Obj != null() { + if self.is_ptr() && self.get_ptr() as *const Obj != null() { // Ignore null pointers used in text_iter. self.forward() } else { @@ -460,32 +445,20 @@ pub const TAG_ARRAY_SLICE_MIN: Tag = 32; #[repr(C)] // See the note at the beginning of this module pub struct Obj { pub tag: Tag, - // Cannot use `#[incremental_gc]` as Rust only allows non-macro attributes for fields. - #[cfg(feature = "incremental_gc")] /// Forwarding pointer to support object moving in the incremental GC. pub forward: Value, } impl Obj { - #[incremental_gc] pub fn init_forward(&mut self, value: Value) { self.forward = value; } - #[non_incremental_gc] - pub fn init_forward(&mut self, _value: Value) {} - /// Check whether the object's forwarding pointer refers to a different location. - #[incremental_gc] pub unsafe fn is_forwarded(self: *const Self) -> bool { (*self).forward.get_ptr() != self as usize } - #[non_incremental_gc] - pub unsafe fn is_forwarded(self: *const Self) -> bool { - false - } - pub unsafe fn tag(self: *const Self) -> Tag { (*self).tag } @@ -758,16 +731,10 @@ impl BigInt { self.add(1) as *mut mp_digit // skip closure header } - #[incremental_gc] pub unsafe fn forward(self: *mut Self) -> *mut Self { (*self).header.forward.as_bigint() } - #[non_incremental_gc] - pub unsafe fn forward(self: *mut Self) -> *mut Self { - self - } - pub unsafe fn from_payload(ptr: *mut mp_digit) -> *mut Self { let bigint = (ptr as *mut u32).sub(size_of::().as_usize()) as *mut BigInt; bigint.forward() diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 9dd221c0202..158240b67bb 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -577,17 +577,8 @@ module E = struct let mem_size env = Int32.(add (div (get_end_of_static_memory env) page_size) 1l) - let gc_strategy_name gc_strategy = match gc_strategy with - | Flags.MarkCompact -> "compacting" - | Flags.Copying -> "copying" - | Flags.Generational -> "generational" - | Flags.Incremental -> "incremental" - let collect_garbage env = - (* GC function name = "schedule_"? ("compacting" | "copying" | "generational" | "incremental") "_gc" *) - let name = gc_strategy_name !Flags.gc_strategy in - let gc_fn = if !Flags.force_gc then name else "schedule_" ^ name in - call_import env "rts" (gc_fn ^ "_gc") + call_import env "rts" "schedule_incremental_gc" (* See Note [Candid subtype checks] *) (* NB: we don't bother detecting duplicate registrations here because the code sharing machinery @@ -861,30 +852,14 @@ module Func = struct end (* Func *) module RTS = struct - let incremental_gc_imports env = + (* The connection to the C and Rust parts of the RTS *) + let system_imports env = E.add_func_import env "rts" "initialize_incremental_gc" [] []; E.add_func_import env "rts" "schedule_incremental_gc" [] []; E.add_func_import env "rts" "incremental_gc" [] []; E.add_func_import env "rts" "write_with_barrier" [I32Type; I32Type] []; E.add_func_import env "rts" "allocation_barrier" [I32Type] [I32Type]; E.add_func_import env "rts" "running_gc" [] [I32Type]; - () - - let non_incremental_gc_imports env = - E.add_func_import env "rts" "initialize_copying_gc" [] []; - E.add_func_import env "rts" "initialize_compacting_gc" [] []; - E.add_func_import env "rts" "initialize_generational_gc" [] []; - E.add_func_import env "rts" "schedule_copying_gc" [] []; - E.add_func_import env "rts" "schedule_compacting_gc" [] []; - E.add_func_import env "rts" "schedule_generational_gc" [] []; - E.add_func_import env "rts" "copying_gc" [] []; - E.add_func_import env "rts" "compacting_gc" [] []; - E.add_func_import env "rts" "generational_gc" [] []; - E.add_func_import env "rts" "post_write_barrier" [I32Type] []; - () - - (* The connection to the C and Rust parts of the RTS *) - let system_imports env = E.add_func_import env "rts" "load_stable_actor" [] [I32Type]; E.add_func_import env "rts" "save_stable_actor" [I32Type] []; E.add_func_import env "rts" "set_static_root" [I32Type] []; @@ -1002,10 +977,6 @@ module RTS = struct E.add_func_import env "rts" "stream_shutdown" [I32Type] []; E.add_func_import env "rts" "stream_reserve" [I32Type; I32Type] [I32Type]; E.add_func_import env "rts" "stream_stable_dest" [I32Type; I64Type; I64Type] []; - if !Flags.gc_strategy = Flags.Incremental then - incremental_gc_imports env - else - non_incremental_gc_imports env; () end (* RTS *) @@ -1019,9 +990,7 @@ module GC = struct let register_globals env = E.add_global64 env "__mutator_instructions" Mutable 0L; - E.add_global64 env "__collector_instructions" Mutable 0L; - if !Flags.gc_strategy <> Flags.Incremental then - E.add_global32 env "_HP" Mutable 0l + E.add_global64 env "__collector_instructions" Mutable 0L let get_mutator_instructions env = G.i (GlobalGet (nr (E.get_global env "__mutator_instructions"))) @@ -1033,17 +1002,6 @@ module GC = struct let set_collector_instructions env = G.i (GlobalSet (nr (E.get_global env "__collector_instructions"))) - let get_heap_pointer env = - if !Flags.gc_strategy <> Flags.Incremental then - G.i (GlobalGet (nr (E.get_global env "_HP"))) - else - assert false - let set_heap_pointer env = - if !Flags.gc_strategy <> Flags.Incremental then - G.i (GlobalSet (nr (E.get_global env "_HP"))) - else - assert false - let record_mutator_instructions env = match E.mode env with | Flags.(ICMode | RefMode) -> @@ -1098,9 +1056,6 @@ module Heap = struct compile_unboxed_const n ^^ E.call_import env "rts" "alloc_words" - let ensure_allocated env = - alloc env 0l ^^ G.i Drop (* dummy allocation, ensures that the page HP points into is backed *) - (* Heap objects *) (* At this level of abstraction, heap objects are just flat arrays of words *) @@ -1498,7 +1453,6 @@ end (* BitTagged *) module Tagged = struct (* Tagged objects all have an object header consisting of a tag and a forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. The tag is to describe their runtime type and serves to traverse the heap (serialization, GC), but also for objectification of arrays. @@ -1578,63 +1532,31 @@ module Tagged = struct | CoercionFailure -> 0xfffffffel | StableSeen -> 0xffffffffl - (* Declare `env` for lazy computation of the header size when the compile environment with compile flags are defined *) - let header_size env = - if !Flags.gc_strategy = Flags.Incremental then 2l else 1l + let header_size = 2l (* The tag *) let tag_field = 0l - let forwarding_pointer_field env = - assert (!Flags.gc_strategy = Flags.Incremental); - 1l + let forwarding_pointer_field = 1l (* Note: post-allocation barrier must be applied after initialization *) let alloc env size tag = assert (size > 1l); let name = Printf.sprintf "alloc_size<%d>_tag<%d>" (Int32.to_int size) (Int32.to_int (int_of_tag tag)) in - (* Computes a (conservative) mask for the bumped HP, so that the existence of non-zero bits under it - guarantees that a page boundary crossing didn't happen (i.e. no ripple-carry). *) - let overflow_mask increment = - let n = Int32.to_int increment in - assert (n > 0 && n < 0x8000); - let page_mask = Int32.sub page_size 1l in - (* We can extend the mask to the right if the bump increment is a power of two. *) - let ext = if Numerics.Nat16.(to_int (popcnt (of_int n))) = 1 then increment else 0l in - Int32.(logor ext (logand page_mask (shift_left minus_one (16 - Numerics.Nat16.(to_int (clz (of_int n))))))) in Func.share_code0 env name [I32Type] (fun env -> let set_object, get_object = new_local env "new_object" in - let size_in_bytes = Int32.(mul size Heap.word_size) in - let half_page_size = Int32.div page_size 2l in - (if !Flags.gc_strategy <> Flags.Incremental && size_in_bytes < half_page_size then - GC.get_heap_pointer env ^^ - GC.get_heap_pointer env ^^ - compile_add_const size_in_bytes ^^ - GC.set_heap_pointer env ^^ - GC.get_heap_pointer env ^^ - compile_bitand_const (overflow_mask size_in_bytes) ^^ - G.if0 - G.nop (* no page crossing *) - (Heap.ensure_allocated env) (* ensure that HP's page is allocated *) - else - Heap.alloc env size) ^^ + Heap.alloc env size ^^ set_object ^^ get_object ^^ compile_unboxed_const (int_of_tag tag) ^^ Heap.store_field tag_field ^^ - (if !Flags.gc_strategy = Flags.Incremental then - get_object ^^ (* object pointer *) - get_object ^^ (* forwarding pointer *) - Heap.store_field (forwarding_pointer_field env) - else - G.nop) ^^ + get_object ^^ (* object pointer *) + get_object ^^ (* forwarding pointer *) + Heap.store_field forwarding_pointer_field ^^ get_object ) let load_forwarding_pointer env = - (if !Flags.gc_strategy = Flags.Incremental then - Heap.load_field (forwarding_pointer_field env) - else - G.nop) + Heap.load_field forwarding_pointer_field let store_tag env tag = load_forwarding_pointer env ^^ @@ -1646,33 +1568,29 @@ module Tagged = struct Heap.load_field tag_field let check_forwarding env unskewed = - (if !Flags.gc_strategy = Flags.Incremental then - let name = "check_forwarding_" ^ if unskewed then "unskewed" else "skewed" in - Func.share_code1 env name ("object", I32Type) [I32Type] (fun env get_object -> - let set_object = G.setter_for get_object in - (if unskewed then - get_object ^^ - compile_unboxed_const ptr_skew ^^ - G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ - set_object - else G.nop) ^^ - get_object ^^ - load_forwarding_pointer env ^^ - get_object ^^ - G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^ - E.else_trap_with env "missing object forwarding" ^^ + let name = "check_forwarding_" ^ if unskewed then "unskewed" else "skewed" in + Func.share_code1 env name ("object", I32Type) [I32Type] (fun env get_object -> + let set_object = G.setter_for get_object in + (if unskewed then get_object ^^ - (if unskewed then - compile_unboxed_const ptr_unskew ^^ - G.i (Binary (Wasm.Values.I32 I32Op.Add)) - else G.nop)) - else G.nop) - + compile_unboxed_const ptr_skew ^^ + G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ + set_object + else G.nop) ^^ + get_object ^^ + load_forwarding_pointer env ^^ + get_object ^^ + G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^ + E.else_trap_with env "missing object forwarding" ^^ + get_object ^^ + (if unskewed then + compile_unboxed_const ptr_unskew ^^ + G.i (Binary (Wasm.Values.I32 I32Op.Add)) + else G.nop)) + let check_forwarding_for_store env typ = - (if !Flags.gc_strategy = Flags.Incremental then - let (set_value, get_value, _) = new_local_ env typ "value" in - set_value ^^ check_forwarding env false ^^ get_value - else G.nop) + let (set_value, get_value, _) = new_local_ env typ "value" in + set_value ^^ check_forwarding env false ^^ get_value let load_field env index = (if !Flags.sanity then check_forwarding env false else G.nop) ^^ @@ -1774,10 +1692,7 @@ module Tagged = struct branch_with env retty (List.filter (fun (tag,c) -> can_have_tag ty tag) branches) let allocation_barrier env = - (if !Flags.gc_strategy = Flags.Incremental then - E.call_import env "rts" "allocation_barrier" - else - G.nop) + E.call_import env "rts" "allocation_barrier" let write_with_barrier env = let (set_value, get_value) = new_local env "written_value" in @@ -1795,14 +1710,14 @@ module Tagged = struct let obj env tag element_instructions : G.t = let n = List.length element_instructions in - let size = (Int32.add (Wasm.I32.of_int_u n) (header_size env)) in + let size = (Int32.add (Wasm.I32.of_int_u n) header_size) in let (set_object, get_object) = new_local env "new_object" in alloc env size tag ^^ set_object ^^ let init_elem idx instrs : G.t = get_object ^^ instrs ^^ - Heap.store_field (Int32.add (Wasm.I32.of_int_u idx) (header_size env)) + Heap.store_field (Int32.add (Wasm.I32.of_int_u idx) header_size) in G.concat_mapi init_elem element_instructions ^^ get_object ^^ @@ -1819,7 +1734,6 @@ module MutBox = struct └──────┴─────┴─────────┘ The object header includes the obj tag (MutBox) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. *) let field = Tagged.header_size @@ -1829,14 +1743,14 @@ module MutBox = struct let load_field env = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (field env) + Tagged.load_field env field let store_field env = let (set_mutbox_value, get_mutbox_value) = new_local env "mutbox_value" in set_mutbox_value ^^ Tagged.load_forwarding_pointer env ^^ get_mutbox_value ^^ - Tagged.store_field env (field env) + Tagged.store_field env field end @@ -1908,7 +1822,7 @@ module Opt = struct let load_some_payload_field env = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (some_payload_field env) + Tagged.load_field env some_payload_field let project env = Func.share_code1 env "opt_project" ("x", I32Type) [I32Type] (fun env get_x -> @@ -1940,11 +1854,10 @@ module Variant = struct └──────┴─────┴────────────┴─────────┘ The object header includes the obj tag (TAG_VARIANT) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. *) let variant_tag_field = Tagged.header_size - let payload_field env = Int32.add (variant_tag_field env) 1l + let payload_field = Int32.add variant_tag_field 1l let hash_variant_label env : Mo_types.Type.lab -> int32 = E.hash env @@ -1954,11 +1867,11 @@ module Variant = struct let get_variant_tag env = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (variant_tag_field env) + Tagged.load_field env variant_tag_field let project env = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (payload_field env) + Tagged.load_field env payload_field (* Test if the top of the stack points to a variant with this label *) let test_is env l = @@ -1979,24 +1892,22 @@ module Closure = struct └──────┴─────┴───────┴──────┴──────────────┘ The object header includes the object tag (TAG_CLOSURE) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. - *) - let header_size env = Int32.add (Tagged.header_size env) 2l + let header_size = Int32.add Tagged.header_size 2l let funptr_field = Tagged.header_size - let len_field env = Int32.add 1l (Tagged.header_size env) + let len_field = Int32.add 1l Tagged.header_size let load_data env i = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (Int32.add (header_size env) i) + Tagged.load_field env (Int32.add header_size i) let store_data env i = let (set_closure_data, get_closure_data) = new_local env "closure_data" in set_closure_data ^^ Tagged.load_forwarding_pointer env ^^ get_closure_data ^^ - Tagged.store_field env (Int32.add (header_size env) i) + Tagged.store_field env (Int32.add header_size i) let prepare_closure_call env = Tagged.load_forwarding_pointer env @@ -2014,7 +1925,7 @@ module Closure = struct FakeMultiVal.ty (Lib.List.make n_res I32Type))) in (* get the table index *) Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (funptr_field env) ^^ + Tagged.load_field env funptr_field ^^ (* All done: Call! *) G.i (CallIndirect (nr ty)) ^^ FakeMultiVal.load env (Lib.List.make n_res I32Type) @@ -2041,8 +1952,6 @@ module BoxedWord64 = struct └──────┴─────┴─────┴─────┘ The object header includes the object tag (Bits64) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. - *) let payload_field = Tagged.header_size @@ -2058,10 +1967,10 @@ module BoxedWord64 = struct let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i64" in - let size = if !Flags.gc_strategy = Flags.Incremental then 4l else 3l in + let size = 4l in Tagged.alloc env size Tagged.Bits64 ^^ set_i ^^ - get_i ^^ compile_elem ^^ Tagged.store_field64 env (payload_field env) ^^ + get_i ^^ compile_elem ^^ Tagged.store_field64 env payload_field ^^ get_i ^^ Tagged.allocation_barrier env @@ -2075,7 +1984,7 @@ module BoxedWord64 = struct get_n ^^ BitTagged.if_tagged_scalar env [I64Type] ( get_n ^^ BitTagged.untag env) - ( get_n ^^ Tagged.load_forwarding_pointer env ^^ Tagged.load_field64 env (payload_field env)) + ( get_n ^^ Tagged.load_forwarding_pointer env ^^ Tagged.load_field64 env payload_field) ) end (* BoxedWord64 *) @@ -2167,7 +2076,7 @@ module BoxedSmallWord = struct *) - let payload_field env = Tagged.header_size env + let payload_field = Tagged.header_size let lit env i = if BitTagged.can_tag_const (Int64.of_int (Int32.to_int i)) @@ -2180,10 +2089,10 @@ module BoxedSmallWord = struct let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i32" in - let size = if !Flags.gc_strategy = Flags.Incremental then 3l else 2l in + let size = 3l in Tagged.alloc env size Tagged.Bits32 ^^ set_i ^^ - get_i ^^ compile_elem ^^ Tagged.store_field env (payload_field env) ^^ + get_i ^^ compile_elem ^^ Tagged.store_field env payload_field ^^ get_i ^^ Tagged.allocation_barrier env @@ -2200,7 +2109,7 @@ module BoxedSmallWord = struct get_n ^^ BitTagged.if_tagged_scalar env [I32Type] (get_n ^^ BitTagged.untag_i32) - (get_n ^^ Tagged.load_forwarding_pointer env ^^ Tagged.load_field env (payload_field env)) + (get_n ^^ Tagged.load_forwarding_pointer env ^^ Tagged.load_field env payload_field) ) let _lit env n = compile_unboxed_const n ^^ box env @@ -2408,10 +2317,9 @@ module Float = struct debug inspection (or GC representation change) arises. The object header includes the object tag (Bits64) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. *) - let payload_field env = Tagged.header_size env + let payload_field = Tagged.header_size let compile_unboxed_const f = G.i (Const (nr (Wasm.Values.F64 f))) @@ -2422,15 +2330,15 @@ module Float = struct let box env = Func.share_code1 env "box_f64" ("f", F64Type) [I32Type] (fun env get_f -> let (set_i, get_i) = new_local env "boxed_f64" in - let size = Int32.add (Tagged.header_size env) 2l in + let size = Int32.add Tagged.header_size 2l in Tagged.alloc env size Tagged.Bits64 ^^ set_i ^^ - get_i ^^ get_f ^^ Tagged.store_field_float64 env (payload_field env) ^^ + get_i ^^ get_f ^^ Tagged.store_field_float64 env payload_field ^^ get_i ^^ Tagged.allocation_barrier env ) - let unbox env = Tagged.load_forwarding_pointer env ^^ Tagged.load_field_float64 env (payload_field env) + let unbox env = Tagged.load_forwarding_pointer env ^^ Tagged.load_field_float64 env payload_field end (* Float *) @@ -3373,7 +3281,6 @@ module Object = struct └─────────────┴─────────────┴───┘ The object header includes the object tag (Object) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. The field hash array lives in static memory (so no size header needed). The hash_ptr is skewed. @@ -3390,11 +3297,11 @@ module Object = struct not for the implementing of sharing of mutable stable values. *) - let header_size env = Int32.add (Tagged.header_size env) 2l + let header_size = Int32.add Tagged.header_size 2l (* Number of object fields *) - let size_field env = Int32.add (Tagged.header_size env) 0l - let hash_ptr_field env = Int32.add (Tagged.header_size env) 1l + let size_field = Int32.add Tagged.header_size 0l + let hash_ptr_field = Int32.add Tagged.header_size 1l module FieldEnv = Env.Make(String) @@ -3422,18 +3329,18 @@ module Object = struct (* Allocate memory *) let (set_ri, get_ri, ri) = new_local_ env I32Type "obj" in - Tagged.alloc env (Int32.add (header_size env) sz) Tagged.Object ^^ + Tagged.alloc env (Int32.add header_size sz) Tagged.Object ^^ set_ri ^^ (* Set size *) get_ri ^^ compile_unboxed_const sz ^^ - Tagged.store_field env (size_field env) ^^ + Tagged.store_field env size_field ^^ (* Set hash_ptr *) get_ri ^^ compile_unboxed_const hash_ptr ^^ - Tagged.store_field env (hash_ptr_field env) ^^ + Tagged.store_field env hash_ptr_field ^^ (* Write all the fields *) let init_field (name, mk_is) : G.t = @@ -3441,7 +3348,7 @@ module Object = struct get_ri ^^ mk_is () ^^ let i = FieldEnv.find name name_pos_map in - let offset = Int32.add (header_size env) i in + let offset = Int32.add header_size i in Tagged.store_field env offset in G.concat_map init_field fs ^^ @@ -3459,14 +3366,14 @@ module Object = struct get_x ^^ Tagged.load_forwarding_pointer env ^^ set_x ^^ - get_x ^^ Tagged.load_field env (hash_ptr_field env) ^^ + get_x ^^ Tagged.load_field env hash_ptr_field ^^ (* Linearly scan through the fields (binary search can come later) *) (* unskew h_ptr and advance both to low bound *) compile_add_const Int32.(add ptr_unskew (mul Heap.word_size (of_int low_bound))) ^^ set_h_ptr ^^ get_x ^^ - compile_add_const Int32.(mul Heap.word_size (add (header_size env) (of_int low_bound))) ^^ + compile_add_const Int32.(mul Heap.word_size (add header_size (of_int low_bound))) ^^ set_x ^^ G.loop0 ( get_h_ptr ^^ load_unskewed_ptr ^^ @@ -3489,7 +3396,7 @@ module Object = struct get_x ^^ get_hash ^^ idx_hash_raw env low_bound ^^ load_ptr ^^ Tagged.load_forwarding_pointer env ^^ - compile_add_const (Int32.mul (MutBox.field env) Heap.word_size) + compile_add_const (Int32.mul MutBox.field Heap.word_size) ) else idx_hash_raw env low_bound @@ -3545,7 +3452,6 @@ module Blob = struct └──────┴─────┴─────────┴──────────────────┘ The object header includes the object tag (Blob) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. This heap object is used for various kinds of binary, non-pointer data. @@ -3553,12 +3459,12 @@ module Blob = struct Unicode. *) - let header_size env = Int32.add (Tagged.header_size env) 1l - let len_field env = Int32.add (Tagged.header_size env) 0l + let header_size = Int32.add Tagged.header_size 1l + let len_field = Int32.add Tagged.header_size 0l let len env = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (len_field env) + Tagged.load_field env len_field let len_nat env = Func.share_code1 env "blob_len" ("text", I32Type) [I32Type] (fun env get -> @@ -3579,7 +3485,7 @@ module Blob = struct (* uninitialized blob payload is allowed by the barrier *) Tagged.allocation_barrier env - let unskewed_payload_offset env = Int32.(add ptr_unskew (mul Heap.word_size (header_size env))) + let unskewed_payload_offset env = Int32.(add ptr_unskew (mul Heap.word_size header_size)) let payload_ptr_unskewed env = Tagged.load_forwarding_pointer env ^^ @@ -3776,7 +3682,6 @@ module Text = struct └──────┴─────┴─────────┴───────┴───────┘ The object header includes the object tag (TAG_CONCAT defined in rts/types.rs) and the forwarding pointer - The forwarding pointer is only reserved if compiled for the incremental GC. This is internal to rts/text.c, with the exception of GC-related code. *) @@ -3850,30 +3755,29 @@ module Arr = struct └──────┴─────┴──────────┴────────┴───┘ The object header includes the object tag (Array) and the forwarding pointer. - The forwarding pointer is only reserved if compiled for the incremental GC. No difference between mutable and immutable arrays. *) - let header_size env = Int32.add (Tagged.header_size env) 1l + let header_size = Int32.add Tagged.header_size 1l let element_size = 4l - let len_field env = Int32.add (Tagged.header_size env) 0l + let len_field = Int32.add Tagged.header_size 0l let len env = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (len_field env) + Tagged.load_field env len_field (* Static array access. No checking *) let load_field env n = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env Int32.(add n (header_size env)) + Tagged.load_field env Int32.(add n header_size) (* Dynamic array access. Returns the address (not the value) of the field. Does no bounds checking *) let unsafe_idx env = Func.share_code2 env "Array.unsafe_idx" (("array", I32Type), ("idx", I32Type)) [I32Type] (fun env get_array get_idx -> get_idx ^^ - compile_add_const (header_size env) ^^ + compile_add_const header_size ^^ compile_mul_const element_size ^^ get_array ^^ Tagged.load_forwarding_pointer env ^^ @@ -3892,7 +3796,7 @@ module Arr = struct E.else_trap_with env "Array index out of bounds" ^^ get_idx ^^ - compile_add_const (header_size env) ^^ + compile_add_const header_size ^^ compile_mul_const element_size ^^ get_array ^^ Tagged.load_forwarding_pointer env ^^ @@ -3932,7 +3836,7 @@ module Arr = struct get_array ^^ Tagged.load_forwarding_pointer env ^^ set_array ^^ (* Initial element pointer, skewed *) - compile_unboxed_const (header_size env) ^^ + compile_unboxed_const header_size ^^ compile_mul_const element_size ^^ get_array ^^ G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ @@ -3940,7 +3844,7 @@ module Arr = struct (* Upper pointer boundary, skewed *) get_array ^^ - Tagged.load_field env (len_field env) ^^ + Tagged.load_field env len_field ^^ compile_mul_const element_size ^^ get_pointer ^^ G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ @@ -4080,7 +3984,7 @@ module Tuple = struct (* Expects on the stack the pointer to the array. *) let load_n env n = Tagged.load_forwarding_pointer env ^^ - Tagged.load_field env (Int32.add (Arr.header_size env) n) + Tagged.load_field env (Int32.add Arr.header_size n) (* Takes n elements of the stack and produces an argument tuple *) let from_stack env n = @@ -5151,31 +5055,6 @@ module RTS_Exports = struct edesc = nr (FuncExport (nr rts_trap_fi)) }); - if !Flags.gc_strategy <> Flags.Incremental then - begin - let set_hp_fi = - E.add_fun env "__set_hp" ( - Func.of_body env ["new_hp", I32Type] [] (fun env -> - G.i (LocalGet (nr 0l)) ^^ - GC.set_heap_pointer env - ) - ) in - E.add_export env (nr { - name = Lib.Utf8.decode "setHP"; - edesc = nr (FuncExport (nr set_hp_fi)) - }); - - let get_hp_fi = E.add_fun env "__get_hp" ( - Func.of_body env [] [I32Type] (fun env -> - GC.get_heap_pointer env - ) - ) in - E.add_export env (nr { - name = Lib.Utf8.decode "getHP"; - edesc = nr (FuncExport (nr get_hp_fi)) - }) - end; - let stable64_write_moc_fi = if E.mode env = Flags.WASIMode then E.add_fun env "stable64_write_moc" ( @@ -7017,7 +6896,7 @@ module BlobStream : Stream = struct let absolute_offset env get_token = let offset = 8l in (* see invariant in `stream.rs` *) - let filled_field = Int32.add (Blob.len_field env) offset in + let filled_field = Int32.add Blob.len_field offset in get_token ^^ Tagged.load_field_unskewed env filled_field let checkpoint _env _get_token = G.i Drop @@ -7140,7 +7019,7 @@ module OldStabilization = struct let ptr64_field env = let offset = 1l in (* see invariant in `stream.rs` *) - Int32.add (Blob.len_field env) offset (* see invariant in `stream.rs`, padding for 64-bit after Stream header *) + Int32.add Blob.len_field offset (* see invariant in `stream.rs`, padding for 64-bit after Stream header *) let terminate env get_token get_data_size header_size = get_token ^^ @@ -7177,10 +7056,7 @@ module OldStabilization = struct let (set_dst, get_dst) = new_local env "dst" in let (set_len, get_len) = new_local env "len" in - (if !Flags.gc_strategy = Flags.Incremental then - E.call_import env "rts" "stop_gc_on_upgrade" - else - G.nop) ^^ + E.call_import env "rts" "stop_gc_on_upgrade" ^^ Externalization.serialize env [t] ^^ @@ -7699,57 +7575,36 @@ module Var = struct (* Returns desired stack representation, preparation code and code to consume the value onto the stack *) - let set_val env ae var : G.t * SR.t * G.t = match (VarEnv.lookup ae var, !Flags.gc_strategy) with - | (Some ((Local (sr, i)), _), _) -> + let set_val env ae var : G.t * SR.t * G.t = match VarEnv.lookup ae var with + | Some ((Local (sr, i)), _) -> G.nop, sr, G.i (LocalSet (nr i)) - | (Some ((HeapInd i), typ), Flags.Generational) when potential_pointer typ -> - G.i (LocalGet (nr i)), - SR.Vanilla, - MutBox.store_field env ^^ - G.i (LocalGet (nr i)) ^^ - Tagged.load_forwarding_pointer env ^^ (* not needed for this GC, but only for forward pointer sanity checks *) - compile_add_const ptr_unskew ^^ - compile_add_const (Int32.mul (MutBox.field env) Heap.word_size) ^^ - E.call_import env "rts" "post_write_barrier" - | (Some ((HeapInd i), typ), Flags.Incremental) when potential_pointer typ -> + | Some ((HeapInd i), typ) when potential_pointer typ -> G.i (LocalGet (nr i)) ^^ Tagged.load_forwarding_pointer env ^^ compile_add_const ptr_unskew ^^ - compile_add_const (Int32.mul (MutBox.field env) Heap.word_size), + compile_add_const (Int32.mul MutBox.field Heap.word_size), SR.Vanilla, Tagged.write_with_barrier env - | (Some ((HeapInd i), typ), _) -> + | Some ((HeapInd i), typ) -> G.i (LocalGet (nr i)), SR.Vanilla, MutBox.store_field env - | (Some ((Static index), typ), Flags.Generational) when potential_pointer typ -> - let (set_static_variable, get_static_variable) = new_local env "static_variable" in - GCRoots.get_static_variable env index ^^ - Tagged.load_forwarding_pointer env ^^ (* not needed for this GC, but only for forward pointer sanity checks *) - set_static_variable ^^ - get_static_variable, - SR.Vanilla, - MutBox.store_field env ^^ - get_static_variable ^^ - compile_add_const ptr_unskew ^^ - compile_add_const (Int32.mul (MutBox.field env) Heap.word_size) ^^ - E.call_import env "rts" "post_write_barrier" - | (Some ((Static index), typ), Flags.Incremental) when potential_pointer typ -> + | Some ((Static index), typ) when potential_pointer typ -> GCRoots.get_static_variable env index ^^ Tagged.load_forwarding_pointer env ^^ compile_add_const ptr_unskew ^^ - compile_add_const (Int32.mul (MutBox.field env) Heap.word_size), + compile_add_const (Int32.mul MutBox.field Heap.word_size), SR.Vanilla, Tagged.write_with_barrier env - | (Some ((Static index), typ), _) -> + | Some ((Static index), typ) -> GCRoots.get_static_variable env index, SR.Vanilla, MutBox.store_field env - | (Some ((Const _), _), _) -> fatal "set_val: %s is const" var - | (Some ((PublicMethod _), _), _) -> fatal "set_val: %s is PublicMethod" var - | (None, _) -> fatal "set_val: %s missing" var + | Some ((Const _), _) -> fatal "set_val: %s is const" var + | Some ((PublicMethod _), _) -> fatal "set_val: %s is PublicMethod" var + | None -> fatal "set_val: %s missing" var (* Stores the payload. Returns stack preparation code, and code that consumes the values from the stack *) let set_val_vanilla env ae var : G.t * G.t = @@ -7983,18 +7838,18 @@ module FuncDec = struct let code = (* Allocate a heap object for the closure *) - Tagged.alloc env (Int32.add (Closure.header_size env) len) Tagged.Closure ^^ + Tagged.alloc env (Int32.add Closure.header_size len) Tagged.Closure ^^ set_clos ^^ (* Store the function pointer number: *) get_clos ^^ compile_unboxed_const (E.add_fun_ptr env fi) ^^ - Tagged.store_field env (Closure.funptr_field env) ^^ + Tagged.store_field env Closure.funptr_field ^^ (* Store the length *) get_clos ^^ compile_unboxed_const len ^^ - Tagged.store_field env (Closure.len_field env) ^^ + Tagged.store_field env Closure.len_field ^^ (* Store all captured values *) store_env ^^ @@ -9199,46 +9054,25 @@ let compile_load_field env typ name = *) let rec compile_lexp (env : E.t) ae lexp : G.t * SR.t * G.t = (fun (code, sr, fill_code) -> G.(with_region lexp.at code, sr, with_region lexp.at fill_code)) @@ - match lexp.it, !Flags.gc_strategy with - | VarLE var, _ -> Var.set_val env ae var - | IdxLE (e1, e2), Flags.Generational when potential_pointer (Arr.element_type env e1.note.Note.typ) -> - let (set_field, get_field) = new_local env "field" in - compile_array_index env ae e1 e2 ^^ - set_field ^^ (* peepholes to tee *) - get_field, - SR.Vanilla, - store_ptr ^^ - get_field ^^ - compile_add_const ptr_unskew ^^ - E.call_import env "rts" "post_write_barrier" - | IdxLE (e1, e2), Flags.Incremental when potential_pointer (Arr.element_type env e1.note.Note.typ) -> + match lexp.it with + | VarLE var -> Var.set_val env ae var + | IdxLE (e1, e2) when potential_pointer (Arr.element_type env e1.note.Note.typ) -> compile_array_index env ae e1 e2 ^^ compile_add_const ptr_unskew, SR.Vanilla, Tagged.write_with_barrier env - | IdxLE (e1, e2), _ -> + | IdxLE (e1, e2) -> compile_array_index env ae e1 e2, SR.Vanilla, store_ptr - | DotLE (e, n), Flags.Generational when potential_pointer (Object.field_type env e.note.Note.typ n) -> - let (set_field, get_field) = new_local env "field" in - compile_exp_vanilla env ae e ^^ - Object.idx env e.note.Note.typ n ^^ - set_field ^^ (* peepholes to tee *) - get_field, - SR.Vanilla, - store_ptr ^^ - get_field ^^ - compile_add_const ptr_unskew ^^ - E.call_import env "rts" "post_write_barrier" - | DotLE (e, n), Flags.Incremental when potential_pointer (Object.field_type env e.note.Note.typ n) -> + | DotLE (e, n) when potential_pointer (Object.field_type env e.note.Note.typ n) -> compile_exp_vanilla env ae e ^^ (* Only real objects have mutable fields, no need to branch on the tag *) Object.idx env e.note.Note.typ n ^^ compile_add_const ptr_unskew, SR.Vanilla, Tagged.write_with_barrier env - | DotLE (e, n), _ -> + | DotLE (e, n) -> compile_exp_vanilla env ae e ^^ (* Only real objects have mutable fields, no need to branch on the tag *) Object.idx env e.note.Note.typ n, @@ -9425,7 +9259,7 @@ and compile_prim_invocation (env : E.t) ae p es at = fields, effectively arriving at the desired element *) G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ (* Not using Tagged.load_field since it is not a proper pointer to the array start *) - Heap.load_field (Arr.header_size env) (* loads the element at the byte offset *) + Heap.load_field Arr.header_size (* loads the element at the byte offset *) | GetPastArrayOffset spacing, [e] -> let shift = match spacing with @@ -11086,7 +10920,7 @@ and conclude_module env set_serialization_globals start_fi_o = (* Wrap the start function with the RTS initialization *) let rts_start_fi = E.add_fun env "rts_start" (Func.of_body env [] [] (fun env1 -> - E.call_import env "rts" ("initialize_" ^ E.gc_strategy_name !Flags.gc_strategy ^ "_gc") ^^ + E.call_import env "rts" ("initialize_incremental_gc") ^^ GCRoots.register_static_variables env ^^ match start_fi_o with | Some fi -> diff --git a/src/exes/moc.ml b/src/exes/moc.ml index f118b506296..6db95348069 100644 --- a/src/exes/moc.ml +++ b/src/exes/moc.ml @@ -141,22 +141,6 @@ let argspec = [ set_mode Compile ()), (* similar to --idl *) " compile and emit signature of stable types to `.most` file"; - "--generational-gc", - Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.Generational), - " use generational GC"; - - "--incremental-gc", - Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.Incremental), - " use incremental GC"; - - "--compacting-gc", - Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.MarkCompact), - " use compacting GC"; - - "--copying-gc", - Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.Copying), - " use copying GC (default)"; - "--force-gc", Arg.Unit (fun () -> Flags.force_gc := true), " disable GC scheduling, always do GC after an update message (for testing)"; diff --git a/src/js/common.ml b/src/js/common.ml index aa027ce6326..3e3f623ad03 100644 --- a/src/js/common.ml +++ b/src/js/common.ml @@ -221,8 +221,4 @@ let gc_flags option = match Js.to_string option with | "force" -> Flags.force_gc := true | "scheduling" -> Flags.force_gc := false - | "copying" -> Flags.gc_strategy := Mo_config.Flags.Copying - | "marking" -> Flags.gc_strategy := Mo_config.Flags.MarkCompact - | "generational" -> Flags.gc_strategy := Mo_config.Flags.Generational - | "incremental" -> Flags.gc_strategy := Mo_config.Flags.Incremental | _ -> raise (Invalid_argument "gc_flags: Unexpected flag") diff --git a/src/mo_config/flags.ml b/src/mo_config/flags.ml index aa031b5548e..bcdfd6d635c 100644 --- a/src/mo_config/flags.ml +++ b/src/mo_config/flags.ml @@ -4,8 +4,6 @@ module M = Map.Make(String) type compile_mode = WasmMode | ICMode | RefMode | WASIMode -type gc_strategy = MarkCompact | Copying | Generational | Incremental - let trace = ref false let verbose = ref false let print_warnings = ref true @@ -39,7 +37,6 @@ let omit_metadata_names : string list ref = ref [] let compiled = ref false let error_detail = ref 2 let sanity = ref false -let gc_strategy = ref Copying let force_gc = ref false let global_timer = ref true let experimental_field_aliasing = ref false diff --git a/src/pipeline/pipeline.ml b/src/pipeline/pipeline.ml index 9867532496e..efde3582809 100644 --- a/src/pipeline/pipeline.ml +++ b/src/pipeline/pipeline.ml @@ -660,11 +660,9 @@ let ir_passes mode prog_ir name = (* Compilation *) let load_as_rts () = - let rts = match (!Flags.gc_strategy, !Flags.sanity) with - | (Flags.Incremental, false) -> Rts.wasm_incremental_release - | (Flags.Incremental, true) -> Rts.wasm_incremental_debug - | (_, false) -> Rts.wasm_non_incremental_release - | (_, true) -> Rts.wasm_non_incremental_debug + let rts = match !Flags.sanity with + | false -> Rts.wasm_release + | true -> Rts.wasm_debug in Wasm_exts.CustomModuleDecode.decode "rts.wasm" (Lazy.force rts) diff --git a/src/rts/gen.sh b/src/rts/gen.sh index c4b90696ae0..513d2df5b52 100755 --- a/src/rts/gen.sh +++ b/src/rts/gen.sh @@ -11,8 +11,5 @@ if [ -z "$1" ]; then exit 1 fi -perl -0777 -ne 'print "let wasm_non_incremental_release = lazy \""; printf "\\x%02x", $_ for unpack("C*", $_); print "\"\n";' "$1/mo-rts.wasm" > "$file" -perl -0777 -ne 'print "let wasm_non_incremental_debug = lazy \""; printf "\\x%02x", $_ for unpack("C*", $_); print "\"";' "$1/mo-rts-debug.wasm" >> "$file" - -perl -0777 -ne 'print "let wasm_incremental_release = lazy \""; printf "\\x%02x", $_ for unpack("C*", $_); print "\"\n";' "$1/mo-rts-incremental.wasm" >> "$file" -perl -0777 -ne 'print "let wasm_incremental_debug = lazy \""; printf "\\x%02x", $_ for unpack("C*", $_); print "\"";' "$1/mo-rts-incremental-debug.wasm" >> "$file" +perl -0777 -ne 'print "let wasm_release = lazy \""; printf "\\x%02x", $_ for unpack("C*", $_); print "\"\n";' "$1/mo-rts.wasm" > "$file" +perl -0777 -ne 'print "let wasm_debug = lazy \""; printf "\\x%02x", $_ for unpack("C*", $_); print "\"";' "$1/mo-rts-debug.wasm" >> "$file" diff --git a/src/rts/rts.ml b/src/rts/rts.ml index db1d9529e6b..39fff73cb41 100644 --- a/src/rts/rts.ml +++ b/src/rts/rts.ml @@ -19,8 +19,5 @@ let load_file env = Printf.eprintf "Environment variable %s not set. Please run moc via the bin/moc wrapper (which should be in your PATH in the nix-shell)." env; exit 1 -let wasm_non_incremental_release : string Lazy.t = lazy (load_file "MOC_NON_INCREMENTAL_RELEASE_RTS") -let wasm_non_incremental_debug : string Lazy.t = lazy (load_file "MOC_NON_INCREMENTAL_DEBUG_RTS") - -let wasm_incremental_release : string Lazy.t = lazy (load_file "MOC_INCREMENTAL_RELEASE_RTS") -let wasm_incremental_debug : string Lazy.t = lazy (load_file "MOC_INCREMENTAL_DEBUG_RTS") +let wasm_release : string Lazy.t = lazy (load_file "MOC_RELEASE_RTS") +let wasm_debug : string Lazy.t = lazy (load_file "MOC_DEBUG_RTS") diff --git a/src/rts/rts.mli b/src/rts/rts.mli index 1955b88014b..27b30301429 100644 --- a/src/rts/rts.mli +++ b/src/rts/rts.mli @@ -1,5 +1,2 @@ -val wasm_non_incremental_release : string Lazy.t -val wasm_non_incremental_debug : string Lazy.t - -val wasm_incremental_release : string Lazy.t -val wasm_incremental_debug : string Lazy.t +val wasm_release : string Lazy.t +val wasm_debug : string Lazy.t From 04f027b73d27c06fa093dac9cab63c4dcf152c96 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 16 Aug 2023 11:46:12 +0200 Subject: [PATCH 029/260] Bug fix --- src/codegen/compile.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 158240b67bb..89223939603 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7293,7 +7293,8 @@ module GCRoots = struct let get_static_variable env index = E.call_import env "rts" "get_static_root" ^^ compile_unboxed_const index ^^ - Arr.idx env + Arr.idx env ^^ + load_ptr end (* GCRoots *) module StackRep = struct From d9b4253d4d438659987509cef8452a05dd5ea934 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 16 Aug 2023 12:02:30 +0200 Subject: [PATCH 030/260] Code refactoring --- src/codegen/compile.ml | 428 ++++++----------------------------------- 1 file changed, 64 insertions(+), 364 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 89223939603..a3e7c42afb1 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -629,7 +629,6 @@ let compile_eq_const = function let compile_op64_const op i = compile_const_64 i ^^ G.i (Binary (Wasm.Values.I64 op)) -let _compile_add64_const = compile_op64_const I64Op.Add let compile_sub64_const = compile_op64_const I64Op.Sub let compile_mul64_const = compile_op64_const I64Op.Mul let _compile_divU64_const = compile_op64_const I64Op.DivU @@ -1060,12 +1059,6 @@ module Heap = struct (* At this level of abstraction, heap objects are just flat arrays of words *) -(* - let load_field_unskewed (i : int32) : G.t = - let offset = Int32.mul word_size i in - G.i (Load {ty = I32Type; align = 2; offset; sz = None}) -*) - let load_field (i : int32) : G.t = let offset = Int32.(add (mul word_size i) ptr_unskew) in G.i (Load {ty = I32Type; align = 2; offset; sz = None}) @@ -1078,12 +1071,6 @@ module Heap = struct 32 bit fields as one 64 bit number *) (* Requires little-endian encoding, see also `Stream` in `types.rs` *) -(* - let load_field64_unskewed (i : int32) : G.t = - let offset = Int32.mul word_size i in - G.i (Load {ty = I64Type; align = 2; offset; sz = None}) -*) - let load_field64 (i : int32) : G.t = let offset = Int32.(add (mul word_size i) ptr_unskew) in G.i (Load {ty = I64Type; align = 2; offset; sz = None}) @@ -1600,16 +1587,6 @@ module Tagged = struct (if !Flags.sanity then check_forwarding_for_store env I32Type else G.nop) ^^ Heap.store_field index -(* - let load_field_unskewed env index = - (if !Flags.sanity then check_forwarding env true else G.nop) ^^ - Heap.load_field_unskewed index - - let load_field64_unskewed env index = - (if !Flags.sanity then check_forwarding env true else G.nop) ^^ - Heap.load_field64_unskewed index -*) - let load_field64 env index = (if !Flags.sanity then check_forwarding env false else G.nop) ^^ Heap.load_field64 index @@ -3642,32 +3619,6 @@ module Blob = struct let dyn_alloc_scratch env = alloc env ^^ payload_ptr_unskewed env -(* - (* TODO: rewrite using MemoryFill *) - let clear env = - Func.share_code1 env "blob_clear" ("x", I32Type) [] (fun env get_x -> - let (set_ptr, get_ptr) = new_local env "ptr" in - let (set_len, get_len) = new_local env "len" in - get_x ^^ - as_ptr_len env ^^ - set_len ^^ - set_ptr ^^ - - (* round to word size *) - get_len ^^ - compile_add_const (Int32.sub Heap.word_size 1l) ^^ - compile_divU_const Heap.word_size ^^ - - (* clear all words *) - from_0_to_n env (fun get_i -> - get_ptr ^^ - compile_unboxed_const 0l ^^ - store_unskewed_ptr ^^ - get_ptr ^^ - compile_add_const Heap.word_size ^^ - set_ptr)) -*) - end (* Blob *) module Text = struct @@ -4724,10 +4675,6 @@ end (* Cycles *) module StableMem = struct - - (* start from 1 to avoid accidental reads of 0 *) - let _version = Int32.of_int 1 - let register_globals env = (* size (in pages) *) E.add_global64 env "__stablemem_size" Mutable 0L @@ -4819,40 +4766,6 @@ module StableMem = struct IC.system_call env "stable64_write")) | _ -> assert false -(* - let _read_word32 env = - read env false "word32" I32Type 4l load_unskewed_ptr - let write_word32 env = - write env false "word32" I32Type 4l store_unskewed_ptr - - (* read and clear word32 from stable mem offset on stack *) - let read_and_clear_word32 env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> - Func.share_code1 env "__stablemem_read_and_clear_word32" - ("offset", I64Type) [I32Type] - (fun env get_offset -> - Stack.with_words env "temp_ptr" 1l (fun get_temp_ptr -> - let (set_word, get_word) = new_local env "word" in - (* read word *) - get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ - get_offset ^^ - compile_const_64 4L ^^ - IC.system_call env "stable64_read" ^^ - get_temp_ptr ^^ load_unskewed_ptr ^^ - set_word ^^ - (* write 0 *) - get_temp_ptr ^^ compile_unboxed_const 0l ^^ store_unskewed_ptr ^^ - get_offset ^^ - get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ - compile_const_64 4L ^^ - IC.system_call env "stable64_write" ^^ - (* return word *) - get_word - )) - | _ -> assert false -*) - (* ensure_pages : ensure at least num pages allocated, growing (real) stable memory if needed *) let ensure_pages env = @@ -4881,37 +4794,6 @@ module StableMem = struct get_size) | _ -> assert false -(* - (* ensure stable memory includes [offset..offset+size), assumes size > 0 *) - let ensure env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> - Func.share_code2 env "__stablemem_ensure" - (("offset", I64Type), ("size", I64Type)) [] - (fun env get_offset get_size -> - let (set_sum, get_sum) = new_local64 env "sum" in - get_offset ^^ - get_size ^^ - G.i (Binary (Wasm.Values.I64 I64Op.Add)) ^^ - set_sum ^^ - (* check for overflow *) - get_sum ^^ - get_offset ^^ - G.i (Compare (Wasm.Values.I64 I64Op.LtU)) ^^ - E.then_trap_with env "Range overflow" ^^ - (* ensure page *) - get_sum ^^ - compile_const_64 (Int64.of_int page_size_bits) ^^ - G.i (Binary (Wasm.Values.I64 I64Op.ShrU)) ^^ - compile_add64_const 1L ^^ - ensure_pages env ^^ - (* Check result *) - compile_const_64 0L ^^ - G.i (Compare (Wasm.Values.I64 I64Op.LtS)) ^^ - E.then_trap_with env "Out of stable memory.") - | _ -> assert false -*) - (* API *) let logical_grow env = @@ -6870,81 +6752,6 @@ end (* MakeSerialization *) module Serialization = MakeSerialization(BumpStream) -(* -module BlobStream : Stream = struct - let create env get_data_size set_token get_token header = - let header_size = Int32.of_int (String.length header) in - get_data_size ^^ compile_add_const header_size ^^ - E.call_import env "rts" "alloc_stream" ^^ set_token ^^ (* allocation barrier called in alloc_stream *) - get_token ^^ - Blob.lit env header ^^ - E.call_import env "rts" "stream_write_text" - - let check_filled env get_token get_data_size = - G.i Drop - - let terminate env get_token _get_data_size _header_size = - get_token ^^ E.call_import env "rts" "stream_split" ^^ - let set_blob, get_blob = new_local env "blob" in - set_blob ^^ - get_blob ^^ Blob.payload_ptr_unskewed env ^^ - get_blob ^^ Blob.len env - - let finalize_buffer code = code - - let name_for fn_name ts = "@Bl_" ^ fn_name ^ "<" ^ Typ_hash.typ_seq_hash ts ^ ">" - - let absolute_offset env get_token = - let offset = 8l in (* see invariant in `stream.rs` *) - let filled_field = Int32.add Blob.len_field offset in - get_token ^^ Tagged.load_field_unskewed env filled_field - - let checkpoint _env _get_token = G.i Drop - - let reserve env get_token bytes = - get_token ^^ compile_unboxed_const bytes ^^ E.call_import env "rts" "stream_reserve" - - let write_word_leb env get_token code = - let set_word, get_word = new_local env "word" in - code ^^ set_word ^^ - I32Leb.compile_store_to_data_buf_unsigned env get_word - (get_token ^^ I32Leb.compile_leb128_size get_word ^^ E.call_import env "rts" "stream_reserve") ^^ - G.i Drop - - let write_word_32 env get_token code = - reserve env get_token Heap.word_size ^^ - code ^^ - G.i (Store {ty = I32Type; align = 0; offset = 0l; sz = None}) - - let write_byte env get_token code = - get_token ^^ code ^^ - E.call_import env "rts" "stream_write_byte" - - let write_blob env get_token get_x = - let set_len, get_len = new_local env "len" in - get_x ^^ Blob.len env ^^ set_len ^^ - write_word_leb env get_token get_len ^^ - get_token ^^ - get_x ^^ Blob.payload_ptr_unskewed env ^^ - get_len ^^ - E.call_import env "rts" "stream_write" - - let write_text env get_token get_x = - write_word_leb env get_token (get_x ^^ Text.size env) ^^ - get_token ^^ get_x ^^ - E.call_import env "rts" "stream_write_text" - - let write_bignum_leb env get_token get_x = - get_token ^^ get_x ^^ - BigNum.compile_store_to_stream_unsigned env - - let write_bignum_sleb env get_token get_x = - get_token ^^ get_x ^^ - BigNum.compile_store_to_stream_signed env - -end -*) - module Stabilization = struct let load_stable_actor env = E.call_import env "rts" "load_stable_actor" @@ -6970,7 +6777,8 @@ module Stabilization = struct end (* -(* OldStabilization (serialization to/from stable memory) of both: +(* OldStabilization as migration code: + Deserializing a last time from explicit stable memory into the stable heap: * stable variables; and * virtual stable memory. c.f. @@ -6979,177 +6787,69 @@ end *) module OldStabilization = struct + (* start from 1 to avoid accidental reads of 0 *) + let stable_memory_version = Int32.of_int 1 let extend64 code = code ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) + let compile_add64_const = compile_op64_const I64Op.Add - (* The below stream implementation is geared towards the - tail section of stable memory, where the serialised - stable variables go. As such a few intimate details of - the stable memory layout are burnt in, such as the - variable `N` from the design document. *) - module StableMemoryStream : Stream = struct - include BlobStream - - let name_for fn_name ts = "@Sm_" ^ fn_name ^ "<" ^ Typ_hash.typ_seq_hash ts ^ ">" + let _read_word32 env = + StableMem.read env false "word32" I32Type 4l load_unskewed_ptr + let write_word32 env = + StableMem.write env false "word32" I32Type 4l store_unskewed_ptr - let create env get_data_size set_token get_token header = - create env (compile_unboxed_const 0x8000l) set_token get_token header ^^ - (* TODO: push header directly? *) + (* read and clear word32 from stable mem offset on stack *) + let read_and_clear_word32 env = + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + Func.share_code1 env "__stablemem_read_and_clear_word32" + ("offset", I64Type) [I32Type] + (fun env get_offset -> + Stack.with_words env "temp_ptr" 1l (fun get_temp_ptr -> + let (set_word, get_word) = new_local env "word" in + (* read word *) + get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ + get_offset ^^ + compile_const_64 4L ^^ + IC.system_call env "stable64_read" ^^ + get_temp_ptr ^^ load_unskewed_ptr ^^ + set_word ^^ + (* write 0 *) + get_temp_ptr ^^ compile_unboxed_const 0l ^^ store_unskewed_ptr ^^ + get_offset ^^ + get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ + compile_const_64 4L ^^ + IC.system_call env "stable64_write" ^^ + (* return word *) + get_word + )) + | _ -> assert false + (* TODO: rewrite using MemoryFill *) + let blob_clear env = + Func.share_code1 env "blob_clear" ("x", I32Type) [] (fun env get_x -> + let (set_ptr, get_ptr) = new_local env "ptr" in let (set_len, get_len) = new_local env "len" in - get_data_size ^^ - compile_add_const (Int32.of_int (String.length header)) ^^ + get_x ^^ + Blob.as_ptr_len env ^^ set_len ^^ + set_ptr ^^ - let (set_dst, get_dst) = new_local64 env "dst" in - StableMem.get_mem_size env ^^ - compile_shl64_const (Int64.of_int page_size_bits) ^^ - compile_add64_const 4L ^^ (* `N` is now on the stack *) - set_dst ^^ - - get_dst ^^ - extend64 get_len ^^ - StableMem.ensure env ^^ - - get_token ^^ - get_dst ^^ - get_dst ^^ extend64 get_len ^^ - G.i (Binary (Wasm.Values.I64 I64Op.Add)) ^^ - E.call_import env "rts" "stream_stable_dest" - - let ptr64_field env = - let offset = 1l in (* see invariant in `stream.rs` *) - Int32.add Blob.len_field offset (* see invariant in `stream.rs`, padding for 64-bit after Stream header *) - - let terminate env get_token get_data_size header_size = - get_token ^^ - E.call_import env "rts" "stream_shutdown" ^^ - compile_unboxed_zero ^^ (* no need to write *) - get_token ^^ - Tagged.load_field64_unskewed env (ptr64_field env) ^^ - StableMem.get_mem_size env ^^ - compile_shl64_const (Int64.of_int page_size_bits) ^^ - G.i (Binary (Wasm.Values.I64 I64Op.Sub)) ^^ - compile_sub64_const 4L ^^ (* `N` is now subtracted *) - G.i (Convert (Wasm.Values.I32 I32Op.WrapI64)) - - let finalize_buffer _ = G.nop (* everything is outputted already *) - - (* Returns a 32-bit unsigned int that is the number of bytes that would - have been written to stable memory if flushed. The difference - of two such numbers will always be an exact byte distance. *) - let absolute_offset env get_token = - let start64_field = Int32.add (ptr64_field env) 2l in (* see invariant in `stream.rs` *) - absolute_offset env get_token ^^ - get_token ^^ - Tagged.load_field64_unskewed env (ptr64_field env) ^^ - get_token ^^ - Tagged.load_field64_unskewed env start64_field ^^ - G.i (Binary (Wasm.Values.I64 I64Op.Sub)) ^^ - G.i (Convert (Wasm.Values.I32 I32Op.WrapI64)) ^^ - G.i (Binary (Wasm.Values.I32 I32Op.Add)) - end - - module Externalization = MakeSerialization(StableMemoryStream) - - let stabilize env t = - let (set_dst, get_dst) = new_local env "dst" in - let (set_len, get_len) = new_local env "len" in - - E.call_import env "rts" "stop_gc_on_upgrade" ^^ - - - Externalization.serialize env [t] ^^ - set_len ^^ - set_dst ^^ - - StableMem.get_mem_size env ^^ - G.i (Test (Wasm.Values.I64 I64Op.Eqz)) ^^ - G.if0 - begin (* ensure [0,..,3,...len+4) *) - compile_const_64 0L ^^ - extend64 get_len ^^ - compile_add64_const 4L ^^ (* reserve one word for size *) - StableMem.ensure env ^^ - - (* write len to initial word of stable memory*) - compile_const_64 0L ^^ - get_len ^^ - StableMem.write_word32 env ^^ - - (* copy data to following stable memory *) - Externalization.Strm.finalize_buffer - begin - compile_const_64 4L ^^ - extend64 get_dst ^^ - extend64 get_len ^^ - IC.system_call env "stable64_write" - end - end - begin - let (set_N, get_N) = new_local64 env "N" in - - (* let N = !size * page_size *) - StableMem.get_mem_size env ^^ - compile_shl64_const (Int64.of_int page_size_bits) ^^ - set_N ^^ - - (* grow mem to page including address - N + 4 + len + 4 + 4 + 4 = N + len + 16 - *) - get_N ^^ - extend64 get_len ^^ - compile_add64_const 16L ^^ - StableMem.ensure env ^^ - - get_N ^^ - get_len ^^ - StableMem.write_word32 env ^^ - - (* copy data to following stable memory *) - Externalization.Strm.finalize_buffer - begin - get_N ^^ - compile_add64_const 4L ^^ - extend64 get_dst ^^ - extend64 get_len ^^ - IC.system_call env "stable64_write" - end ^^ - - (* let M = pagesize * ic0.stable64_size64() - 1 *) - (* M is beginning of last page *) - let (set_M, get_M) = new_local64 env "M" in - IC.system_call env "stable64_size" ^^ - compile_sub64_const 1L ^^ - compile_shl64_const (Int64.of_int page_size_bits) ^^ - set_M ^^ - - (* store mem_size at M + (pagesize - 12) *) - get_M ^^ - compile_add64_const (Int64.sub page_size64 12L) ^^ - StableMem.get_mem_size env ^^ - G.i (Convert (Wasm.Values.I32 I32Op.WrapI64)) ^^ - (* TODO: write word64 *) - StableMem.write_word32 env ^^ - - (* save first word at M + (pagesize - 8); - mark first word as 0 *) - get_M ^^ - compile_add64_const (Int64.sub page_size64 8L) ^^ - compile_const_64 0L ^^ - StableMem.read_and_clear_word32 env ^^ - StableMem.write_word32 env ^^ - - (* save version at M + (pagesize - 4) *) - get_M ^^ - compile_add64_const (Int64.sub page_size64 4L) ^^ - (* TODO bump version? *) - compile_unboxed_const StableMem.version ^^ - StableMem.write_word32 env + (* round to word size *) + get_len ^^ + compile_add_const (Int32.sub Heap.word_size 1l) ^^ + compile_divU_const Heap.word_size ^^ - end + (* clear all words *) + from_0_to_n env (fun get_i -> + get_ptr ^^ + compile_unboxed_const 0l ^^ + store_unskewed_ptr ^^ + get_ptr ^^ + compile_add_const Heap.word_size ^^ + set_ptr)) - let destabilize env ty = + let _destabilize env ty = match E.mode env with | Flags.ICMode | Flags.RefMode -> let (set_pages, get_pages) = new_local64 env "pages" in @@ -7175,7 +6875,7 @@ module OldStabilization = struct let (set_len, get_len) = new_local env "len" in let (set_offset, get_offset) = new_local64 env "offset" in compile_const_64 0L ^^ - StableMem.read_and_clear_word32 env ^^ + read_and_clear_word32 env ^^ set_marker ^^ get_marker ^^ @@ -7194,28 +6894,28 @@ module OldStabilization = struct (* read version *) get_M ^^ compile_add64_const (Int64.sub page_size64 4L) ^^ - StableMem.read_and_clear_word32 env ^^ + read_and_clear_word32 env ^^ set_version ^^ (* check version *) get_version ^^ - compile_unboxed_const StableMem.version ^^ + compile_unboxed_const stable_memory_version ^^ G.i (Compare (Wasm.Values.I32 I32Op.GtU)) ^^ E.then_trap_with env (Printf.sprintf "higher stable memory version (expected %s)" - (Int32.to_string StableMem.version)) ^^ + (Int32.to_string stable_memory_version)) ^^ (* restore StableMem bytes [0..4) *) compile_const_64 0L ^^ get_M ^^ compile_add64_const (Int64.sub page_size64 8L) ^^ - StableMem.read_and_clear_word32 env ^^ - StableMem.write_word32 env ^^ + read_and_clear_word32 env ^^ + write_word32 env ^^ (* restore mem_size *) get_M ^^ compile_add64_const (Int64.sub page_size64 12L) ^^ - extend64 (StableMem.read_and_clear_word32 env) ^^ (*TODO: use 64 bits *) + extend64 (read_and_clear_word32 env) ^^ (*TODO: use 64 bits *) StableMem.set_mem_size env ^^ StableMem.get_mem_size env ^^ @@ -7224,7 +6924,7 @@ module OldStabilization = struct (* set len *) get_N ^^ - StableMem.read_and_clear_word32 env ^^ + read_and_clear_word32 env ^^ set_len ^^ (* set offset *) @@ -7264,7 +6964,7 @@ module OldStabilization = struct (* clear blob contents *) get_blob ^^ - Blob.clear env ^^ + blob_clear env ^^ (* copy zeros from blob to stable memory *) get_offset ^^ From 1cb23ac8012ca6c904b35ee689ce64a7036356b4 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 16 Aug 2023 13:48:07 +0200 Subject: [PATCH 031/260] Make all necessary runtime state persistent --- rts/motoko-rts-tests/src/gc.rs | 10 +- rts/motoko-rts-tests/src/gc/heap.rs | 11 +- rts/motoko-rts/src/continuation_table.rs | 21 +- rts/motoko-rts/src/gc/incremental.rs | 61 ++++-- .../src/gc/incremental/partitioned_heap.rs | 4 - rts/motoko-rts/src/gc/mark_compact/bitmap.rs | 201 ------------------ .../src/gc/mark_compact/mark_stack.rs | 75 ------- rts/motoko-rts/src/memory/ic.rs | 9 - .../src/memory/ic/partitioned_memory.rs | 5 + rts/motoko-rts/src/persistence.rs | 3 +- 10 files changed, 77 insertions(+), 323 deletions(-) delete mode 100644 rts/motoko-rts/src/gc/mark_compact/bitmap.rs delete mode 100644 rts/motoko-rts/src/gc/mark_compact/mark_stack.rs diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs index 1f32eccc2e6..295ffe78c7a 100644 --- a/rts/motoko-rts-tests/src/gc.rs +++ b/rts/motoko-rts-tests/src/gc.rs @@ -11,9 +11,7 @@ mod random; mod utils; use heap::MotokoHeap; -use utils::{ - get_scalar_value, make_pointer, read_word, unskew_pointer, ObjectIdx, WORD_SIZE, -}; +use utils::{get_scalar_value, make_pointer, read_word, unskew_pointer, ObjectIdx, WORD_SIZE}; use motoko_rts::types::*; @@ -262,7 +260,7 @@ fn check_dynamic_heap( offset += WORD_SIZE + length as usize; } else { assert!(tag == TAG_ARRAY || tag >= TAG_ARRAY_SLICE_MIN); - + if is_forwarded { let forward_offset = forward as usize - heap.as_ptr() as usize; let length = read_word( @@ -404,7 +402,7 @@ fn check_static_root_array(mut offset: usize, roots: &[ObjectIdx], heap: &[u8]) assert_eq!(read_word(heap, offset), make_pointer(array_address as u32)); offset += WORD_SIZE; - + assert_eq!(read_word(heap, offset), roots.len() as u32); offset += WORD_SIZE; @@ -438,7 +436,7 @@ fn check_continuation_table(mut offset: usize, continuation_table: &[ObjectIdx], assert_eq!(read_word(heap, offset), make_pointer(table_addr as u32)); offset += WORD_SIZE; - + assert_eq!(read_word(heap, offset), continuation_table.len() as u32); offset += WORD_SIZE; diff --git a/rts/motoko-rts-tests/src/gc/heap.rs b/rts/motoko-rts-tests/src/gc/heap.rs index 195101f8fb1..00298e76ff0 100644 --- a/rts/motoko-rts-tests/src/gc/heap.rs +++ b/rts/motoko-rts-tests/src/gc/heap.rs @@ -291,8 +291,7 @@ impl Memory for DummyMemory { } /// Compute the size of the heap to be allocated for the GC test. -fn heap_size_for_gc( -) -> usize { +fn heap_size_for_gc() -> usize { 3 * PARTITION_SIZE } @@ -324,7 +323,7 @@ fn create_dynamic_heap( write_word(dynamic_heap, heap_offset, make_pointer(address)); // forwarding pointer heap_offset += WORD_SIZE; - + // Store length: idx + refs write_word( dynamic_heap, @@ -377,7 +376,7 @@ fn create_dynamic_heap( write_word(dynamic_heap, heap_offset, make_pointer(mutbox_address)); heap_offset += WORD_SIZE; - + let root_ptr = *object_addrs.get(root_id).unwrap(); write_word(dynamic_heap, heap_offset, make_pointer(root_ptr as u32)); heap_offset += WORD_SIZE; @@ -394,7 +393,7 @@ fn create_dynamic_heap( make_pointer(static_root_array_address), ); heap_offset += WORD_SIZE; - + assert_eq!(static_roots.len(), root_mutboxes.len()); write_word(dynamic_heap, heap_offset, root_mutboxes.len() as u32); heap_offset += WORD_SIZE; @@ -416,7 +415,7 @@ fn create_dynamic_heap( make_pointer(continuation_table_address), ); heap_offset += WORD_SIZE; - + write_word(dynamic_heap, heap_offset, continuation_table.len() as u32); heap_offset += WORD_SIZE; diff --git a/rts/motoko-rts/src/continuation_table.rs b/rts/motoko-rts/src/continuation_table.rs index b0be5f4dbda..98ec500d085 100644 --- a/rts/motoko-rts/src/continuation_table.rs +++ b/rts/motoko-rts/src/continuation_table.rs @@ -1,5 +1,10 @@ //! This file implements the data structure the Motoko runtime uses to keep track of outstanding -//! continuations. It needs to support the following operations +//! continuations. +//! +//! The structure is re-initialized on canister upgrades. This is why it is not part of the +//! persistent metadata, cf. `persistence::PersistentMetadata`. +//! +//! It needs to support the following operations //! //! 1. Adding a continuation (any heap pointer) and getting an index (i32) //! 2. Looking up a continuation by index, which also frees it @@ -22,7 +27,7 @@ //! the free list. Since all indices are relative to the payload begin, they stay valid. We never //! shrink the table. -use crate::barriers::allocation_barrier; +use crate::barriers::{allocation_barrier, write_with_barrier}; use crate::memory::{alloc_array, Memory}; use crate::rts_trap_with; use crate::types::Value; @@ -31,6 +36,9 @@ use motoko_rts_macros::ic_mem_fn; const INITIAL_SIZE: u32 = 256; +// The static variables are re-initialized on canister upgrades and therefore not part of the +// persistent metadata. + // Skewed pointer to the `Array` object. This needs to be a skewed pointer to be able to pass its // location to the GC. static mut TABLE: Value = Value::from_scalar(0); @@ -61,8 +69,8 @@ unsafe fn double_continuation_table(mem: &mut M) { let new_size = old_size * 2; - TABLE = alloc_array(mem, new_size); - let new_array = TABLE.as_array(); + let new_table = alloc_array(mem, new_size); + let new_array = new_table.as_array(); for i in 0..old_size { let old_value = old_array.get(i); @@ -72,7 +80,10 @@ unsafe fn double_continuation_table(mem: &mut M) { for i in old_size..new_size { new_array.set_scalar(i, Value::from_scalar(i + 1)); } - allocation_barrier(TABLE); + allocation_barrier(new_table); + + let location = &mut TABLE as *mut Value; + write_with_barrier(mem, location, new_table); } pub unsafe fn table_initialized() -> bool { diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 422a45f7b13..dbe257dd8c6 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -6,13 +6,17 @@ //! - Focus on reclaiming high-garbage partitions. //! - Compacting heap space with partition evacuations. //! - Incremental copying enabled by forwarding pointers. +//! +//! The entire GC state including the scheduling statistics must be +//! retained across upgrades and therefore be stored part of the +//! persistent metadata, cf. `persistence::PersistentMetadata`. use motoko_rts_macros::ic_mem_fn; use crate::{memory::Memory, types::*, visitor::visit_pointer_fields}; use self::{ - partitioned_heap::{PartitionedHeap, PartitionedHeapIterator}, + partitioned_heap::{PartitionedHeap, PartitionedHeapIterator, UNINITIALIZED_HEAP}, phases::{ evacuation_increment::EvacuationIncrement, mark_increment::{MarkIncrement, MarkState}, @@ -64,9 +68,6 @@ unsafe fn incremental_gc(mem: &mut M) { } } -#[cfg(feature = "ic")] -static mut LAST_ALLOCATIONS: Bytes = Bytes(0u64); - #[cfg(feature = "ic")] unsafe fn should_start() -> bool { use self::partitioned_heap::PARTITION_SIZE; @@ -84,8 +85,9 @@ unsafe fn should_start() -> bool { }; let current_allocations = partitioned_memory::get_total_allocations(); - debug_assert!(current_allocations >= LAST_ALLOCATIONS); - let absolute_growth = current_allocations - LAST_ALLOCATIONS; + let state = get_incremental_gc_state(); + debug_assert!(current_allocations >= state.statistics.last_allocations); + let absolute_growth = current_allocations - state.statistics.last_allocations; let relative_growth = absolute_growth.0 as f64 / heap_size.as_usize() as f64; relative_growth > growth_threshold && heap_size.as_usize() >= PARTITION_SIZE } @@ -93,19 +95,31 @@ unsafe fn should_start() -> bool { #[cfg(feature = "ic")] unsafe fn record_gc_start() { use crate::memory::ic::partitioned_memory; - LAST_ALLOCATIONS = partitioned_memory::get_total_allocations(); + + let state = get_incremental_gc_state(); + state.statistics.last_allocations = partitioned_memory::get_total_allocations(); } #[cfg(feature = "ic")] unsafe fn record_gc_stop() { - use crate::memory::ic::{self, partitioned_memory}; + use crate::memory::ic::partitioned_memory; use crate::persistence::HEAP_START; let heap_size = partitioned_memory::get_heap_size(); let static_size = Bytes(HEAP_START as u32); debug_assert!(heap_size >= static_size); let dynamic_size = heap_size - static_size; - ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, dynamic_size); + let state = get_incremental_gc_state(); + state.statistics.max_live = ::core::cmp::max(state.statistics.max_live, dynamic_size); +} + +// Persistent GC statistics used for scheduling and diagnostics. +#[cfg(feature = "ic")] +struct Statistics { + // Total number of allocation at the start of the last GC run. + last_allocations: Bytes, + // Maximum heap size the end of a GC run. + max_live: Bytes, } /// GC phases per run. Each of the following phases is performed in potentially multiple increments. @@ -147,8 +161,24 @@ pub struct State { allocation_count: usize, // Number of allocations during an active GC run. mark_state: Option, iterator_state: Option, + #[cfg(feature = "ic")] + statistics: Statistics, } +/// Optimization: Avoiding `Option` or `Lazy`. +pub(crate) const UNINITIALIZED_STATE: State = State { + phase: Phase::Pause, + partitioned_heap: UNINITIALIZED_HEAP, + allocation_count: 0, + mark_state: None, + iterator_state: None, + #[cfg(feature = "ic")] + statistics: Statistics { + last_allocations: Bytes(0), + max_live: Bytes(0), + }, +}; + /// Incremental GC. /// Each GC call has its new GC instance that shares the common GC state `STATE`. pub struct IncrementalGC<'a, M: Memory> { @@ -388,15 +418,14 @@ pub unsafe fn get_incremental_gc_state() -> &'static mut State { crate::persistence::get_incremental_gc_state() } +#[cfg(feature = "ic")] +pub unsafe fn get_max_live_size() -> Bytes { + get_incremental_gc_state().statistics.max_live +} + // For RTS unit testing only. #[cfg(not(feature = "ic"))] -static mut TEST_GC_STATE: State = State { - phase: Phase::Pause, - partitioned_heap: partitioned_heap::UNINITIALIZED_HEAP, - allocation_count: 0, - mark_state: None, - iterator_state: None, -}; +static mut TEST_GC_STATE: State = UNINITIALIZED_STATE; // For RTS unit testing only. #[cfg(not(feature = "ic"))] diff --git a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs index 2a48e1f5e6f..a7b8c5653f8 100644 --- a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs +++ b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs @@ -88,9 +88,7 @@ pub struct Partition { update: bool, // Specifies whether the pointers in the partition have to be updated. } -/// For RTS unit testing only. /// Optimization: Avoiding `Option` or `Lazy`. -#[cfg(not(feature = "ic"))] const UNINITIALIZED_PARTITION: Partition = Partition { index: usize::MAX, free: false, @@ -364,9 +362,7 @@ pub struct PartitionedHeap { precomputed_heap_size: usize, // Occupied heap size, excluding the dynamic heap in the allocation partition. } -/// For RTS unit testing only. /// Optimization: Avoiding `Option` or `LazyCell`. -#[cfg(not(feature = "ic"))] pub const UNINITIALIZED_HEAP: PartitionedHeap = PartitionedHeap { partitions: [UNINITIALIZED_PARTITION; MAX_PARTITIONS], heap_base: 0, diff --git a/rts/motoko-rts/src/gc/mark_compact/bitmap.rs b/rts/motoko-rts/src/gc/mark_compact/bitmap.rs deleted file mode 100644 index dc86885489a..00000000000 --- a/rts/motoko-rts/src/gc/mark_compact/bitmap.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::mem_utils::memzero; -use crate::memory::{alloc_blob, Memory}; -use crate::types::{size_of, Blob, Bytes, Obj}; - -/* How the Wasm-heap maps to the bitmap - - +---- RTS stack ----+---- Motoko statics ----+---- Dynamic heap ------+ Heap limit - (prefix words) bitmap lives here -> | BM | - / \ - / \ - each bit represents a word -> /...bits...\ - in the dynamic heap - -## Marking with absolute addresses - -When marking, we need to map an address to a bit in the bitmap. Internally the -bitmap is kept in a (non-moving) blob after the dynamic heap (DH). To -efficiently mark the right bit in the bitmap, we maintain a pointer that points -_before the start of the bitmap_ such that `address / 8` and `address % 8` -will address the right bit: - - - +---- BITMAP_FORBIDDEN_PTR +---- BITMAP_PTR - v v - , (forbidden) ,...................... bitmap ..........~~...., - | heap_prefix_words / 8 bytes | heap_size / 32 bytes | - | get_bitmap_forbidden_size() | BITMAP_SIZE | - ^ ^ ^ - / ^^^^^^^^^^^ \ | - / corresponds \ ^^^^^^^^^^^ / - / \ corresponds / - / \ / - +---- Rts stack ----+---- Motoko statics ----+--------- Dynamic heap ----------~~--+ - ! - ! 32-byte aligned - - -Debug assertions guard the forbidden bytes from access, as this area potentially -overlaps with the Motoko dynamic heap. - -## The alignment caveat - -For this scheme to work, it is essential that the start of the DH is an address that -is divisible by 32 (`heap_prefix_words % 8 == 0`). Otherwise the `address / 8` -and `address % 8` operations on the DH's starting address will not yield the -least significant bit in the BM. - -## Example calculation - -Assume the DH is at 0x80000. Assume heap limit being at 0xB0000. Then the BM thus -could be placed at 0xB0008. Since the heap_prefix_words is 0x20000, -BITMAP_FORBIDDEN_PTR = 0xB0008 - 0x20000 / 8 = 0xAC008. - -Now let's mark the address 0x80548 in the DH. Its absolute word number is 0x20152. -The `(0x20152 / 8, 0x20152 % 8)`-rule gives a bit position 2 with byte offset 0x402A, -thus we mark bit 2 in byte 0xAC004 + 0x402A = 0xB002E, which is in the BM. - - */ - -/// Current bitmap -static mut BITMAP_FORBIDDEN_PTR: *mut u8 = core::ptr::null_mut(); -static mut BITMAP_PTR: *mut u8 = core::ptr::null_mut(); -static mut BITMAP_SIZE: u32 = 0; - -unsafe fn get_bitmap_forbidden_size() -> usize { - BITMAP_PTR as usize - BITMAP_FORBIDDEN_PTR as usize -} - -pub unsafe fn alloc_bitmap(mem: &mut M, heap_size: Bytes, heap_prefix_words: u32) { - // See Note "How the Wasm-heap maps to the bitmap" above - debug_assert_eq!(heap_prefix_words % 8, 0); - // We will have at most this many objects in the heap, each requiring a bit - let n_bits = heap_size.to_words().as_u32(); - // Each byte will hold 8 bits. - BITMAP_SIZE = (n_bits + 7) / 8; - // Also round allocation up to 8-bytes to make iteration efficient. We want to be able to read - // 64 bits in a single read and check as many bits as possible with a single `word != 0`. - let bitmap_bytes = Bytes(((BITMAP_SIZE + 7) / 8) * 8); - // Allocating an actual object here as otherwise dump_heap gets confused - // No post allocation barrier as this RTS-internal blob will be collected by the GC. - let blob = alloc_blob(mem, bitmap_bytes).get_ptr() as *mut Blob; - memzero(blob.payload_addr() as usize, bitmap_bytes.to_words()); - - BITMAP_PTR = blob.payload_addr(); - BITMAP_FORBIDDEN_PTR = BITMAP_PTR.sub(heap_prefix_words as usize / 8) -} - -pub unsafe fn free_bitmap() { - BITMAP_PTR = core::ptr::null_mut(); - BITMAP_FORBIDDEN_PTR = core::ptr::null_mut(); - BITMAP_SIZE = 0; -} - -pub unsafe fn get_bit(idx: u32) -> bool { - let (byte_idx, bit_idx) = (idx / 8, idx % 8); - debug_assert!(byte_idx as usize >= get_bitmap_forbidden_size()); - debug_assert!(get_bitmap_forbidden_size() + BITMAP_SIZE as usize > byte_idx as usize); - let byte = *BITMAP_FORBIDDEN_PTR.add(byte_idx as usize); - (byte >> bit_idx) & 0b1 != 0 -} - -pub unsafe fn set_bit(idx: u32) { - let (byte_idx, bit_idx) = (idx / 8, idx % 8); - debug_assert!(byte_idx as usize >= get_bitmap_forbidden_size()); - debug_assert!(get_bitmap_forbidden_size() + BITMAP_SIZE as usize > byte_idx as usize); - let byte = *BITMAP_FORBIDDEN_PTR.add(byte_idx as usize); - let new_byte = byte | (0b1 << bit_idx); - *BITMAP_FORBIDDEN_PTR.add(byte_idx as usize) = new_byte; -} - -pub struct BitmapIter { - /// Size of the bitmap, in bits. Does not change after initialization. - size: u32, - /// Current bit index - current_bit_idx: u32, - /// Current 64-bit word in the bitmap that we're iterating. We read in 64-bit chunks to be able - /// to check as many bits as possible with a single `word != 0`. - current_word: u64, - /// How many leading bits are initially zeroed in the current_word - leading_zeros: u32, -} - -pub unsafe fn iter_bits() -> BitmapIter { - let blob_len_bytes = (BITMAP_PTR.sub(size_of::().to_bytes().as_usize()) as *mut Obj) - .as_blob() - .len() - .as_u32(); - - debug_assert_eq!(blob_len_bytes % 8, 0); - - let blob_len_64bit_words = blob_len_bytes / 8; - - let current_word = if blob_len_64bit_words == 0 { - 0 - } else { - let bitmap_ptr64 = BITMAP_PTR as *const u64; - bitmap_ptr64.read_unaligned() - }; - - debug_assert!(BITMAP_PTR as usize >= BITMAP_FORBIDDEN_PTR as usize); - let forbidden_bits = get_bitmap_forbidden_size() as u32 * 8; - - BitmapIter { - size: blob_len_bytes * 8 + forbidden_bits, - current_bit_idx: forbidden_bits, - current_word, - leading_zeros: current_word.leading_zeros(), - } -} - -/// This value marks the end-of-stream in `BitmapIter`. Using this value instead of `None` for -/// end-of-stream reduces Wasm instructions executed by ~2.7% in some cases. -// -// Heap is 4GiB and each 32-bit word gets a bit, so this is one larger than the bit for the last -// word in heap. -// -// (We actually need less bits than that as when the heap is full we can't allocate bitmap and mark -// stack and can't do GC) -pub const BITMAP_ITER_END: u32 = u32::MAX; - -impl BitmapIter { - /// Returns the next bit, or `BITMAP_ITER_END` if there are no more bits set. - pub fn next(&mut self) -> u32 { - debug_assert!(self.current_bit_idx <= self.size); - - if self.current_bit_idx == self.size { - return BITMAP_ITER_END; - } - - // Outer loop iterates 64-bit words - loop { - // Examine the least significant bit(s) in the current word - if self.current_word != 0 { - let shift_amt = self.current_word.trailing_zeros(); - self.current_word >>= shift_amt; - self.current_word >>= 1; - let bit_idx = self.current_bit_idx + shift_amt; - self.current_bit_idx = bit_idx + 1; - return bit_idx; - } - - // Move on to next word (always 64-bit boundary) - self.current_bit_idx += self.leading_zeros; - unsafe { - debug_assert_eq!( - (self.current_bit_idx - get_bitmap_forbidden_size() as u32 * 8) % 64, - 0 - ) - } - if self.current_bit_idx == self.size { - return BITMAP_ITER_END; - } - self.current_word = unsafe { - let ptr64 = - BITMAP_FORBIDDEN_PTR.add(self.current_bit_idx as usize / 8) as *const u64; - ptr64.read_unaligned() - }; - self.leading_zeros = self.current_word.leading_zeros(); - } - } -} diff --git a/rts/motoko-rts/src/gc/mark_compact/mark_stack.rs b/rts/motoko-rts/src/gc/mark_compact/mark_stack.rs deleted file mode 100644 index 990cf32ff25..00000000000 --- a/rts/motoko-rts/src/gc/mark_compact/mark_stack.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! A stack for marking heap objects (for GC). There should be no allocation after the stack -//! otherwise things will break as we push. This invariant is checked in debug builds. - -use crate::memory::{alloc_blob, Memory}; -use crate::types::{Blob, Tag, Words}; - -use core::ptr::null_mut; - -/// Initial stack size -pub const INIT_STACK_SIZE: Words = Words(64); - -/// Pointer to the `blob` object for the mark stack. Used to get the capacity of the stack. -static mut STACK_BLOB_PTR: *mut Blob = null_mut(); - -/// Bottom of the mark stack -pub static mut STACK_BASE: *mut usize = null_mut(); - -/// Top of the mark stack -pub static mut STACK_TOP: *mut usize = null_mut(); - -/// Next free slot in the mark stack -pub static mut STACK_PTR: *mut usize = null_mut(); - -pub unsafe fn alloc_mark_stack(mem: &mut M) { - debug_assert!(STACK_BLOB_PTR.is_null()); - - // Allocating an actual object here to not break dump_heap - // No post allocation barrier as this RTS-internal blob will be collected by the GC. - STACK_BLOB_PTR = alloc_blob(mem, INIT_STACK_SIZE.to_bytes()).get_ptr() as *mut Blob; - STACK_BASE = STACK_BLOB_PTR.payload_addr() as *mut usize; - STACK_PTR = STACK_BASE; - STACK_TOP = STACK_BASE.add(INIT_STACK_SIZE.as_usize()); -} - -pub unsafe fn free_mark_stack() { - STACK_BLOB_PTR = null_mut(); - STACK_BASE = null_mut(); - STACK_PTR = null_mut(); - STACK_TOP = null_mut(); -} - -/// Doubles the stack size -pub unsafe fn grow_stack(mem: &mut M) { - let stack_cap: Words = STACK_BLOB_PTR.len().to_words(); - let p = mem.alloc_words(stack_cap).get_ptr() as *mut usize; - - // Make sure nothing was allocated after the stack - debug_assert_eq!(STACK_TOP, p); - - let new_cap: Words = stack_cap * 2; - (*STACK_BLOB_PTR).len = new_cap.to_bytes(); - STACK_TOP = STACK_BASE.add(new_cap.as_usize()); -} - -pub unsafe fn push_mark_stack(mem: &mut M, obj: usize, obj_tag: Tag) { - // We add 2 words in a push, and `STACK_PTR` and `STACK_TOP` are both multiples of 2, so we can - // do simple equality check here - if STACK_PTR == STACK_TOP { - grow_stack(mem); - } - - *STACK_PTR = obj; - *STACK_PTR.add(1) = obj_tag as usize; - STACK_PTR = STACK_PTR.add(2); -} - -pub unsafe fn pop_mark_stack() -> Option<(usize, Tag)> { - if STACK_PTR == STACK_BASE { - return None; - } - STACK_PTR = STACK_PTR.sub(2); - let p = *STACK_PTR; - let tag = *STACK_PTR.add(1); - return Some((p, tag as u32)); -} diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index 135a0f4ad3f..7e466595e0d 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -5,17 +5,8 @@ pub mod partitioned_memory; use super::Memory; use crate::constants::WASM_PAGE_SIZE; use crate::rts_trap_with; -use crate::types::Bytes; use core::arch::wasm32; -/// Maximum live data retained in a GC. -pub(crate) static mut MAX_LIVE: Bytes = Bytes(0); - -#[no_mangle] -unsafe extern "C" fn get_max_live_size() -> Bytes { - MAX_LIVE -} - /// Provides a `Memory` implementation, to be used in functions compiled for IC or WASI. The /// `Memory` implementation allocates in Wasm heap with Wasm `memory.grow` instruction. pub struct IcMemory; diff --git a/rts/motoko-rts/src/memory/ic/partitioned_memory.rs b/rts/motoko-rts/src/memory/ic/partitioned_memory.rs index acf16a48132..8ec4ec5fe55 100644 --- a/rts/motoko-rts/src/memory/ic/partitioned_memory.rs +++ b/rts/motoko-rts/src/memory/ic/partitioned_memory.rs @@ -16,6 +16,11 @@ pub unsafe extern "C" fn get_heap_size() -> Bytes { crate::gc::incremental::get_partitioned_heap().occupied_size() } +#[no_mangle] +pub unsafe extern "C" fn get_max_live_size() -> Bytes { + crate::gc::incremental::get_max_live_size() +} + impl Memory for IcMemory { #[inline] unsafe fn alloc_words(&mut self, n: Words) -> Value { diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index 06af1dc9126..0bbd4a14118 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -6,7 +6,7 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ barriers::{allocation_barrier, write_with_barrier}, - gc::incremental::State, + gc::incremental::{State, UNINITIALIZED_STATE}, memory::Memory, types::{size_of, Null, Value, TAG_NULL}, }; @@ -81,6 +81,7 @@ impl PersistentMetadata { (*self).version = VERSION; (*self).stable_actor = DEFAULT_VALUE; (*self).null_singleton = alloc_null(mem); + (*self).incremental_gc_state = UNINITIALIZED_STATE; } } From f9e87886f1a7fd146f6288d83f98782df48af104 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 16 Aug 2023 13:51:22 +0200 Subject: [PATCH 032/260] Code refactoring --- rts/motoko-rts/src/gc/incremental.rs | 14 ++--- rts/motoko-rts/src/memory/ic.rs | 61 +++++++++++++------ .../src/memory/ic/partitioned_memory.rs | 34 ----------- 3 files changed, 51 insertions(+), 58 deletions(-) delete mode 100644 rts/motoko-rts/src/memory/ic/partitioned_memory.rs diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index dbe257dd8c6..0d496d4a7f1 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -71,20 +71,20 @@ unsafe fn incremental_gc(mem: &mut M) { #[cfg(feature = "ic")] unsafe fn should_start() -> bool { use self::partitioned_heap::PARTITION_SIZE; - use crate::memory::ic::partitioned_memory; + use crate::memory::ic; const CRITICAL_HEAP_LIMIT: Bytes = Bytes(u32::MAX - 768 * 1024 * 1024); const CRITICAL_GROWTH_THRESHOLD: f64 = 0.01; const NORMAL_GROWTH_THRESHOLD: f64 = 0.65; - let heap_size = partitioned_memory::get_heap_size(); + let heap_size = ic::get_heap_size(); let growth_threshold = if heap_size > CRITICAL_HEAP_LIMIT { CRITICAL_GROWTH_THRESHOLD } else { NORMAL_GROWTH_THRESHOLD }; - let current_allocations = partitioned_memory::get_total_allocations(); + let current_allocations = ic::get_total_allocations(); let state = get_incremental_gc_state(); debug_assert!(current_allocations >= state.statistics.last_allocations); let absolute_growth = current_allocations - state.statistics.last_allocations; @@ -94,18 +94,18 @@ unsafe fn should_start() -> bool { #[cfg(feature = "ic")] unsafe fn record_gc_start() { - use crate::memory::ic::partitioned_memory; + use crate::memory::ic; let state = get_incremental_gc_state(); - state.statistics.last_allocations = partitioned_memory::get_total_allocations(); + state.statistics.last_allocations = ic::get_total_allocations(); } #[cfg(feature = "ic")] unsafe fn record_gc_stop() { - use crate::memory::ic::partitioned_memory; + use crate::memory::ic; use crate::persistence::HEAP_START; - let heap_size = partitioned_memory::get_heap_size(); + let heap_size = ic::get_heap_size(); let static_size = Bytes(HEAP_START as u32); debug_assert!(heap_size >= static_size); let dynamic_size = heap_size - static_size; diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index 7e466595e0d..97b05269b5c 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -1,31 +1,58 @@ // This module is only enabled when compiling the RTS for IC or WASI. -pub mod partitioned_memory; - use super::Memory; use crate::constants::WASM_PAGE_SIZE; use crate::rts_trap_with; +use crate::types::{Bytes, Value, Words}; use core::arch::wasm32; /// Provides a `Memory` implementation, to be used in functions compiled for IC or WASI. The /// `Memory` implementation allocates in Wasm heap with Wasm `memory.grow` instruction. pub struct IcMemory; -/// Page allocation. Ensures that the memory up to, but excluding, the given pointer is allocated, -/// with the slight exception of not allocating the extra page for address 0xFFFF_0000. -unsafe fn grow_memory(ptr: u64) { - debug_assert_eq!(0xFFFF_0000, usize::MAX - WASM_PAGE_SIZE.as_usize() + 1); - if ptr > 0xFFFF_0000 { - // spare the last wasm memory page - rts_trap_with("Cannot grow memory") - }; - let page_size = u64::from(WASM_PAGE_SIZE.as_u32()); - let total_pages_needed = ((ptr + page_size - 1) / page_size) as usize; - let current_pages = wasm32::memory_size(0); - if total_pages_needed > current_pages { - if wasm32::memory_grow(0, total_pages_needed - current_pages) == core::usize::MAX { - // replica signals that there is not enough memory - rts_trap_with("Cannot grow memory"); +impl Memory for IcMemory { + #[inline] + unsafe fn alloc_words(&mut self, n: Words) -> Value { + crate::gc::incremental::get_partitioned_heap().allocate(self, n) + } + + /// Page allocation. Ensures that the memory up to, but excluding, the given pointer is allocated, + /// with the slight exception of not allocating the extra page for address 0xFFFF_0000. + #[inline(never)] + unsafe fn grow_memory(&mut self, ptr: u64) { + debug_assert_eq!(0xFFFF_0000, usize::MAX - WASM_PAGE_SIZE.as_usize() + 1); + if ptr > 0xFFFF_0000 { + // spare the last wasm memory page + rts_trap_with("Cannot grow memory") + }; + let page_size = u64::from(WASM_PAGE_SIZE.as_u32()); + let total_pages_needed = ((ptr + page_size - 1) / page_size) as usize; + let current_pages = wasm32::memory_size(0); + if total_pages_needed > current_pages { + if wasm32::memory_grow(0, total_pages_needed - current_pages) == core::usize::MAX { + // replica signals that there is not enough memory + rts_trap_with("Cannot grow memory"); + } } } } + +#[no_mangle] +unsafe extern "C" fn get_reclaimed() -> Bytes { + crate::gc::incremental::get_partitioned_heap().reclaimed_size() +} + +#[no_mangle] +pub unsafe extern "C" fn get_total_allocations() -> Bytes { + Bytes(u64::from(get_heap_size().as_u32())) + get_reclaimed() +} + +#[no_mangle] +pub unsafe extern "C" fn get_heap_size() -> Bytes { + crate::gc::incremental::get_partitioned_heap().occupied_size() +} + +#[no_mangle] +pub unsafe extern "C" fn get_max_live_size() -> Bytes { + crate::gc::incremental::get_max_live_size() +} diff --git a/rts/motoko-rts/src/memory/ic/partitioned_memory.rs b/rts/motoko-rts/src/memory/ic/partitioned_memory.rs deleted file mode 100644 index 8ec4ec5fe55..00000000000 --- a/rts/motoko-rts/src/memory/ic/partitioned_memory.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::{IcMemory, Memory}; -use crate::types::*; - -#[no_mangle] -unsafe extern "C" fn get_reclaimed() -> Bytes { - crate::gc::incremental::get_partitioned_heap().reclaimed_size() -} - -#[no_mangle] -pub unsafe extern "C" fn get_total_allocations() -> Bytes { - Bytes(u64::from(get_heap_size().as_u32())) + get_reclaimed() -} - -#[no_mangle] -pub unsafe extern "C" fn get_heap_size() -> Bytes { - crate::gc::incremental::get_partitioned_heap().occupied_size() -} - -#[no_mangle] -pub unsafe extern "C" fn get_max_live_size() -> Bytes { - crate::gc::incremental::get_max_live_size() -} - -impl Memory for IcMemory { - #[inline] - unsafe fn alloc_words(&mut self, n: Words) -> Value { - crate::gc::incremental::get_partitioned_heap().allocate(self, n) - } - - #[inline(never)] - unsafe fn grow_memory(&mut self, ptr: u64) { - super::grow_memory(ptr); - } -} From e1d5fc1ec5938e85bce47e4caf93c953022ddeda Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Wed, 16 Aug 2023 15:20:40 +0200 Subject: [PATCH 033/260] Update .gitignore --- rts/.gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/rts/.gitignore b/rts/.gitignore index 5099a3e57b9..31939c72d62 100644 --- a/rts/.gitignore +++ b/rts/.gitignore @@ -1,5 +1,3 @@ mo-rts.wasm mo-rts-debug.wasm -mo-rts-incremental.wasm -mo-rts-incremental-debug.wasm _build From 79d0f79158b44c29c94f6bb382d4cf2003eb914d Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Thu, 17 Aug 2023 10:15:30 +0200 Subject: [PATCH 034/260] Use specific IC branch that retains the heap on upgrade --- nix/sources.json | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nix/sources.json b/nix/sources.json index d17eccb4c6a..7f246f6e526 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -21,16 +21,15 @@ "version": "3.2.25" }, "ic": { - "branch": "master", - "builtin": false, + "branch": "luc/stable-heap-on-release", "description": "Internet Computer blockchain source: the client/replica software run by nodes", "homepage": "", - "owner": "dfinity", + "owner": "luc-blaeser", "repo": "ic", - "rev": "629f7fc8e82ff52e6373a528dcd29f9066563ad4", - "sha256": "1jadgk4jfgvkw3i5nlxikg0wi6144d46sca165g9xgqpsr8hkm00", + "rev": "af7331a313bc76a12d0d8bb9e6f8ddc72c2a6b5d", + "sha256": "1w66vfsk9cvvcdlhhsiyf4zqms9l0mkmfpkdmwlpdjcs1ikjf1bb", "type": "tarball", - "url": "https://github.com/dfinity/ic/archive/629f7fc8e82ff52e6373a528dcd29f9066563ad4.tar.gz", + "url": "https://github.com/luc-blaeser/ic/archive/af7331a313bc76a12d0d8bb9e6f8ddc72c2a6b5d.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "ic-hs": { From d67e4bdca2f54c189a2c1017263f9e4bce57c49b Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Thu, 17 Aug 2023 10:32:58 +0200 Subject: [PATCH 035/260] Add missing constant materialization --- src/codegen/compile.ml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index a3e7c42afb1..25ed24a3031 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7076,6 +7076,7 @@ module StackRep = struct | Const _ | Unreachable -> G.nop let rec materialize_constant env = function + | Const.Lit (Const.Vanilla l) -> compile_unboxed_const l | Const.Lit (Const.Bool b) -> Bool.lit b | Const.Lit (Const.Blob t) -> Blob.lit env t | Const.Lit (Const.Null) -> Opt.null_lit env @@ -7088,6 +7089,7 @@ module StackRep = struct | Const.Message fi -> assert false | Const.Unit -> Tuple.compile_unit | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env c) + | Const.Array cs -> Arr.lit env (List.map (fun c -> materialize_constant env c) cs) | _ -> assert false let adjust env (sr_in : t) sr_out = From 755e3cdc65b4a329c5b65326a5e63eccd666b314 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Thu, 17 Aug 2023 15:19:28 +0200 Subject: [PATCH 036/260] Create the null object after memory initialization --- rts/motoko-rts-tests/src/bigint.rs | 8 +-- .../src/continuation_table.rs | 13 ++-- rts/motoko-rts-tests/src/crc32.rs | 8 ++- rts/motoko-rts-tests/src/gc.rs | 14 ++--- rts/motoko-rts-tests/src/memory.rs | 15 ++++- rts/motoko-rts-tests/src/principal_id.rs | 8 ++- rts/motoko-rts-tests/src/stream.rs | 8 ++- rts/motoko-rts-tests/src/text.rs | 6 +- rts/motoko-rts/src/gc/incremental.rs | 59 ++++++++++--------- .../src/gc/incremental/partitioned_heap.rs | 27 --------- rts/motoko-rts/src/persistence.rs | 25 ++++++-- 11 files changed, 96 insertions(+), 95 deletions(-) diff --git a/rts/motoko-rts-tests/src/bigint.rs b/rts/motoko-rts-tests/src/bigint.rs index c37dac2b824..2bf93a7dde8 100644 --- a/rts/motoko-rts-tests/src/bigint.rs +++ b/rts/motoko-rts-tests/src/bigint.rs @@ -1,8 +1,8 @@ -use crate::memory::TestMemory; +use crate::memory::{initialize_test_memory, reset_test_memory, TestMemory}; use motoko_rts::bigint::{self, *}; use motoko_rts::buf::Buf; -use motoko_rts::types::{Bytes, Value, Words}; +use motoko_rts::types::{Bytes, Value}; // mp functions below are implemented separately for tests as we can't modify mp_int source code to // pass a generic heap argument (then monomorphise it for IC). @@ -27,8 +27,7 @@ unsafe extern "C" fn mp_realloc( pub unsafe fn test() { println!("Testing BigInt ..."); - // Not sure how much we will need in these tests but 1G should be enough - let mut heap = TestMemory::new(Words(1024 * 1024)); + let mut heap = initialize_test_memory(); HEAP = &mut heap; assert!(bigint_eq( @@ -63,6 +62,7 @@ pub unsafe fn test() { } HEAP = std::ptr::null_mut(); + reset_test_memory(); drop(heap); } diff --git a/rts/motoko-rts-tests/src/continuation_table.rs b/rts/motoko-rts-tests/src/continuation_table.rs index 877eb4714c7..2decbfd0aae 100644 --- a/rts/motoko-rts-tests/src/continuation_table.rs +++ b/rts/motoko-rts-tests/src/continuation_table.rs @@ -1,12 +1,12 @@ use std::array::from_fn; -use crate::memory::TestMemory; +use crate::memory::{initialize_test_memory, reset_test_memory}; use motoko_rts::continuation_table::{ continuation_count, recall_continuation, remember_continuation, }; use motoko_rts::memory::alloc_blob; -use motoko_rts::types::{size_of, Array, Blob, Bytes, Value, Words}; +use motoko_rts::types::{Bytes, Value}; pub unsafe fn test() { println!("Testing continuation table ..."); @@ -15,12 +15,7 @@ pub unsafe fn test() { const N: usize = 2000; // >256, to exercise `double_continuation_table` - // Array will be doubled 3 times, so 256 + 512 + 1024 + 2048 = 3840 words, plus each array will - // have an array header. Also add the set of N pointers to empty blobs. - let mut heap = TestMemory::new(Words( - 3840 + 4 * size_of::().to_bytes().as_u32() - + N as u32 * size_of::().to_bytes().as_u32(), - )); + let mut heap = initialize_test_memory(); let pointers: [Value; N] = from_fn(|_| alloc_blob(&mut heap, Bytes(0))); @@ -48,4 +43,6 @@ pub unsafe fn test() { ); assert_eq!(continuation_count(), i as u32); } + + reset_test_memory(); } diff --git a/rts/motoko-rts-tests/src/crc32.rs b/rts/motoko-rts-tests/src/crc32.rs index 167c46d6fc1..dc40f8352e8 100644 --- a/rts/motoko-rts-tests/src/crc32.rs +++ b/rts/motoko-rts-tests/src/crc32.rs @@ -1,13 +1,13 @@ -use crate::memory::TestMemory; +use crate::memory::{initialize_test_memory, reset_test_memory}; use motoko_rts::principal_id::{base32_of_checksummed_blob, base32_to_blob}; use motoko_rts::text::{text_compare, text_of_ptr_size}; -use motoko_rts::types::{Bytes, Words}; +use motoko_rts::types::Bytes; pub unsafe fn test() { println!("Testing crc32 ..."); - let mut heap = TestMemory::new(Words(1024 * 1024)); + let mut heap = initialize_test_memory(); // // Encoding @@ -78,4 +78,6 @@ pub unsafe fn test() { ), 0 ); + + reset_test_memory(); } diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs index 295ffe78c7a..7f903700fbe 100644 --- a/rts/motoko-rts-tests/src/gc.rs +++ b/rts/motoko-rts-tests/src/gc.rs @@ -147,10 +147,11 @@ fn test_gc( fn initialize_gc(heap: &mut MotokoHeap) { use motoko_rts::gc::incremental::{ - get_incremental_gc_state, get_partitioned_heap, IncrementalGC, + get_partitioned_heap, set_incremental_gc_state, IncrementalGC, }; unsafe { - IncrementalGC::initialize(heap, get_incremental_gc_state(), heap.heap_base_address()); + let state = IncrementalGC::initial_gc_state(heap, heap.heap_base_address()); + set_incremental_gc_state(Some(state)); let allocation_size = heap.heap_ptr_address() - heap.heap_base_address(); // Synchronize the partitioned heap with one big combined allocation by starting from the base pointer as the heap pointer. @@ -163,14 +164,9 @@ fn initialize_gc(heap: &mut MotokoHeap) { } fn reset_gc() { - use crate::memory::TestMemory; - use motoko_rts::gc::incremental::{ - get_incremental_gc_state, partitioned_heap::PARTITION_SIZE, IncrementalGC, - }; - - let mut memory = TestMemory::new(Words(PARTITION_SIZE as u32)); + use motoko_rts::gc::incremental::set_incremental_gc_state; unsafe { - IncrementalGC::initialize(&mut memory, get_incremental_gc_state(), 0); + set_incremental_gc_state(None); } } diff --git a/rts/motoko-rts-tests/src/memory.rs b/rts/motoko-rts-tests/src/memory.rs index 9f725a015b8..7e1280a9ae0 100644 --- a/rts/motoko-rts-tests/src/memory.rs +++ b/rts/motoko-rts-tests/src/memory.rs @@ -1,5 +1,7 @@ +use motoko_rts::gc::incremental::partitioned_heap::PARTITION_SIZE; +use motoko_rts::gc::incremental::{set_incremental_gc_state, IncrementalGC}; use motoko_rts::memory::Memory; -use motoko_rts::types::{Value, Words}; +use motoko_rts::types::{Bytes, Value, Words}; pub struct TestMemory { heap: Box<[u8]>, @@ -59,3 +61,14 @@ impl Memory for TestMemory { } } } + +pub unsafe fn initialize_test_memory() -> TestMemory { + let mut memory = TestMemory::new(Bytes(PARTITION_SIZE as u32).to_words()); + let state = IncrementalGC::initial_gc_state(&mut memory, 0); + set_incremental_gc_state(Some(state)); + memory +} + +pub unsafe fn reset_test_memory() { + set_incremental_gc_state(None); +} diff --git a/rts/motoko-rts-tests/src/principal_id.rs b/rts/motoko-rts-tests/src/principal_id.rs index be37cb33d5b..8c9a8d3eb99 100644 --- a/rts/motoko-rts-tests/src/principal_id.rs +++ b/rts/motoko-rts-tests/src/principal_id.rs @@ -1,13 +1,13 @@ -use crate::memory::TestMemory; +use crate::memory::{initialize_test_memory, reset_test_memory}; use motoko_rts::principal_id::{blob_of_principal, principal_of_blob}; use motoko_rts::text::{text_compare, text_of_ptr_size, text_of_str}; -use motoko_rts::types::{Bytes, Words}; +use motoko_rts::types::Bytes; pub unsafe fn test() { println!("Testing principal id encoding ..."); - let mut heap = TestMemory::new(Words(1024 * 1024)); + let mut heap = initialize_test_memory(); // // Encoding @@ -46,4 +46,6 @@ pub unsafe fn test() { ), 0 ); + + reset_test_memory(); } diff --git a/rts/motoko-rts-tests/src/stream.rs b/rts/motoko-rts-tests/src/stream.rs index f830ee10e81..fcff8036779 100644 --- a/rts/motoko-rts-tests/src/stream.rs +++ b/rts/motoko-rts-tests/src/stream.rs @@ -1,14 +1,14 @@ //! Stream tests -use crate::memory::TestMemory; +use crate::memory::{initialize_test_memory, reset_test_memory}; use motoko_rts::stream::alloc_stream; -use motoko_rts::types::{size_of, Blob, Bytes, Stream, Value, Words}; +use motoko_rts::types::{size_of, Blob, Bytes, Stream, Value}; pub unsafe fn test() { println!("Testing streaming ..."); - let mut mem = TestMemory::new(Words(1024 * 1024)); + let mut mem = initialize_test_memory(); const STREAM_SMALL_SIZE: u32 = 60; @@ -87,4 +87,6 @@ pub unsafe fn test() { assert_eq!(WRITTEN, Bytes(STREAM_LARGE_SIZE + STREAM_RESERVE_SIZE1)); // all at once stream.shutdown(); assert_eq!(WRITTEN, Bytes(STREAM_LARGE_SIZE + STREAM_RESERVE_SIZE1 + 1)); // u8 too + + reset_test_memory(); } diff --git a/rts/motoko-rts-tests/src/text.rs b/rts/motoko-rts-tests/src/text.rs index 3fbbc3d8e0c..5a08d9f384a 100644 --- a/rts/motoko-rts-tests/src/text.rs +++ b/rts/motoko-rts-tests/src/text.rs @@ -1,6 +1,6 @@ //! Text and text iterator tests -use crate::memory::TestMemory; +use crate::memory::{initialize_test_memory, reset_test_memory, TestMemory}; use motoko_rts::memory::Memory; use motoko_rts::text::{ @@ -48,7 +48,7 @@ impl<'a, M: Memory> Iterator for TextIter<'a, M> { pub unsafe fn test() { println!("Testing text and text iterators ..."); - let mut mem = TestMemory::new(Words(1024 * 1024)); + let mut mem = initialize_test_memory(); println!(" Testing decode_code_point and text_singleton for ASCII"); for i in 0..=255u32 { @@ -93,6 +93,8 @@ pub unsafe fn test() { }, ) .unwrap(); + + reset_test_memory(); } unsafe fn concat1(mem: &mut M) { diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 0d496d4a7f1..15f1651aa2d 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -16,7 +16,7 @@ use motoko_rts_macros::ic_mem_fn; use crate::{memory::Memory, types::*, visitor::visit_pointer_fields}; use self::{ - partitioned_heap::{PartitionedHeap, PartitionedHeapIterator, UNINITIALIZED_HEAP}, + partitioned_heap::{PartitionedHeap, PartitionedHeapIterator}, phases::{ evacuation_increment::EvacuationIncrement, mark_increment::{MarkIncrement, MarkState}, @@ -40,10 +40,9 @@ pub mod time; #[ic_mem_fn(ic_only)] unsafe fn initialize_incremental_gc(mem: &mut M) { - use crate::persistence::{initialize_memory, HEAP_START}; + use crate::persistence::initialize_memory; initialize_memory(mem); - IncrementalGC::::initialize(mem, get_incremental_gc_state(), HEAP_START); } #[ic_mem_fn(ic_only)] @@ -114,7 +113,6 @@ unsafe fn record_gc_stop() { } // Persistent GC statistics used for scheduling and diagnostics. -#[cfg(feature = "ic")] struct Statistics { // Total number of allocation at the start of the last GC run. last_allocations: Bytes, @@ -161,24 +159,9 @@ pub struct State { allocation_count: usize, // Number of allocations during an active GC run. mark_state: Option, iterator_state: Option, - #[cfg(feature = "ic")] statistics: Statistics, } -/// Optimization: Avoiding `Option` or `Lazy`. -pub(crate) const UNINITIALIZED_STATE: State = State { - phase: Phase::Pause, - partitioned_heap: UNINITIALIZED_HEAP, - allocation_count: 0, - mark_state: None, - iterator_state: None, - #[cfg(feature = "ic")] - statistics: Statistics { - last_allocations: Bytes(0), - max_live: Bytes(0), - }, -}; - /// Incremental GC. /// Each GC call has its new GC instance that shares the common GC state `STATE`. pub struct IncrementalGC<'a, M: Memory> { @@ -190,12 +173,20 @@ pub struct IncrementalGC<'a, M: Memory> { impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { /// (Re-)Initialize the entire incremental garbage collector. /// Called on a runtime system start with incremental GC and also during RTS testing. - pub unsafe fn initialize(mem: &'a mut M, state: &mut State, heap_base: usize) { - state.phase = Phase::Pause; - state.partitioned_heap = PartitionedHeap::new(mem, heap_base); - state.allocation_count = 0; - state.mark_state = None; - state.iterator_state = None; + pub unsafe fn initial_gc_state(mem: &'a mut M, heap_base: usize) -> State { + let partitioned_heap = PartitionedHeap::new(mem, heap_base); + let statistics = Statistics { + last_allocations: Bytes(0), + max_live: Bytes(0), + }; + State { + phase: Phase::Pause, + partitioned_heap, + allocation_count: 0, + mark_state: None, + iterator_state: None, + statistics, + } } /// Each GC schedule point can get a new GC instance that shares the common GC state. @@ -423,12 +414,22 @@ pub unsafe fn get_max_live_size() -> Bytes { get_incremental_gc_state().statistics.max_live } -// For RTS unit testing only. +/// For RTS unit testing only. #[cfg(not(feature = "ic"))] -static mut TEST_GC_STATE: State = UNINITIALIZED_STATE; +static mut TEST_GC_STATE: Option = None; -// For RTS unit testing only. +/// For RTS unit testing only. #[cfg(not(feature = "ic"))] pub unsafe fn get_incremental_gc_state() -> &'static mut State { - &mut TEST_GC_STATE + let state = TEST_GC_STATE.as_mut().unwrap(); + // Read the statistics to get rid of unused warnings. + assert!(state.statistics.last_allocations == Bytes(0)); + assert!(state.statistics.max_live == Bytes(0)); + state +} + +/// For RTS unit testing only. +#[cfg(not(feature = "ic"))] +pub unsafe fn set_incremental_gc_state(state: Option) { + TEST_GC_STATE = state; } diff --git a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs index a7b8c5653f8..cffba859a10 100644 --- a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs +++ b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs @@ -88,20 +88,6 @@ pub struct Partition { update: bool, // Specifies whether the pointers in the partition have to be updated. } -/// Optimization: Avoiding `Option` or `Lazy`. -const UNINITIALIZED_PARTITION: Partition = Partition { - index: usize::MAX, - free: false, - large_content: false, - marked_size: 0, - static_size: 0, - dynamic_size: 0, - bitmap: super::mark_bitmap::DEFAULT_MARK_BITMAP, - temporary: false, - evacuate: false, - update: false, -}; - impl Partition { pub fn get_index(&self) -> usize { self.index @@ -362,19 +348,6 @@ pub struct PartitionedHeap { precomputed_heap_size: usize, // Occupied heap size, excluding the dynamic heap in the allocation partition. } -/// Optimization: Avoiding `Option` or `LazyCell`. -pub const UNINITIALIZED_HEAP: PartitionedHeap = PartitionedHeap { - partitions: [UNINITIALIZED_PARTITION; MAX_PARTITIONS], - heap_base: 0, - allocation_index: 0, - free_partitions: 0, - evacuating: false, - reclaimed: 0, - bitmap_allocation_pointer: 0, - gc_running: false, - precomputed_heap_size: 0, -}; - impl PartitionedHeap { pub unsafe fn new(mem: &mut M, heap_base: usize) -> PartitionedHeap { let allocation_index = heap_base / PARTITION_SIZE; diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs index 0bbd4a14118..f3ff0ffef9e 100644 --- a/rts/motoko-rts/src/persistence.rs +++ b/rts/motoko-rts/src/persistence.rs @@ -6,7 +6,7 @@ use motoko_rts_macros::ic_mem_fn; use crate::{ barriers::{allocation_barrier, write_with_barrier}, - gc::incremental::{State, UNINITIALIZED_STATE}, + gc::incremental::{IncrementalGC, State}, memory::Memory, types::{size_of, Null, Value, TAG_NULL}, }; @@ -31,12 +31,12 @@ struct PersistentMetadata { // Reference to the stable sub-record of the actor, comprising all stable actor fields. Set before upgrade. // Constitutes a GC root and requires pointer forwarding. stable_actor: Value, - // Singleton of the top-level null value. To be retained across upgrades. - // Constitutes a GC root and requires pointer forwarding. - null_singleton: Value, // The state of the incremental GC including the partitioned heap description. // The GC continues work after upgrades. incremental_gc_state: State, + // Singleton of the top-level null value. To be retained across upgrades. + // Constitutes a GC root and requires pointer forwarding. + null_singleton: Value, } const METATDATA_ADDRESS: usize = 4 * 1024 * 1024; @@ -80,8 +80,8 @@ impl PersistentMetadata { (*self).fingerprint = FINGERPRINT; (*self).version = VERSION; (*self).stable_actor = DEFAULT_VALUE; - (*self).null_singleton = alloc_null(mem); - (*self).incremental_gc_state = UNINITIALIZED_STATE; + (*self).incremental_gc_state = IncrementalGC::initial_gc_state(mem, HEAP_START); + (*self).null_singleton = DEFAULT_VALUE; } } @@ -95,9 +95,18 @@ pub unsafe fn initialize_memory(mem: &mut M) { metadata.check_version(); } else { metadata.initialize(mem); + allocate_initial_objects(mem); } } +/// Allocate initial objects only after the partitioned heap has been initialized. +#[cfg(feature = "ic")] +unsafe fn allocate_initial_objects(mem: &mut M) { + let metadata = PersistentMetadata::get(); + debug_assert!((*metadata).null_singleton == DEFAULT_VALUE); + (*metadata).null_singleton = alloc_null(mem); +} + /// Returns the stable sub-record of the actor of the upgraded canister version. /// Returns scalar 0 if no actor is stored after on a fresh memory. #[no_mangle] @@ -123,6 +132,7 @@ pub(crate) unsafe fn stable_actor_location() -> *mut Value { unsafe fn alloc_null(mem: &mut M) -> Value { let value = mem.alloc_words(size_of::()); + debug_assert!(value.get_ptr() >= HEAP_START); let null = value.get_ptr() as *mut Null; (*null).header.tag = TAG_NULL; (*null).header.init_forward(value); @@ -136,12 +146,15 @@ unsafe fn alloc_null(mem: &mut M) -> Value { /// NOTE: The forwarding pointer of the other comparison argument needs /// to be resolved too. #[no_mangle] +#[cfg(feature = "ic")] pub unsafe extern "C" fn null_singleton() -> Value { let metadata = PersistentMetadata::get(); + debug_assert!((*metadata).null_singleton != DEFAULT_VALUE); (*metadata).null_singleton.forward_if_possible() } // GC root pointer required for GC marking and updating. +#[cfg(feature = "ic")] pub(crate) unsafe fn null_singleton_location() -> *mut Value { let metadata = PersistentMetadata::get(); &mut (*metadata).null_singleton as *mut Value From fefa3de559c0cb437107b984602930c7c601353f Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Thu, 17 Aug 2023 15:52:58 +0200 Subject: [PATCH 037/260] Null comparison with conditional pointer forwarding resolution --- src/codegen/compile.ml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 25ed24a3031..b90576051c5 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -1768,10 +1768,20 @@ module Opt = struct E.call_import env "rts" "null_singleton" (* forwarding pointer already resolved *) let is_some env = - (* resolve forwarding pointer on pointer equality check *) - Tagged.load_forwarding_pointer env ^^ - null_lit env ^^ - G.i (Compare (Wasm.Values.I32 I32Op.Ne)) + Func.share_code1 env "is_some" ("x", I32Type) [I32Type] (fun env get_x -> + get_x ^^ BitTagged.if_tagged_scalar env [I32Type] + ( Bool.lit true ) (* scalar *) + ( get_x ^^ BitTagged.is_true_literal env ^^ (* exclude true literal since `branch_default` follows the forwarding pointer *) + E.if_ env [I32Type] + ( Bool.lit true ) (* true literal *) + ( (* pointer to object, resolve forwarding pointer on null pointer equality check *) + get_x ^^ + Tagged.load_forwarding_pointer env ^^ + null_lit env ^^ + G.i (Compare (Wasm.Values.I32 I32Op.Ne)) + ) + ) + ) let alloc_some env get_payload = Tagged.obj env Tagged.Some [ get_payload ] From e7e7a4a1f43f555502c45d4b26b2ea25b906010c Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 15:14:40 +0200 Subject: [PATCH 038/260] Extend constant materialization --- src/codegen/compile.ml | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index b90576051c5..ae1e7370d7b 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -7086,21 +7086,25 @@ module StackRep = struct | Const _ | Unreachable -> G.nop let rec materialize_constant env = function - | Const.Lit (Const.Vanilla l) -> compile_unboxed_const l - | Const.Lit (Const.Bool b) -> Bool.lit b - | Const.Lit (Const.Blob t) -> Blob.lit env t + | Const.Lit (Const.Vanilla value) -> compile_unboxed_const value + | Const.Lit (Const.Bool number) -> Bool.lit number + | Const.Lit (Const.Blob payload) -> Blob.lit env payload | Const.Lit (Const.Null) -> Opt.null_lit env - | Const.Lit (Const.BigInt n) -> BigNum.lit env n - | Const.Lit (Const.Word32 n) -> BoxedSmallWord.lit env n - | Const.Lit (Const.Word64 n) -> BoxedWord64.lit env n - | Const.Lit (Const.Float64 f) -> Float.lit env f - | Const.Opt c -> Opt.inject env (materialize_constant env c) + | Const.Lit (Const.BigInt number) -> BigNum.lit env number + | Const.Lit (Const.Word32 number) -> BoxedSmallWord.lit env number + | Const.Lit (Const.Word64 number) -> BoxedWord64.lit env number + | Const.Lit (Const.Float64 number) -> Float.lit env number + | Const.Opt value -> Opt.inject env (materialize_constant env value) | Const.Fun (get_fi, _) -> Closure.alloc env (get_fi ()) - | Const.Message fi -> assert false + | Const.Message _ -> assert false | Const.Unit -> Tuple.compile_unit - | Const.Tag (i, c) -> Variant.inject env i (materialize_constant env c) - | Const.Array cs -> Arr.lit env (List.map (fun c -> materialize_constant env c) cs) - | _ -> assert false + | Const.Tag (tag, value) -> Variant.inject env tag (materialize_constant env value) + | Const.Array elements -> + let materialized_elements = List.map (materialize_constant env) elements in + Arr.lit env materialized_elements + | Const.Obj fields -> + let materialized_fields = List.map (fun (name, value) -> (name, (fun () -> materialize_constant env value))) fields in + Object.lit_raw env materialized_fields let adjust env (sr_in : t) sr_out = if eq sr_in sr_out From 26115012a15d574cd34dc133cad83a9d2eeeca45 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 15:38:47 +0200 Subject: [PATCH 039/260] Adjust 64-bit boxed literals --- src/codegen/compile.ml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index ae1e7370d7b..bba5d530f88 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -1943,15 +1943,6 @@ module BoxedWord64 = struct let payload_field = Tagged.header_size - let lit env i = - if BitTagged.can_tag_const i - then - compile_unboxed_const (BitTagged.tag_const i) - else - Tagged.obj env Tagged.Bits64 [ - compile_const_64 i - ] - let compile_box env compile_elem : G.t = let (set_i, get_i) = new_local env "boxed_i64" in let size = 4l in @@ -1961,6 +1952,13 @@ module BoxedWord64 = struct get_i ^^ Tagged.allocation_barrier env + let lit env i = + if BitTagged.can_tag_const i + then + compile_unboxed_const (BitTagged.tag_const i) + else + compile_box env (compile_const_64 i) + let box env = Func.share_code1 env "box_i64" ("n", I64Type) [I32Type] (fun env get_n -> get_n ^^ BitTagged.if_can_tag_i64 env [I32Type] (get_n ^^ BitTagged.tag) @@ -2310,11 +2308,6 @@ module Float = struct let compile_unboxed_const f = G.i (Const (nr (Wasm.Values.F64 f))) - let lit env f = - Tagged.obj env Tagged.Bits64 [ - compile_const_64 (Wasm.F64.to_bits f) - ] - let box env = Func.share_code1 env "box_f64" ("f", F64Type) [I32Type] (fun env get_f -> let (set_i, get_i) = new_local env "boxed_f64" in let size = Int32.add Tagged.header_size 2l in @@ -2327,6 +2320,8 @@ module Float = struct let unbox env = Tagged.load_forwarding_pointer env ^^ Tagged.load_field_float64 env payload_field + let lit env f = (compile_const_64 (Wasm.F64.to_bits f)) ^^ box env + end (* Float *) From 070b74df688d8d0abad097621d847790a20647d0 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 15:53:17 +0200 Subject: [PATCH 040/260] Adjust float literal --- src/codegen/compile.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index bba5d530f88..733c48e0706 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2320,7 +2320,7 @@ module Float = struct let unbox env = Tagged.load_forwarding_pointer env ^^ Tagged.load_field_float64 env payload_field - let lit env f = (compile_const_64 (Wasm.F64.to_bits f)) ^^ box env + let lit env f = (compile_unboxed_const f) ^^ box env end (* Float *) From c0c600b8284564fc4cb7ee420e2734203f3dfce9 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 15:54:01 +0200 Subject: [PATCH 041/260] Code refactoring --- src/codegen/compile.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 733c48e0706..b2d2c362ae7 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -2320,7 +2320,7 @@ module Float = struct let unbox env = Tagged.load_forwarding_pointer env ^^ Tagged.load_field_float64 env payload_field - let lit env f = (compile_unboxed_const f) ^^ box env + let lit env f = compile_unboxed_const f ^^ box env end (* Float *) From 39e3604cfff0c3bf45c6329bf779ae1f9db4fb45 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 16:06:38 +0200 Subject: [PATCH 042/260] Adjust function comparison --- src/codegen/compile.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index b2d2c362ae7..5a0f59e99c8 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -155,6 +155,7 @@ module Const = struct let eq v1 v2 = match v1, v2 with | Lit l1, Lit l2 -> lit_eq l1 l2 + | Fun (f1_fp, _), Fun (f2_fp, _) -> f1_fp() = f2_fp() | _ -> v1 = v2 end (* Const *) From a20af45f218de788b74d0441b16239a01aac58c7 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 16:25:27 +0200 Subject: [PATCH 043/260] Adjust tests --- test/run/const-func-static.mo | 2 +- test/run/idl-ops.mo | 3 +-- test/run/idl.mo | 3 +-- test/run/ok/array-len.wasm-run.ok | 2 +- test/run/optimise-for-array.mo | 30 ++++++++++++++---------------- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/test/run/const-func-static.mo b/test/run/const-func-static.mo index a6a4eb0de69..3c66c93986e 100644 --- a/test/run/const-func-static.mo +++ b/test/run/const-func-static.mo @@ -5,7 +5,7 @@ func higher_order(foo: () -> ()) = foo(); func bar() = (); higher_order(bar); let after = Prim.rts_heap_size(); -assert(+after-before == 0); +assert(+after-before == 16); Prim.debugPrint("Ignore Diff: heap size increase " # debug_show (+after-before)); //SKIP run diff --git a/test/run/idl-ops.mo b/test/run/idl-ops.mo index 15a2746cdbd..dda6f1d37f0 100644 --- a/test/run/idl-ops.mo +++ b/test/run/idl-ops.mo @@ -50,8 +50,7 @@ assert((?arrayNat) == deserArrayInt (serArrayInt arrayNat)); assert(null == deserArrayNat (serArrayInt arrayInt)); assert((?arrayInt) == deserArrayInt (serArrayInt arrayInt)); let heapDifference = Prim.rts_heap_size() : Int - started_with; -// Difference between incremental and non-incremental GC -assert(heapDifference == +6_848 or heapDifference == +7_464); +assert(heapDifference == 26_360); //SKIP run //SKIP run-ir diff --git a/test/run/idl.mo b/test/run/idl.mo index 3ab349baf66..2c906de95ba 100644 --- a/test/run/idl.mo +++ b/test/run/idl.mo @@ -46,8 +46,7 @@ assert(arrayNat == deserArrayInt (serArrayInt arrayNat)); assert(arrayInt == deserArrayInt (serArrayInt arrayInt)); let heapDifference = Prim.rts_heap_size() : Int - started_with; -// Difference between incremental and non-incremental GC -assert(heapDifference == +4_488 or heapDifference == +4_888); +assert(heapDifference == 19_704); //SKIP run //SKIP run-ir diff --git a/test/run/ok/array-len.wasm-run.ok b/test/run/ok/array-len.wasm-run.ok index 49fd311aadf..79d6e3a21c5 100644 --- a/test/run/ok/array-len.wasm-run.ok +++ b/test/run/ok/array-len.wasm-run.ok @@ -1,4 +1,4 @@ -Allocation delta: 0 +Allocation delta: 68 a b c diff --git a/test/run/optimise-for-array.mo b/test/run/optimise-for-array.mo index d68faa26609..a0a6b115af9 100644 --- a/test/run/optimise-for-array.mo +++ b/test/run/optimise-for-array.mo @@ -5,13 +5,11 @@ import Prim "mo:⛔"; // CHECK: (local $check0 i32) // CHECK-NOT: call $@immut_array_size -// DON'TCHECK: i32.load offset=(5 or 9) +// DON'TCHECK: i32.load offset=9 // CHECK: i32.load offset= // CHECK: i32.const 2 // CHECK: i32.shl -// CHECK: i32.lt_u -// CHECK: i32.add -// DON'TCHECK: i32.load offset=(9 or 13) +// DON'TCHECK: i32.load offset=13 // CHECK: local.tee $check0 // CHECK-NEXT: call $print_text // CHECK: i32.const 4 @@ -20,22 +18,21 @@ for (check0 in ["hello", "world"].vals()) { Prim.debugPrint check0 }; // CHECK-NOT: call $@mut_array_size -// DON'TCHECK: i32.load offset=(5 or 9) +// DON'TCHECK: i32.load offset=9 // CHECK: i32.load offset= // CHECK: i32.const 2 -// CHECK-NEXT: i32.shl -// CHECK: i32.lt_u -// CHECK: i32.add -// DON'TCHECK: i32.load offset=(9 or 13) -// CHECK: i32.load offset= -// CHECK-NEXT: local.tee $check1 +// CHECK: i32.shl +// DON'TCHECK: i32.load offset=13 +// CHECK: local.tee $check1 // CHECK-NEXT: call $print_text +// CHECK: i32.const 4 +// CHECK-NEXT: i32.add for (check1 in [var "hello", "mutable", "world"].vals()) { Prim.debugPrint check1 }; let array = [var "hello", "remutable", "world"]; array[1] := "mutable"; // CHECK-NOT: call $@immut_array_size -// DON'TCHECK: i32.load offset=(5 or 9) +// DON'TCHECK: i32.load offset=9 // CHECK: i32.load offset= // CHECK: i32.const 2 // CHECK: i32.shl @@ -47,15 +44,16 @@ array[1] := "mutable"; for (check2 in array.vals()) { Prim.debugPrint check2 }; // CHECK-NOT: call $@immut_array_size -// DON'TCHECK: i32.load offset=(5 or 9) +// DON'TCHECK: i32.load offset=9 // CHECK: i32.load offset= // CHECK: i32.const 2 // CHECK: i32.shl // CHECK: i32.lt_u // CHECK: i32.add -// DON'TCHECK: i32.load offset=(9 or 13) -// CHECK: i32.load offset= -// CHECK-NEXT: local.tee $check3 +// DON'TCHECK: i32.load offset=13 +// CHECK: local.tee $check3 +// CHECK: i32.const 4 +// CHECK-NEXT: i32.add // interfering parentheses don't disturb us for (check3 in (((["hello", "immutable", "world"].vals())))) { Prim.debugPrint check3 }; From f62590a90f4f1edff7f810cfbfa0c7630e7e7c88 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 16:26:14 +0200 Subject: [PATCH 044/260] Refine test --- test/run/optimise-for-array.mo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/run/optimise-for-array.mo b/test/run/optimise-for-array.mo index a0a6b115af9..038e79937b4 100644 --- a/test/run/optimise-for-array.mo +++ b/test/run/optimise-for-array.mo @@ -92,7 +92,7 @@ check6[1] := "mutable"; // this passes the IR type check, which demonstrates that no name capture happens for (check6 in check6.vals()) { ignore check6 }; -// DON'TCHECK: i32.load offset=(5 or 9) +// DON'TCHECK: i32.load offset=9 // CHECK: i32.load offset= // CHECK: i32.const 2 // CHECK: i32.shl @@ -101,7 +101,7 @@ for (check7 in [].vals(Prim.debugPrint "want to see you")) { }; // CHECK: local.set $num8 // CHECK-NOT: call $@immut_array_size -// DON'TCHECK: i32.load offset=(5 or 9) +// DON'TCHECK: i32.load offset=9 // CHECK: i32.load offset= // CHECK: i32.const 1 // CHECK: i32.shl From c2f01cdff02fe11b07a0951118f18f9e56e25aea Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 16:55:48 +0200 Subject: [PATCH 045/260] Allow GC to see the null object --- rts/motoko-rts/src/visitor.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rts/motoko-rts/src/visitor.rs b/rts/motoko-rts/src/visitor.rs index 47108bc6935..8c40e9aa046 100644 --- a/rts/motoko-rts/src/visitor.rs +++ b/rts/motoko-rts/src/visitor.rs @@ -112,14 +112,10 @@ pub unsafe fn visit_pointer_fields( } } - TAG_BITS64 | TAG_BITS32 | TAG_BLOB | TAG_BIGINT => { + TAG_BITS64 | TAG_BITS32 | TAG_BLOB | TAG_BIGINT | TAG_NULL => { // These don't have pointers, skip } - TAG_NULL => { - rts_trap_with("encountered NULL object tag in visit_pointer_fields"); - } - TAG_FWD_PTR | TAG_ONE_WORD_FILLER | TAG_FREE_SPACE | _ => { rts_trap_with("invalid object tag in visit_pointer_fields"); } From 6ab0b185bd55c84e873bf331adfe5787bd856391 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 17:08:03 +0200 Subject: [PATCH 046/260] Code refactoring --- .../src/gc/incremental/roots.rs | 56 ++++++++----------- rts/motoko-rts/src/gc/incremental.rs | 1 - .../gc/incremental/phases/mark_increment.rs | 3 +- .../gc/incremental/phases/update_increment.rs | 3 +- rts/motoko-rts/src/gc/incremental/roots.rs | 6 +- rts/motoko-rts/src/visitor.rs | 40 +++++++------ 6 files changed, 50 insertions(+), 59 deletions(-) diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs index 9a2c23e9783..5454f2d55d1 100644 --- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs +++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs @@ -25,47 +25,37 @@ pub unsafe fn test() { unsafe fn check_visit_static_roots(heap: &MotokoHeap, root_ids: &[ObjectIdx]) { let roots = get_roots(heap); let mut visited_static_roots = vec![]; - visit_roots( - roots, - heap.heap_base_address(), - &mut visited_static_roots, - |context, field| { - let object = *field; - let array = object.as_array(); - if array.len() == root_ids.len() as u32 { - for index in 0..array.len() { - let mutbox_value = array.get(index); - let mutbox = mutbox_value.as_mutbox(); - let root_address = (*mutbox).field.get_ptr(); - let root_id = object_id(heap, root_address); - context.push(root_id); - } + visit_roots(roots, &mut visited_static_roots, |context, field| { + let object = *field; + let array = object.as_array(); + if array.len() == root_ids.len() as u32 { + for index in 0..array.len() { + let mutbox_value = array.get(index); + let mutbox = mutbox_value.as_mutbox(); + let root_address = (*mutbox).field.get_ptr(); + let root_id = object_id(heap, root_address); + context.push(root_id); } - }, - ); + } + }); assert_eq!(visited_static_roots, root_ids); } unsafe fn check_visit_continuation_table(heap: &MotokoHeap, continuation_ids: &[ObjectIdx]) { let roots = get_roots(heap); let mut visited_continuations = vec![]; - visit_roots( - roots, - heap.heap_base_address(), - &mut visited_continuations, - |context, field| { - let object = *field; - let array = object.as_array(); - if array.len() == continuation_ids.len() as u32 { - assert_eq!(context.len(), 0); - for index in 0..array.len() { - let element = array.get(index); - let id = object_id(&heap, element.get_ptr()); - context.push(id); - } + visit_roots(roots, &mut visited_continuations, |context, field| { + let object = *field; + let array = object.as_array(); + if array.len() == continuation_ids.len() as u32 { + assert_eq!(context.len(), 0); + for index in 0..array.len() { + let element = array.get(index); + let id = object_id(&heap, element.get_ptr()); + context.push(id); } - }, - ); + } + }); assert_eq!(visited_continuations, continuation_ids); } diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 15f1651aa2d..d159d9e5766 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -383,7 +383,6 @@ unsafe fn update_new_allocation(state: &State, new_object: Value) { &mut (), object, object.tag(), - state.partitioned_heap.base_address(), |_, field| { *field = (*field).forward_if_possible(); }, diff --git a/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs b/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs index 4580fb32ebe..d98a8af3988 100644 --- a/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs +++ b/rts/motoko-rts/src/gc/incremental/phases/mark_increment.rs @@ -66,7 +66,7 @@ impl<'a, M: Memory + 'a> MarkIncrement<'a, M> { } pub unsafe fn mark_roots(&mut self, roots: Roots) { - visit_roots(roots, self.heap.base_address(), self, |gc, field| { + visit_roots(roots, self, |gc, field| { gc.mark_object(*field); gc.time.tick(); }); @@ -115,7 +115,6 @@ impl<'a, M: Memory + 'a> MarkIncrement<'a, M> { self, object, object.tag(), - self.heap.base_address(), |gc, field_address| { let field_value = *field_address; gc.mark_object(field_value); diff --git a/rts/motoko-rts/src/gc/incremental/phases/update_increment.rs b/rts/motoko-rts/src/gc/incremental/phases/update_increment.rs index d82c8b3e152..a759e9c8337 100644 --- a/rts/motoko-rts/src/gc/incremental/phases/update_increment.rs +++ b/rts/motoko-rts/src/gc/incremental/phases/update_increment.rs @@ -49,7 +49,7 @@ impl<'a> UpdateIncrement<'a> { } pub unsafe fn update_roots(&mut self, roots: Roots) { - visit_roots(roots, self.heap.base_address(), self, |gc, field| { + visit_roots(roots, self, |gc, field| { let value = *field; if value.is_forwarded() { *field = value.forward_if_possible(); @@ -106,7 +106,6 @@ impl<'a> UpdateIncrement<'a> { self, object, object.tag(), - self.heap.base_address(), |gc, field_address| { *field_address = (*field_address).forward_if_possible(); gc.time.tick(); diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs index 4de6a99d73f..e973a702151 100644 --- a/rts/motoko-rts/src/gc/incremental/roots.rs +++ b/rts/motoko-rts/src/gc/incremental/roots.rs @@ -1,6 +1,6 @@ use motoko_rts_macros::ic_mem_fn; -use crate::{types::Value, visitor::pointer_to_dynamic_heap}; +use crate::{types::Value, visitor::is_pointer_field}; /// Root referring to all canister variables. /// This root is reinitialized on each canister upgrade. @@ -27,13 +27,11 @@ pub unsafe fn root_set() -> Roots { pub unsafe fn visit_roots( roots: Roots, - heap_base: usize, context: &mut C, visit_field: V, ) { for location in roots { - // TODO: Check whether all pointers lead to dynamic heap, no static heap - if pointer_to_dynamic_heap(location, heap_base) { + if is_pointer_field(location) { visit_field(context, location); } } diff --git a/rts/motoko-rts/src/visitor.rs b/rts/motoko-rts/src/visitor.rs index 8c40e9aa046..00d2d2fc09d 100644 --- a/rts/motoko-rts/src/visitor.rs +++ b/rts/motoko-rts/src/visitor.rs @@ -9,7 +9,6 @@ use crate::types::*; /// * `ctx`: any context passed to the `visit_*` callbacks /// * `obj`: the heap object to be visited (note: its heap tag may be invalid) /// * `tag`: the heap object's logical tag (or start of array object's suffix slice) -/// * `heap_base`: start address of the dynamic heap /// * `visit_ptr_field`: callback for individual fields /// * `visit_field_range`: callback for determining the suffix slice /// Arguments: @@ -24,7 +23,6 @@ pub unsafe fn visit_pointer_fields( ctx: &mut C, obj: *mut Obj, tag: Tag, - heap_base: usize, visit_ptr_field: F, visit_field_range: G, ) where @@ -37,7 +35,7 @@ pub unsafe fn visit_pointer_fields( let obj_payload = obj.payload_addr(); for i in 0..obj.size() { let field_addr = obj_payload.add(i as usize); - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, obj_payload.add(i as usize)); } } @@ -51,7 +49,7 @@ pub unsafe fn visit_pointer_fields( debug_assert!(stop <= array.len()); for i in slice_start..stop { let field_addr = array_payload.add(i as usize); - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, field_addr); } } @@ -60,7 +58,7 @@ pub unsafe fn visit_pointer_fields( TAG_MUTBOX => { let mutbox = obj as *mut MutBox; let field_addr = &mut (*mutbox).field; - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, field_addr); } } @@ -70,7 +68,7 @@ pub unsafe fn visit_pointer_fields( let closure_payload = closure.payload_addr(); for i in 0..closure.size() { let field_addr = closure_payload.add(i as usize); - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, field_addr); } } @@ -79,7 +77,7 @@ pub unsafe fn visit_pointer_fields( TAG_SOME => { let some = obj as *mut Some; let field_addr = &mut (*some).field; - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, field_addr); } } @@ -87,7 +85,7 @@ pub unsafe fn visit_pointer_fields( TAG_VARIANT => { let variant = obj as *mut Variant; let field_addr = &mut (*variant).field; - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, field_addr); } } @@ -95,11 +93,11 @@ pub unsafe fn visit_pointer_fields( TAG_CONCAT => { let concat = obj as *mut Concat; let field1_addr = &mut (*concat).text1; - if pointer_to_dynamic_heap(field1_addr, heap_base) { + if is_pointer_field(field1_addr) { visit_ptr_field(ctx, field1_addr); } let field2_addr = &mut (*concat).text2; - if pointer_to_dynamic_heap(field2_addr, heap_base) { + if is_pointer_field(field2_addr) { visit_ptr_field(ctx, field2_addr); } } @@ -107,7 +105,7 @@ pub unsafe fn visit_pointer_fields( TAG_OBJ_IND => { let obj_ind = obj as *mut ObjInd; let field_addr = &mut (*obj_ind).field; - if pointer_to_dynamic_heap(field_addr, heap_base) { + if is_pointer_field(field_addr) { visit_ptr_field(ctx, field_addr); } } @@ -122,10 +120,18 @@ pub unsafe fn visit_pointer_fields( } } -pub unsafe fn pointer_to_dynamic_heap(field_addr: *mut Value, heap_base: usize) -> bool { - // NB. pattern matching on `field_addr.get()` generates inefficient code - let field_value = (*field_addr).get_raw(); - // NOTE: Pointers to static space is no longer allowed. - debug_assert!(!is_ptr(field_value) || unskew(field_value as usize) >= heap_base); - is_ptr(field_value) +// Temporary function can be later removed. +pub unsafe fn is_pointer_field(field_addr: *mut Value) -> bool { + let field_value = *field_addr; + check_field_value(field_value); + field_value.is_ptr() } + +// Temporary check, can be later removed. +#[cfg(feature = "ic")] +fn check_field_value(value: Value) { + debug_assert!(value.is_scalar() || value.get_ptr() >= crate::persistence::HEAP_START); +} + +#[cfg(not(feature = "ic"))] +fn check_field_value(_value: Value) {} From 88478e8b4f3b35f5d82ae9b4ed9268a029faec51 Mon Sep 17 00:00:00 2001 From: luc-blaeser Date: Fri, 18 Aug 2023 18:39:15 +0200 Subject: [PATCH 047/260] Fix RTS stack size --- doc/md/compiler-ref.md | 1 - src/codegen/compile.ml | 7 +++-- src/exes/moc.ml | 4 --- src/mo_config/flags.ml | 2 -- test/run-drun/max-stack-variant.mo | 2 +- test/run-drun/max-stack.mo | 2 +- test/run-drun/rts-stack-pages.mo | 50 ------------------------------ 7 files changed, 7 insertions(+), 61 deletions(-) delete mode 100644 test/run-drun/rts-stack-pages.mo diff --git a/doc/md/compiler-ref.md b/doc/md/compiler-ref.md index 29bd7b7d7ef..b3fb40a7e3f 100644 --- a/doc/md/compiler-ref.md +++ b/doc/md/compiler-ref.md @@ -47,7 +47,6 @@ You can use the following options with the `moc` command. | `--release` | Ignores debug expressions in the source. | | `--stable-types` | Compile binary and emit signature of stable types to `.most` file. | | `--stable-compatible
 `        | Test upgrade compatibility between stable-type signatures `
` and ``.                                                                       |
-| `--rts-stack-pages `                   | Set maximum number of pages available for runtime system stack (default 32).
 | `--trap-on-call-error`                    | Trap, don't throw an `Error`, when an IC call fails due to destination queue full or freezing threshold is crossed.
 |                                           | Emulates behaviour of moc versions < 0.8.0.
 | `-t`                                      | Activates tracing in interpreter.
diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 5a0f59e99c8..8b84209a9d2 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -1125,7 +1125,10 @@ module Stack = struct
      grows downwards.)
   *)
 
-  let end_ () = Int32.mul (Int32.of_int (!Flags.rts_stack_pages)) page_size
+  (* Predefined constant stack size of 2MB, according to the persistent memory layout. *)
+  let stack_size = 2 * 1024 * 1024
+
+  let end_ () = Int32.of_int stack_size 
 
   let register_globals env =
     (* stack pointer *)
@@ -1177,7 +1180,7 @@ module Stack = struct
   let alloc_words env n =
     let n_bytes = Int32.mul n Heap.word_size in
     (* avoid absurd allocations *)
-    assert Int32.(to_int n_bytes < !Flags.rts_stack_pages * to_int page_size);
+    assert (Int32.(to_int n_bytes) < stack_size);
     (* alloc words *)
     get_stack_ptr env ^^
     compile_unboxed_const n_bytes ^^
diff --git a/src/exes/moc.ml b/src/exes/moc.ml
index 6db95348069..b0ed5cefcb5 100644
--- a/src/exes/moc.ml
+++ b/src/exes/moc.ml
@@ -153,10 +153,6 @@ let argspec = [
   Arg.Unit (fun () -> Flags.experimental_field_aliasing := true),
   " enable experimental support for aliasing of var fields";
 
-  "--rts-stack-pages",
-  Arg.Set_int Flags.rts_stack_pages,
-  "  set maximum number of pages available for runtime system stack (default " ^ (Int.to_string Flags.rts_stack_pages_default) ^ ")";
-
   "--trap-on-call-error",
   Arg.Unit (fun () -> Flags.trap_on_call_error := true),
   " Trap, don't throw an `Error`, when an IC call fails due to destination queue full or freezing threshold is crossed. Emulates behaviour of moc versions < 0.8.0."
diff --git a/src/mo_config/flags.ml b/src/mo_config/flags.ml
index bcdfd6d635c..8577b3e8a7f 100644
--- a/src/mo_config/flags.ml
+++ b/src/mo_config/flags.ml
@@ -41,6 +41,4 @@ let force_gc = ref false
 let global_timer = ref true
 let experimental_field_aliasing = ref false
 let ocaml_js = ref false
-let rts_stack_pages_default = 32 (* 2MB *)
-let rts_stack_pages : int ref = ref rts_stack_pages_default
 let trap_on_call_error = ref false
diff --git a/test/run-drun/max-stack-variant.mo b/test/run-drun/max-stack-variant.mo
index f31fe3da5f8..7c51aeae6de 100644
--- a/test/run-drun/max-stack-variant.mo
+++ b/test/run-drun/max-stack-variant.mo
@@ -1,4 +1,4 @@
-//MOC-FLAG --compacting-gc --rts-stack-pages 32 -measure-rts-stack
+//MOC-FLAG -measure-rts-stack
 import { errorMessage; performanceCounter; rts_heap_size; rts_max_stack_size; debugPrint; } = "mo:⛔";
 
 actor stack {
diff --git a/test/run-drun/max-stack.mo b/test/run-drun/max-stack.mo
index dd15bbc8c23..573e49ed15c 100644
--- a/test/run-drun/max-stack.mo
+++ b/test/run-drun/max-stack.mo
@@ -1,4 +1,4 @@
-//MOC-FLAG --compacting-gc --rts-stack-pages 32 -measure-rts-stack
+//MOC-FLAG -measure-rts-stack
 import { errorMessage; performanceCounter; rts_heap_size; rts_max_stack_size; debugPrint; } = "mo:⛔";
 
 actor stack {
diff --git a/test/run-drun/rts-stack-pages.mo b/test/run-drun/rts-stack-pages.mo
deleted file mode 100644
index 9391f9996a6..00000000000
--- a/test/run-drun/rts-stack-pages.mo
+++ /dev/null
@@ -1,50 +0,0 @@
-//MOC-FLAG --rts-stack-pages 2
-import Prim = "mo:prim";
-// tests --rts-stack-pages adjusts stack limit
-// test fails with RTS stack underflow with just 2 pages,
-// but succeeds with 32 pages (the default).
-actor a {
-
-   let x = Prim.stableMemoryGrow(1);
-   assert Prim.stableMemorySize() == 1;
-
-
-   type List = ?(T, List);
-
-   var map : List<(Blob, Blob)> = null;
-
-   var count : Nat32 = 0;
-
-   func fillMB(mb : Nat) : () {
-     var c = 1024;
-     while (c > 0) {
-       count += 1;
-       Prim.stableMemoryStoreNat32(0, count);
-       var k = Prim.stableMemoryLoadBlob(0, 32);
-       var v = Prim.stableMemoryLoadBlob(0, 65536);
-       map := ?((k,v), map);
-       c -= 1;
-     };
-     if (Prim.rts_heap_size() < mb * 1024 * 1024) {
-      // Difference between incremental and non-incremental GC (due to different object header lengths).
-      let toleranceMB = 2;
-      Prim.debugPrint(debug_show({heap_MB = Prim.rts_heap_size()/1024/1024/toleranceMB*toleranceMB}));
-      fillMB(mb);
-     }
-   };
-
-   fillMB(768);
-   Prim.debugPrint "serializing";
-   let blob = to_candid(map);
-   Prim.debugPrint "serialized";
-   let opt = from_candid(blob) : ?(List<(Blob,Blob)>);
-   assert (opt != null);
-   // if we got here, deserialization has succeeded
-   Prim.debugPrint "deserialized";
-   assert false;
-
-}
-//SKIP run
-//SKIP run-ir
-//SKIP run-low
-//SKIP ic-ref-run

From 938cbb743accd43ed6fb8900749f8f81b4830883 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 21 Aug 2023 13:44:44 +0200
Subject: [PATCH 048/260] Upgrade to more recent IC branch

---
 nix/sources.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/nix/sources.json b/nix/sources.json
index 7f246f6e526..5a5af2f0704 100644
--- a/nix/sources.json
+++ b/nix/sources.json
@@ -26,10 +26,10 @@
         "homepage": "",
         "owner": "luc-blaeser",
         "repo": "ic",
-        "rev": "af7331a313bc76a12d0d8bb9e6f8ddc72c2a6b5d",
-        "sha256": "1w66vfsk9cvvcdlhhsiyf4zqms9l0mkmfpkdmwlpdjcs1ikjf1bb",
+        "rev": "18de74c14491c06499f59404fa5434a18bb9ca50",
+        "sha256": "1zlq1d4zb594nb3hld50cp7kmzfzwmhka01xsbazxagszs61p2sk",
         "type": "tarball",
-        "url": "https://github.com/luc-blaeser/ic/archive/af7331a313bc76a12d0d8bb9e6f8ddc72c2a6b5d.tar.gz",
+        "url": "https://github.com/luc-blaeser/ic/archive/18de74c14491c06499f59404fa5434a18bb9ca50.tar.gz",
         "url_template": "https://github.com///archive/.tar.gz"
     },
     "ic-hs": {

From 49e7246f6a05f1c07cbe1c0918d4d3a67f12cefe Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 21 Aug 2023 14:48:25 +0200
Subject: [PATCH 049/260] Remove pointer to zero address in text iteration

---
 .../src/continuation_table.rs                 |  4 +--
 .../src/gc/incremental/partitioned_heap.rs    | 20 +++++++++----
 rts/motoko-rts/src/blob_iter.rs               |  9 +++---
 rts/motoko-rts/src/continuation_table.rs      | 12 ++++----
 .../src/gc/incremental/sanity_checks.rs       | 11 +++----
 rts/motoko-rts/src/text_iter.rs               | 30 ++++++++++---------
 rts/motoko-rts/src/types.rs                   | 16 +++-------
 7 files changed, 52 insertions(+), 50 deletions(-)

diff --git a/rts/motoko-rts-tests/src/continuation_table.rs b/rts/motoko-rts-tests/src/continuation_table.rs
index 2decbfd0aae..e8dbcc6b0db 100644
--- a/rts/motoko-rts-tests/src/continuation_table.rs
+++ b/rts/motoko-rts-tests/src/continuation_table.rs
@@ -26,7 +26,7 @@ pub unsafe fn test() {
     }
 
     for i in 0..N / 2 {
-        let c = recall_continuation(references[i]);
+        let c = recall_continuation(&mut heap, references[i]);
         assert_eq!(c.get_ptr(), pointers[i].get_ptr());
         assert_eq!(continuation_count(), (N - i - 1) as u32);
     }
@@ -38,7 +38,7 @@ pub unsafe fn test() {
 
     for i in (0..N).rev() {
         assert_eq!(
-            recall_continuation(references[i]).get_ptr(),
+            recall_continuation(&mut heap, references[i]).get_ptr(),
             pointers[i].get_ptr(),
         );
         assert_eq!(continuation_count(), i as u32);
diff --git a/rts/motoko-rts-tests/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts-tests/src/gc/incremental/partitioned_heap.rs
index c60f37ac4f9..c0b5bdbadd9 100644
--- a/rts/motoko-rts-tests/src/gc/incremental/partitioned_heap.rs
+++ b/rts/motoko-rts-tests/src/gc/incremental/partitioned_heap.rs
@@ -10,7 +10,9 @@ use motoko_rts::{
             Partition, PartitionedHeap, PartitionedHeapIterator, PARTITION_SIZE,
             SURVIVAL_RATE_THRESHOLD,
         },
+        set_incremental_gc_state,
         time::BoundedTime,
+        IncrementalGC,
     },
     memory::{alloc_array, alloc_blob, Memory},
     types::{Array, Blob, Bytes, Obj, Tag, Value, Words, TAG_ARRAY, TAG_BLOB},
@@ -39,6 +41,7 @@ unsafe fn test_normal_size_scenario() {
     test_survival_rate(&mut heap.inner);
     test_freeing_partitions(&mut heap, HEAP_SIZE / PARTITION_SIZE + 1);
     test_close_partition(&mut heap);
+    set_incremental_gc_state(None);
 }
 
 fn test_allocation_partitions(heap: &PartitionedHeap, number_of_partitions: usize) {
@@ -258,6 +261,9 @@ unsafe fn test_large_size_scenario() {
 unsafe fn test_allocation_sizes(sizes: &[usize], number_of_partitions: usize) {
     let total_partitions = number_of_partitions + 1; // Plus temporary partition.
     let mut heap = PartitionedTestHeap::new(total_partitions * PARTITION_SIZE);
+    let heap_base = heap.heap_base();
+    let state = IncrementalGC::initial_gc_state(&mut heap, heap_base);
+    set_incremental_gc_state(Some(state));
     assert!(heap.inner.occupied_size().as_usize() < PARTITION_SIZE + heap.heap_base());
     let mut time = BoundedTime::new(0);
     heap.inner.start_collection(&mut heap.memory, &mut time);
@@ -279,7 +285,8 @@ unsafe fn test_allocation_sizes(sizes: &[usize], number_of_partitions: usize) {
     heap.inner.complete_collection();
     heap.inner.start_collection(&mut heap.memory, &mut time);
     iterate_large_objects(&heap.inner, &[]);
-    assert!(heap.inner.occupied_size().as_usize() < PARTITION_SIZE + heap.heap_base())
+    assert!(heap.inner.occupied_size().as_usize() < PARTITION_SIZE + heap.heap_base());
+    set_incremental_gc_state(None);
 }
 
 unsafe fn unmark_all_objects(heap: &mut PartitionedTestHeap) {
@@ -329,9 +336,12 @@ unsafe fn occupied_space(partition: &Partition) -> usize {
     occupied_space
 }
 
-fn create_test_heap() -> PartitionedTestHeap {
+unsafe fn create_test_heap() -> PartitionedTestHeap {
     println!("    Create test heap...");
     let mut heap = PartitionedTestHeap::new(HEAP_SIZE);
+    let heap_base = heap.heap_base();
+    let state = IncrementalGC::initial_gc_state(&mut heap, heap_base);
+    set_incremental_gc_state(Some(state));
     let mut time = BoundedTime::new(0);
     unsafe {
         heap.inner.start_collection(&mut heap.memory, &mut time);
@@ -399,7 +409,7 @@ impl PartitionedTestHeap {
             let array = alloc_array(self, elements.len() as u32);
             for index in 0..elements.len() {
                 let raw_array = array.as_array();
-                raw_array.set_scalar(index as u32, elements[index]);
+                raw_array.set(index as u32, elements[index], self);
             }
             array
         }
@@ -428,7 +438,7 @@ impl Memory for PartitionedTestHeap {
         result
     }
 
-    unsafe fn grow_memory(&mut self, _ptr: u64) {
-        unreachable!();
+    unsafe fn grow_memory(&mut self, ptr: u64) {
+        assert!(ptr as usize <= self.memory.heap_end());
     }
 }
diff --git a/rts/motoko-rts/src/blob_iter.rs b/rts/motoko-rts/src/blob_iter.rs
index b63419f3d78..bbcf4296b89 100644
--- a/rts/motoko-rts/src/blob_iter.rs
+++ b/rts/motoko-rts/src/blob_iter.rs
@@ -1,5 +1,6 @@
 use crate::{
     barriers::allocation_barrier,
+    memory::Memory,
     types::{size_of, Array, Bytes, Value, Words, TAG_ARRAY},
 };
 
@@ -21,7 +22,7 @@ unsafe fn blob_iter(mem: &mut M, blob: Value) -> Value
     (*iter_array).len = 2;
 
     iter_array.initialize(ITER_BLOB_IDX, blob, mem);
-    iter_array.set_scalar(ITER_POS_IDX, Value::from_scalar(0));
+    iter_array.set(ITER_POS_IDX, Value::from_scalar(0), mem);
 
     allocation_barrier(iter_ptr)
 }
@@ -38,14 +39,14 @@ unsafe extern "C" fn blob_iter_done(iter: Value) -> u32 {
 }
 
 /// Reads next byte, advances the iterator
-#[no_mangle]
-unsafe fn blob_iter_next(iter: Value) -> u32 {
+#[ic_mem_fn]
+unsafe fn blob_iter_next(mem: &mut M, iter: Value) -> u32 {
     let iter_array = iter.as_array();
 
     let blob = iter_array.get(ITER_BLOB_IDX);
     let pos = iter_array.get(ITER_POS_IDX).get_scalar();
 
-    iter_array.set_scalar(ITER_POS_IDX, Value::from_scalar(pos + 1));
+    iter_array.set(ITER_POS_IDX, Value::from_scalar(pos + 1), mem);
 
     blob.as_blob().get(pos).into()
 }
diff --git a/rts/motoko-rts/src/continuation_table.rs b/rts/motoko-rts/src/continuation_table.rs
index 98ec500d085..905942644df 100644
--- a/rts/motoko-rts/src/continuation_table.rs
+++ b/rts/motoko-rts/src/continuation_table.rs
@@ -56,7 +56,7 @@ unsafe fn create_continuation_table(mem: &mut M) {
 
     let table = TABLE.as_array();
     for i in 0..INITIAL_SIZE {
-        table.set_scalar(i, Value::from_scalar(i + 1));
+        table.set(i, Value::from_scalar(i + 1), mem);
     }
     allocation_barrier(TABLE);
 }
@@ -78,7 +78,7 @@ unsafe fn double_continuation_table(mem: &mut M) {
     }
 
     for i in old_size..new_size {
-        new_array.set_scalar(i, Value::from_scalar(i + 1));
+        new_array.set(i, Value::from_scalar(i + 1), mem);
     }
     allocation_barrier(new_table);
 
@@ -111,7 +111,7 @@ pub unsafe fn remember_continuation(mem: &mut M, ptr: Value) -> u32 {
 
     FREE_SLOT = table.get(idx).get_scalar();
 
-    table.set_pointer(idx, ptr, mem);
+    table.set(idx, ptr, mem);
 
     N_CONTINUATIONS += 1;
 
@@ -141,8 +141,8 @@ pub unsafe extern "C" fn peek_future_continuation(idx: u32) -> Value {
     ptr.as_array().get(FUTURE_ARRAY_INDEX)
 }
 
-#[no_mangle]
-pub unsafe fn recall_continuation(idx: u32) -> Value {
+#[ic_mem_fn]
+pub unsafe fn recall_continuation(mem: &mut M, idx: u32) -> Value {
     if !table_initialized() {
         rts_trap_with("recall_continuation: Continuation table not allocated");
     }
@@ -155,7 +155,7 @@ pub unsafe fn recall_continuation(idx: u32) -> Value {
 
     let ptr = table.get(idx);
 
-    table.set_scalar(idx, Value::from_scalar(FREE_SLOT));
+    table.set(idx, Value::from_scalar(FREE_SLOT), mem);
 
     FREE_SLOT = idx;
 
diff --git a/rts/motoko-rts/src/gc/incremental/sanity_checks.rs b/rts/motoko-rts/src/gc/incremental/sanity_checks.rs
index aa6000bd5ad..eec09d6557a 100644
--- a/rts/motoko-rts/src/gc/incremental/sanity_checks.rs
+++ b/rts/motoko-rts/src/gc/incremental/sanity_checks.rs
@@ -102,13 +102,10 @@ impl<'a, M: Memory> MemoryChecker<'a, M> {
             0,
             |gc, field_address| {
                 let value = *field_address;
-                // Ignore null pointers used in `text_iter`.
-                if value.get_ptr() as *const Obj != null() {
-                    if value.get_ptr() >= gc.heap.base_address() {
-                        gc.check_object(value);
-                    } else {
-                        gc.check_object_header(value);
-                    }
+                if value.is_ptr() {
+                    gc.check_object(value);
+                } else {
+                    gc.check_object_header(value);
                 }
             },
             |_, _, array| array.len(),
diff --git a/rts/motoko-rts/src/text_iter.rs b/rts/motoko-rts/src/text_iter.rs
index 4129f78724f..578cdded890 100644
--- a/rts/motoko-rts/src/text_iter.rs
+++ b/rts/motoko-rts/src/text_iter.rs
@@ -10,13 +10,11 @@
 //! 1. A pointer to the text
 //! 2. 0, or a pointer to the next list entry
 
-use core::ptr::null_mut;
-
 use crate::barriers::allocation_barrier;
 use crate::memory::{alloc_array, Memory};
 use crate::rts_trap_with;
 use crate::text::decode_code_point;
-use crate::types::{Array, Value, TAG_BLOB, TAG_CONCAT};
+use crate::types::{Value, TAG_BLOB, TAG_CONCAT};
 
 use motoko_rts_macros::ic_mem_fn;
 
@@ -50,6 +48,10 @@ const ITER_BLOB_IDX: u32 = 0;
 const ITER_POS_IDX: u32 = 1;
 const ITER_TODO_IDX: u32 = 2;
 
+// Use non-pointer sentinel value as `null` to allow simpler visitor logic.
+// Anlogous to the design of `continuation_table` and `persistence`.
+const NO_OBJECT: Value = Value::from_scalar(0);
+
 /// Returns a new iterator for the text
 #[ic_mem_fn]
 pub unsafe fn text_iter(mem: &mut M, text: Value) -> Value {
@@ -58,10 +60,10 @@ pub unsafe fn text_iter(mem: &mut M, text: Value) -> Value {
 
     // Initialize the TODO field first, to be able to use it use the location to `find_leaf`
     let todo_addr = array.payload_addr().add(ITER_TODO_IDX as usize) as *mut _;
-    *todo_addr = Value::from_ptr(null_mut() as *mut Array as usize);
+    *todo_addr = NO_OBJECT;
 
     // Initialize position field
-    array.set_scalar(ITER_POS_IDX, Value::from_scalar(0));
+    array.set(ITER_POS_IDX, Value::from_scalar(0), mem);
 
     // Initialize blob field, no pre-update barrier, but post-update barrier.
     array.initialize(
@@ -80,7 +82,7 @@ pub unsafe extern "C" fn text_iter_done(iter: Value) -> u32 {
     let blob = array.get(ITER_BLOB_IDX).as_blob();
     let todo = array.get(ITER_TODO_IDX);
 
-    if pos >= blob.len().as_u32() && todo.get_ptr() as *mut Array == null_mut() {
+    if pos >= blob.len().as_u32() && todo == NO_OBJECT {
         1
     } else {
         0
@@ -99,7 +101,7 @@ pub unsafe fn text_iter_next(mem: &mut M, iter: Value) -> u32 {
     if pos >= blob.len().as_u32() {
         let todo = iter_array.get(ITER_TODO_IDX);
 
-        if todo.get_ptr() as *mut Array == null_mut() {
+        if todo == NO_OBJECT {
             // Caller should check with text_iter_done
             rts_trap_with("text_iter_next: Iter already done");
         }
@@ -113,11 +115,11 @@ pub unsafe fn text_iter_next(mem: &mut M, iter: Value) -> u32 {
             // allocation)
             let concat = text.as_concat();
 
-            todo_array.set_pointer(TODO_TEXT_IDX, (*concat).text2, mem);
-            iter_array.set_scalar(ITER_POS_IDX, Value::from_scalar(0));
+            todo_array.set(TODO_TEXT_IDX, (*concat).text2, mem);
+            iter_array.set(ITER_POS_IDX, Value::from_scalar(0), mem);
             let todo_addr = iter_array.payload_addr().add(ITER_TODO_IDX as usize);
 
-            iter_array.set_pointer(
+            iter_array.set(
                 ITER_BLOB_IDX,
                 find_leaf(mem, (*concat).text1, todo_addr),
                 mem,
@@ -128,10 +130,10 @@ pub unsafe fn text_iter_next(mem: &mut M, iter: Value) -> u32 {
             // Otherwise remove the entry from the chain
             debug_assert_eq!(text.tag(), TAG_BLOB);
 
-            iter_array.set_pointer(ITER_BLOB_IDX, text, mem);
-            iter_array.set_scalar(ITER_POS_IDX, Value::from_scalar(0));
+            iter_array.set(ITER_BLOB_IDX, text, mem);
+            iter_array.set(ITER_POS_IDX, Value::from_scalar(0), mem);
 
-            iter_array.set_pointer(ITER_TODO_IDX, todo_array.get(TODO_LINK_IDX), mem);
+            iter_array.set(ITER_TODO_IDX, todo_array.get(TODO_LINK_IDX), mem);
 
             text_iter_next(mem, iter)
         }
@@ -140,7 +142,7 @@ pub unsafe fn text_iter_next(mem: &mut M, iter: Value) -> u32 {
         let blob_payload = blob.payload_const();
         let mut step: u32 = 0;
         let char = decode_code_point(blob_payload.add(pos as usize), &mut step as *mut u32);
-        iter_array.set_scalar(ITER_POS_IDX, Value::from_scalar(pos + step));
+        iter_array.set(ITER_POS_IDX, Value::from_scalar(pos + step), mem);
         char
     }
 }
diff --git a/rts/motoko-rts/src/types.rs b/rts/motoko-rts/src/types.rs
index 4efb03104a5..a9715a42420 100644
--- a/rts/motoko-rts/src/types.rs
+++ b/rts/motoko-rts/src/types.rs
@@ -504,23 +504,15 @@ impl Array {
         init_with_barrier(mem, slot_addr, value);
     }
 
-    /// Write a pointer value to an array element.
-    /// Uses a incremental pre-update barrier and a generational post-update barrier.
+    /// Write a value to an array element.
+    /// The written and overwritten value can be a scalar or a pointer.
+    /// Applies an incremental pre-update barrier when needed.
     /// Resolves pointer forwarding for the written value.
-    pub unsafe fn set_pointer(self: *mut Self, idx: u32, value: Value, mem: &mut M) {
-        debug_assert!(value.is_ptr());
+    pub unsafe fn set(self: *mut Self, idx: u32, value: Value, mem: &mut M) {
         let slot_addr = self.element_address(idx) as *mut Value;
         write_with_barrier(mem, slot_addr, value);
     }
 
-    /// Write a scalar value to an array element.
-    /// No need for a write barrier.
-    pub unsafe fn set_scalar(self: *mut Self, idx: u32, value: Value) {
-        debug_assert!(value.is_scalar());
-        let slot_addr = self.element_address(idx);
-        *(slot_addr as *mut Value) = value;
-    }
-
     #[inline]
     unsafe fn element_address(self: *const Self, idx: u32) -> usize {
         debug_assert!(self.len() > idx);

From 7f011625e15dcf1b3e39d5fd604c56cc1b8bf8dd Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 21 Aug 2023 15:03:07 +0200
Subject: [PATCH 050/260] Restore stable memory size

---
 src/codegen/compile.ml | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 8b84209a9d2..666ea440705 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -6762,6 +6762,11 @@ end (* MakeSerialization *)
 module Serialization = MakeSerialization(BumpStream)
 
 module Stabilization = struct
+  let restore_stable_memory env =
+    (* TODO: Check version and if needed, migrate from old serialized stable format. *)
+    IC.system_call env "stable64_size" ^^
+    StableMem.set_mem_size env
+
   let load_stable_actor env = E.call_import env "rts" "load_stable_actor"
     
   let save_stable_actor env = E.call_import env "rts" "save_stable_actor"
@@ -6780,9 +6785,10 @@ module Stabilization = struct
   let destabilize env t =
     load_stable_actor env ^^
     G.i (Test (Wasm.Values.I32 I32Op.Eqz)) ^^
-    G.if1 I32Type
+    (G.if1 I32Type
       (empty_actor env t)
-      (load_stable_actor env)
+      (load_stable_actor env)) ^^
+    restore_stable_memory env
 end
 
 (*

From 483cd4060ee6affcd8fe7495e20a0c901a5d3a71 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 21 Aug 2023 15:11:52 +0200
Subject: [PATCH 051/260] Adjust test, serialization no longer overflowing

---
 ...low.drun-run.ok => stable-size-no-overflow.drun-run.ok} | 2 +-
 test/run-drun/ok/stable-size-overflow.ic-ref-run.ok        | 7 -------
 ...{stable-size-overflow.mo => stable-size-no-overflow.mo} | 0
 3 files changed, 1 insertion(+), 8 deletions(-)
 rename test/run-drun/ok/{stable-size-overflow.drun-run.ok => stable-size-no-overflow.drun-run.ok} (60%)
 delete mode 100644 test/run-drun/ok/stable-size-overflow.ic-ref-run.ok
 rename test/run-drun/{stable-size-overflow.mo => stable-size-no-overflow.mo} (100%)

diff --git a/test/run-drun/ok/stable-size-overflow.drun-run.ok b/test/run-drun/ok/stable-size-no-overflow.drun-run.ok
similarity index 60%
rename from test/run-drun/ok/stable-size-overflow.drun-run.ok
rename to test/run-drun/ok/stable-size-no-overflow.drun-run.ok
index 3969ab1cb07..fc0ac7dc610 100644
--- a/test/run-drun/ok/stable-size-overflow.drun-run.ok
+++ b/test/run-drun/ok/stable-size-no-overflow.drun-run.ok
@@ -1,4 +1,4 @@
 ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
 ingress Completed: Reply: 0x4449444c0000
 debug.print: upgrading...
-ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: buffer_size overflow
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-size-overflow.ic-ref-run.ok b/test/run-drun/ok/stable-size-overflow.ic-ref-run.ok
deleted file mode 100644
index 75b91bf6b36..00000000000
--- a/test/run-drun/ok/stable-size-overflow.ic-ref-run.ok
+++ /dev/null
@@ -1,7 +0,0 @@
-=> update provisional_create_canister_with_cycles(record {settings = null; qgqjpK = null; amount = null; ddww...
-<= replied: (record {hymijyo = principal "rwlgt-iiaaa-aaaaa-aaaaa-cai"})
-=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0...
-<= replied: ()
-=> update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0...
-debug.print: upgrading...
-<= rejected (RC_CANISTER_ERROR): Pre-upgrade trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: buffer_size overflow"
diff --git a/test/run-drun/stable-size-overflow.mo b/test/run-drun/stable-size-no-overflow.mo
similarity index 100%
rename from test/run-drun/stable-size-overflow.mo
rename to test/run-drun/stable-size-no-overflow.mo

From c563ae5a94311569098a4ecefed72281f640e3be Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 21 Aug 2023 15:21:38 +0200
Subject: [PATCH 052/260] Support `--force-gc` mode again

---
 src/codegen/compile.ml       | 4 +++-
 test/run-drun/empty-actor.mo | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 666ea440705..51eac1a4e73 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -579,7 +579,9 @@ module E = struct
     Int32.(add (div (get_end_of_static_memory env) page_size) 1l)
 
   let collect_garbage env =
-    call_import env "rts" "schedule_incremental_gc"
+    let name = "incremental_gc" in
+    let gc_fn = if !Flags.force_gc then name else "schedule_" ^ name in
+    call_import env "rts" gc_fn
 
   (* See Note [Candid subtype checks] *)
   (* NB: we don't bother detecting duplicate registrations here because the code sharing machinery
diff --git a/test/run-drun/empty-actor.mo b/test/run-drun/empty-actor.mo
index f4a85e0220a..31684809b4f 100644
--- a/test/run-drun/empty-actor.mo
+++ b/test/run-drun/empty-actor.mo
@@ -9,7 +9,7 @@ actor {};
 // CHECK-NEXT:    i32.const 0
 // CHECK-NEXT:    call 32
 // CHECK-NEXT:    global.set 4
-// CHECK-NEXT:    call ${{copying_gc|compacting_gc|generational_gc|incremental_gc}}
+// CHECK-NEXT:    call $incremental_gc
 // CHECK-NEXT:    i32.const 0
 // CHECK-NEXT:    call 32
 // CHECK-NEXT:    global.get 4

From e57ad63c64276327c1d6e718ff0cb7239d8ae7d6 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 22 Aug 2023 16:13:07 +0200
Subject: [PATCH 053/260] More descriptive error when exceeding data segment
 capacity

---
 src/linking/linkModule.ml | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/linking/linkModule.ml b/src/linking/linkModule.ml
index 0cf3f7ddfb5..7f6656a8163 100644
--- a/src/linking/linkModule.ml
+++ b/src/linking/linkModule.ml
@@ -767,7 +767,11 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
   let new_heap_start = align 4l (Int32.add lib_heap_start dylink.memory_size) in
 
   (* Data segments must fit below 4MB according to the persistent heap layout. *)
-  assert ((Int32.to_int new_heap_start) <= 4 * 1024 * 1024);
+  (if (Int32.to_int new_heap_start) > 4 * 1024 * 1024 then
+    (raise (LinkError "The Wasm data segment size exceeds the supported maxmimum of 2MB."))
+  else
+    ()
+  );
 
   let old_table_size = read_table_size em1.module_ in
   let lib_table_start = align dylink.table_alignment old_table_size in

From c073d29c5387232bd31ca571ac223fcf82bbd533 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 22 Aug 2023 16:14:06 +0200
Subject: [PATCH 054/260] Adjust test

---
 test/run-drun/class-import.mo               | 11 +++--------
 test/run-drun/ok/class-import.drun-run.ok   |  2 --
 test/run-drun/ok/class-import.ic-ref-run.ok |  2 --
 3 files changed, 3 insertions(+), 12 deletions(-)

diff --git a/test/run-drun/class-import.mo b/test/run-drun/class-import.mo
index cad8c448449..24d757f97f3 100644
--- a/test/run-drun/class-import.mo
+++ b/test/run-drun/class-import.mo
@@ -2,8 +2,9 @@ import Prim "mo:⛔";
 import Cycles = "cycles/cycles";
 import M0 "class-import/empty";
 import M1 "class-import/one";
-import M2 "class-import/two";
 import M3 "class-import/trap";
+// Too many class imports exceed the maximum data segment size of 2MB,
+// in particular if they are compiled with sanity checks.
 
 actor a {
  public func go() : async () {
@@ -21,11 +22,6 @@ actor a {
    let one : M1.One = await M1.One("one");
    await one.test();
 
-   // test two arg class
-   Cycles.add(2_000_000_000_000);
-   let two : M2.Two = await M2.Two("one","two");
-   await two.test();
-
    // test non-trapping install
    try {
      Cycles.add(2_000_000_000_000);
@@ -54,8 +50,7 @@ a.go() //OR-CALL ingress go "DIDL\x00\x00"
 //SKIP run-ir
 //SKIP run-low
 
-// check exactly 4 embedded wasms
-//CHECK:  canister_update __motoko_async_helper
+// check exactly 3 embedded wasms
 //CHECK:  canister_update __motoko_async_helper
 //CHECK:  canister_update __motoko_async_helper
 //CHECK:  canister_update __motoko_async_helper
diff --git a/test/run-drun/ok/class-import.drun-run.ok b/test/run-drun/ok/class-import.drun-run.ok
index d6c7ff9471d..b75ebe214aa 100644
--- a/test/run-drun/ok/class-import.drun-run.ok
+++ b/test/run-drun/ok/class-import.drun-run.ok
@@ -4,7 +4,5 @@ debug.print: empty
 debug.print: () tested
 debug.print: one
 debug.print: "one" tested
-debug.print: ("one", "two")
-debug.print: ("one", "two")tested
 debug.print: caught trap
 ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/class-import.ic-ref-run.ok b/test/run-drun/ok/class-import.ic-ref-run.ok
index 96aaf64f1b2..ae5bde48e56 100644
--- a/test/run-drun/ok/class-import.ic-ref-run.ok
+++ b/test/run-drun/ok/class-import.ic-ref-run.ok
@@ -7,7 +7,5 @@ debug.print: empty
 debug.print: () tested
 debug.print: one
 debug.print: "one" tested
-debug.print: ("one", "two")
-debug.print: ("one", "two")tested
 debug.print: caught trap
 <= replied: ()

From ab675a55d98b3eddad739d4ae77fc4972b15da9f Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 13:39:27 +0200
Subject: [PATCH 055/260] Place object field hashes into dynamic heap

---
 rts/motoko-rts/src/debug.rs   |   2 +-
 rts/motoko-rts/src/types.rs   |   6 +-
 rts/motoko-rts/src/visitor.rs |   2 +
 src/codegen/compile.ml        | 368 +++++++++++++++++-----------------
 4 files changed, 195 insertions(+), 183 deletions(-)

diff --git a/rts/motoko-rts/src/debug.rs b/rts/motoko-rts/src/debug.rs
index 4c36a959bea..b15dc1c5510 100644
--- a/rts/motoko-rts/src/debug.rs
+++ b/rts/motoko-rts/src/debug.rs
@@ -143,7 +143,7 @@ pub(crate) unsafe fn print_boxed_object(buf: &mut WriteBuf, p: usize) {
                 buf,
                 " *mut Value {
+        &mut (*self).hash_blob
+    }
+
     pub unsafe fn payload_addr(self: *mut Self) -> *mut Value {
         self.add(1) as *mut Value // skip object header
     }
diff --git a/rts/motoko-rts/src/visitor.rs b/rts/motoko-rts/src/visitor.rs
index 00d2d2fc09d..aee48bc3046 100644
--- a/rts/motoko-rts/src/visitor.rs
+++ b/rts/motoko-rts/src/visitor.rs
@@ -32,6 +32,8 @@ pub unsafe fn visit_pointer_fields(
     match tag {
         TAG_OBJECT => {
             let obj = obj as *mut Object;
+            debug_assert!(is_pointer_field(obj.hash_blob_addr()));
+            visit_ptr_field(ctx, obj.hash_blob_addr());
             let obj_payload = obj.payload_addr();
             for i in 0..obj.size() {
                 let field_addr = obj_payload.add(i as usize);
diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 51eac1a4e73..057096758b8 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -3251,187 +3251,6 @@ module Prim = struct
     TaggedSmallWord.shift_leftWordNtoI32 b
 end (* Prim *)
 
-module Object = struct
- (* An object with a mutable field1 and immutable field 2 has the following
-    heap layout:
-
-    ┌──────┬─────┬──────────┬──────────┬─────────┬─────────────┬───┐
-    │ obj header │ n_fields │ hash_ptr │ ind_ptr │ field2_data │ … │
-    └──────┴─────┴──────────┴┬─────────┴┬────────┴─────────────┴───┘
-         ┌───────────────────┘          │
-         │   ┌──────────────────────────┘
-         │   ↓
-         │  ╶─┬────────┬─────────────┐
-         │    │ ObjInd │ field1_data │
-         ↓    └────────┴─────────────┘
-        ╶─┬─────────────┬─────────────┬───┐
-          │ field1_hash │ field2_hash │ … │
-          └─────────────┴─────────────┴───┘
-
-    The object header includes the object tag (Object) and the forwarding pointer.
-
-    The field hash array lives in static memory (so no size header needed).
-    The hash_ptr is skewed.
-
-    The field2_data for immutable fields is a vanilla word.
-
-    The field1_data for mutable fields are pointers to either an ObjInd, or a
-    MutBox (they have the same layout). This indirection is a consequence of
-    how we compile object literals with `await` instructions, as these mutable
-    fields need to be able to alias local mutable variables.
-
-    We could alternatively switch to an allocate-first approach in the
-    await-translation of objects, and get rid of this indirection -- if it were
-    not for the implementing of sharing of mutable stable values.
-  *)
-
-  let header_size = Int32.add Tagged.header_size 2l
-
-  (* Number of object fields *)
-  let size_field = Int32.add Tagged.header_size 0l
-  let hash_ptr_field = Int32.add Tagged.header_size 1l
-
-  module FieldEnv = Env.Make(String)
-
-  (* This is for non-recursive objects, i.e. ObjNewE *)
-  (* The instructions in the field already create the indirection if needed *)
-  let lit_raw env (fs : (string * (unit -> G.t)) list ) =
-    let name_pos_map =
-      fs |>
-      (* We could store only public fields in the object, but
-         then we need to allocate separate boxes for the non-public ones:
-         List.filter (fun (_, vis, f) -> vis.it = Public) |>
-      *)
-      List.map (fun (n,_) -> (E.hash env n, n)) |>
-      List.sort compare |>
-      List.mapi (fun i (_h,n) -> (n,Int32.of_int i)) |>
-      List.fold_left (fun m (n,i) -> FieldEnv.add n i m) FieldEnv.empty in
-
-    let sz = Int32.of_int (FieldEnv.cardinal name_pos_map) in
-
-    (* Create hash array *)
-    let hashes = fs |>
-      List.map (fun (n,_) -> E.hash env n) |>
-      List.sort compare in
-    let hash_ptr = E.add_static env StaticBytes.[ i32s hashes ] in
-
-    (* Allocate memory *)
-    let (set_ri, get_ri, ri) = new_local_ env I32Type "obj" in
-    Tagged.alloc env (Int32.add header_size sz) Tagged.Object ^^
-    set_ri ^^
-
-    (* Set size *)
-    get_ri ^^
-    compile_unboxed_const sz ^^
-    Tagged.store_field env size_field ^^
-
-    (* Set hash_ptr *)
-    get_ri ^^
-    compile_unboxed_const hash_ptr ^^
-    Tagged.store_field env hash_ptr_field ^^
-
-    (* Write all the fields *)
-    let init_field (name, mk_is) : G.t =
-      (* Write the pointer to the indirection *)
-      get_ri ^^
-      mk_is () ^^
-      let i = FieldEnv.find name name_pos_map in
-      let offset = Int32.add header_size i in
-      Tagged.store_field env offset
-    in
-    G.concat_map init_field fs ^^
-
-    (* Return the pointer to the object *)
-    get_ri ^^
-    Tagged.allocation_barrier env
-
-  (* Returns a pointer to the object field (without following the field indirection) *)
-  let idx_hash_raw env low_bound =
-    let name = Printf.sprintf "obj_idx<%d>" low_bound  in
-    Func.share_code2 env name (("x", I32Type), ("hash", I32Type)) [I32Type] (fun env get_x get_hash ->
-      let set_x = G.setter_for get_x in
-      let set_h_ptr, get_h_ptr = new_local env "h_ptr" in
-
-      get_x ^^ Tagged.load_forwarding_pointer env ^^ set_x ^^
-
-      get_x ^^ Tagged.load_field env hash_ptr_field ^^
-
-      (* Linearly scan through the fields (binary search can come later) *)
-      (* unskew h_ptr and advance both to low bound *)
-      compile_add_const Int32.(add ptr_unskew (mul Heap.word_size (of_int low_bound))) ^^
-      set_h_ptr ^^
-      get_x ^^
-      compile_add_const Int32.(mul Heap.word_size (add header_size (of_int low_bound))) ^^
-      set_x ^^
-      G.loop0 (
-          get_h_ptr ^^ load_unskewed_ptr ^^
-          get_hash ^^ G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^
-          G.if0
-            (get_x ^^ G.i Return)
-            (get_h_ptr ^^ compile_add_const Heap.word_size ^^ set_h_ptr ^^
-             get_x ^^ compile_add_const Heap.word_size ^^ set_x ^^
-             G.i (Br (nr 1l)))
-        ) ^^
-      G.i Unreachable
-    )
-
-  (* Returns a pointer to the object field (possibly following the indirection) *)
-  let idx_hash env low_bound indirect =
-    if indirect
-    then
-      let name = Printf.sprintf "obj_idx_ind<%d>" low_bound in
-      Func.share_code2 env name (("x", I32Type), ("hash", I32Type)) [I32Type] (fun env get_x get_hash ->
-      get_x ^^ get_hash ^^
-      idx_hash_raw env low_bound ^^
-      load_ptr ^^ Tagged.load_forwarding_pointer env ^^
-      compile_add_const (Int32.mul MutBox.field Heap.word_size)
-    )
-    else idx_hash_raw env low_bound
-
-  let field_type env obj_type s =
-    let _, fields = Type.as_obj_sub [s] obj_type in
-    Type.lookup_val_field s fields
-
-  (* Determines whether the field is mutable (and thus needs an indirection) *)
-  let is_mut_field env obj_type s =
-    let _, fields = Type.as_obj_sub [s] obj_type in
-    Type.is_mut (Type.lookup_val_field s fields)
-
-  (* Computes a lower bound for the positional index of a field in an object *)
-  let field_lower_bound env obj_type s =
-    let open Type in
-    let _, fields = as_obj_sub [s] obj_type in
-    List.iter (function {typ = Typ _; _} -> assert false | _ -> ()) fields;
-    let sorted_by_hash =
-      List.sort
-        (fun (h1, _) (h2, _) -> Lib.Uint32.compare h1 h2)
-        (List.map (fun f -> Lib.Uint32.of_int32 (E.hash env f.lab), f) fields) in
-    match Lib.List.index_of s (List.map (fun (_, {lab; _}) -> lab) sorted_by_hash) with
-    | Some i -> i
-    | _ -> assert false
-
-  (* Returns a pointer to the object field (without following the indirection) *)
-  let idx_raw env f =
-    compile_unboxed_const (E.hash env f) ^^
-    idx_hash_raw env 0
-
-  (* Returns a pointer to the object field (possibly following the indirection) *)
-  let idx env obj_type f =
-    compile_unboxed_const (E.hash env f) ^^
-    idx_hash env (field_lower_bound env obj_type f) (is_mut_field env obj_type f)
-
-  (* load the value (or the mutbox) *)
-  let load_idx_raw env f =
-    idx_raw env f ^^
-    load_ptr
-
-  (* load the actual value (dereferencing the mutbox) *)
-  let load_idx env obj_type f =
-    idx env obj_type f ^^
-    load_ptr
-
-end (* Object *)
-
 module Blob = struct
   (* The layout of a blob object is
 
@@ -3632,6 +3451,193 @@ module Blob = struct
 
 end (* Blob *)
 
+module Object = struct
+  (* An object with a mutable field1 and immutable field 2 has the following
+     heap layout:
+ 
+     ┌──────┬─────┬──────────┬──────────┬─────────┬─────────────┬───┐
+     │ obj header │ n_fields │ hash_ptr │ ind_ptr │ field2_data │ … │
+     └──────┴─────┴──────────┴┬─────────┴┬────────┴─────────────┴───┘
+          ┌───────────────────┘          │
+          │   ┌──────────────────────────┘
+          │   ↓
+          │  ╶─┬────────┬─────────────┐
+          │    │ ObjInd │ field1_data │
+          ↓    └────────┴─────────────┘
+          ┌─────────────┬─────────────┬─────────────┬───┐
+          │ blob header │ field1_hash │ field2_hash │ … │
+          └─────────────┴─────────────┴─────────────┴───┘        
+ 
+     The object header includes the object tag (Object) and the forwarding pointer.
+ 
+     The field hashes reside in a blob inside the dynamic heap.
+     The hash blob needs to be tracked by the GC, but not the content of the hash blob.
+     This is because the hash values are plain numbers that would look like skewed pointers.ters.
+     The hash_ptr is skewed.
+     TODO: Optimize by sharing the hash blob for objects of the same type.
+ 
+     The field2_data for immutable fields is a vanilla word.
+ 
+     The field1_data for mutable fields are pointers to either an ObjInd, or a
+     MutBox (they have the same layout). This indirection is a consequence of
+     how we compile object literals with `await` instructions, as these mutable
+     fields need to be able to alias local mutable variables.
+ 
+     We could alternatively switch to an allocate-first approach in the
+     await-translation of objects, and get rid of this indirection -- if it were
+     not for the implementing of sharing of mutable stable values.
+   *)
+ 
+  let header_size = Int32.add Tagged.header_size 2l
+ 
+  (* Number of object fields *)
+  let size_field = Int32.add Tagged.header_size 0l
+  let hash_ptr_field = Int32.add Tagged.header_size 1l
+ 
+  module FieldEnv = Env.Make(String)
+ 
+  (* This is for non-recursive objects, i.e. ObjNewE *)
+  (* The instructions in the field already create the indirection if needed *)
+  let lit_raw env (fs : (string * (unit -> G.t)) list ) =
+    let name_pos_map =
+      fs |>
+        (* We could store only public fields in the object, but
+          then we need to allocate separate boxes for the non-public ones:
+          List.filter (fun (_, vis, f) -> vis.it = Public) |>
+        *)
+        List.map (fun (n,_) -> (E.hash env n, n)) |>
+        List.sort compare |>
+        List.mapi (fun i (_h,n) -> (n,Int32.of_int i)) |>
+        List.fold_left (fun m (n,i) -> FieldEnv.add n i m) FieldEnv.empty in
+
+      let sz = Int32.of_int (FieldEnv.cardinal name_pos_map) in
+
+      (* Create hash blob *)
+      let hashes = fs |>
+        List.map (fun (n,_) -> E.hash env n) |>
+        List.sort compare in
+      let hash_blob env =
+        let hash_payload = StaticBytes.[ i32s hashes ] in
+        Blob.lit env (StaticBytes.as_bytes hash_payload) in
+
+      (* Allocate memory *)
+      let (set_ri, get_ri, ri) = new_local_ env I32Type "obj" in
+      Tagged.alloc env (Int32.add header_size sz) Tagged.Object ^^
+      set_ri ^^
+
+      (* Set size *)
+      get_ri ^^
+      compile_unboxed_const sz ^^
+      Tagged.store_field env size_field ^^
+
+      (* Set hash_ptr *)
+      get_ri ^^
+      hash_blob env ^^
+      Tagged.store_field env hash_ptr_field ^^
+
+      (* Write all the fields *)
+      let init_field (name, mk_is) : G.t =
+        (* Write the pointer to the indirection *)
+        get_ri ^^
+        mk_is () ^^
+        let i = FieldEnv.find name name_pos_map in
+        let offset = Int32.add header_size i in
+        Tagged.store_field env offset
+      in
+      G.concat_map init_field fs ^^
+
+      (* Return the pointer to the object *)
+      get_ri ^^
+      Tagged.allocation_barrier env
+ 
+  (* Returns a pointer to the object field (without following the field indirection) *)
+  let idx_hash_raw env low_bound =
+    let name = Printf.sprintf "obj_idx<%d>" low_bound  in
+    Func.share_code2 env name (("x", I32Type), ("hash", I32Type)) [I32Type] (fun env get_x get_hash ->
+      let set_x = G.setter_for get_x in
+      let set_h_ptr, get_h_ptr = new_local env "h_ptr" in
+
+      get_x ^^ Tagged.load_forwarding_pointer env ^^ set_x ^^
+
+      get_x ^^ Tagged.load_field env hash_ptr_field ^^
+      Blob.payload_ptr_unskewed env ^^
+
+      (* Linearly scan through the fields (binary search can come later) *)
+      (* unskew h_ptr and advance both to low bound *)
+      compile_add_const Int32.(mul Heap.word_size (of_int low_bound)) ^^
+      set_h_ptr ^^
+      get_x ^^
+      compile_add_const Int32.(mul Heap.word_size (add header_size (of_int low_bound))) ^^
+      set_x ^^
+      G.loop0 (
+          get_h_ptr ^^ load_unskewed_ptr ^^
+          get_hash ^^ G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^
+          G.if0
+            (get_x ^^ G.i Return)
+            (get_h_ptr ^^ compile_add_const Heap.word_size ^^ set_h_ptr ^^
+            get_x ^^ compile_add_const Heap.word_size ^^ set_x ^^
+            G.i (Br (nr 1l)))
+        ) ^^
+      G.i Unreachable
+    )
+
+  (* Returns a pointer to the object field (possibly following the indirection) *)
+  let idx_hash env low_bound indirect =
+    if indirect
+    then
+      let name = Printf.sprintf "obj_idx_ind<%d>" low_bound in
+      Func.share_code2 env name (("x", I32Type), ("hash", I32Type)) [I32Type] (fun env get_x get_hash ->
+      get_x ^^ get_hash ^^
+      idx_hash_raw env low_bound ^^
+      load_ptr ^^ Tagged.load_forwarding_pointer env ^^
+      compile_add_const (Int32.mul MutBox.field Heap.word_size)
+    )
+    else idx_hash_raw env low_bound
+
+  let field_type env obj_type s =
+    let _, fields = Type.as_obj_sub [s] obj_type in
+    Type.lookup_val_field s fields
+
+  (* Determines whether the field is mutable (and thus needs an indirection) *)
+  let is_mut_field env obj_type s =
+    let _, fields = Type.as_obj_sub [s] obj_type in
+    Type.is_mut (Type.lookup_val_field s fields)
+
+  (* Computes a lower bound for the positional index of a field in an object *)
+  let field_lower_bound env obj_type s =
+    let open Type in
+    let _, fields = as_obj_sub [s] obj_type in
+    List.iter (function {typ = Typ _; _} -> assert false | _ -> ()) fields;
+    let sorted_by_hash =
+      List.sort
+        (fun (h1, _) (h2, _) -> Lib.Uint32.compare h1 h2)
+        (List.map (fun f -> Lib.Uint32.of_int32 (E.hash env f.lab), f) fields) in
+    match Lib.List.index_of s (List.map (fun (_, {lab; _}) -> lab) sorted_by_hash) with
+    | Some i -> i
+    | _ -> assert false
+
+  (* Returns a pointer to the object field (without following the indirection) *)
+  let idx_raw env f =
+    compile_unboxed_const (E.hash env f) ^^
+    idx_hash_raw env 0
+
+  (* Returns a pointer to the object field (possibly following the indirection) *)
+  let idx env obj_type f =
+    compile_unboxed_const (E.hash env f) ^^
+    idx_hash env (field_lower_bound env obj_type f) (is_mut_field env obj_type f)
+
+  (* load the value (or the mutbox) *)
+  let load_idx_raw env f =
+    idx_raw env f ^^
+    load_ptr
+
+  (* load the actual value (dereferencing the mutbox) *)
+  let load_idx env obj_type f =
+    idx env obj_type f ^^
+    load_ptr
+ 
+end (* Object *) 
+
 module Text = struct
   (*
   Most of the heavy lifting around text values is in rts/motoko-rts/src/text.rs

From 80deb710103c23137aea0ec9b5b7fa3ede41a780 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 13:40:43 +0200
Subject: [PATCH 056/260] Bug fix in debug output

---
 rts/motoko-rts/src/debug.rs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/rts/motoko-rts/src/debug.rs b/rts/motoko-rts/src/debug.rs
index b15dc1c5510..dbe0b760118 100644
--- a/rts/motoko-rts/src/debug.rs
+++ b/rts/motoko-rts/src/debug.rs
@@ -123,13 +123,14 @@ unsafe fn print_tagged_scalar(buf: &mut WriteBuf, p: u32) {
 pub(crate) unsafe fn print_boxed_object(buf: &mut WriteBuf, p: usize) {
     let _ = write!(buf, "{:#x}: ", p);
 
-    let forward = (*(p as *mut Value)).forward();
+
+    let obj = p as *mut Obj;
+    let forward = (*obj).forward;
     if forward.get_ptr() != p {
         let _ = write!(buf, "", forward.get_ptr());
         return;
     }
 
-    let obj = p as *mut Obj;
     let tag = obj.tag();
 
     if tag == 0 {

From b221fba7c9c9af2cdc2cb3c920003fd13bd794ce Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 13:41:00 +0200
Subject: [PATCH 057/260] Reformat

---
 rts/motoko-rts/src/debug.rs | 1 -
 rts/motoko-rts/src/types.rs | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/rts/motoko-rts/src/debug.rs b/rts/motoko-rts/src/debug.rs
index dbe0b760118..07426c387a8 100644
--- a/rts/motoko-rts/src/debug.rs
+++ b/rts/motoko-rts/src/debug.rs
@@ -123,7 +123,6 @@ unsafe fn print_tagged_scalar(buf: &mut WriteBuf, p: u32) {
 pub(crate) unsafe fn print_boxed_object(buf: &mut WriteBuf, p: usize) {
     let _ = write!(buf, "{:#x}: ", p);
 
-
     let obj = p as *mut Obj;
     let forward = (*obj).forward;
     if forward.get_ptr() != p {
diff --git a/rts/motoko-rts/src/types.rs b/rts/motoko-rts/src/types.rs
index e42e4566bd8..8784b2eacab 100644
--- a/rts/motoko-rts/src/types.rs
+++ b/rts/motoko-rts/src/types.rs
@@ -527,7 +527,7 @@ impl Array {
 #[repr(C)] // See the note at the beginning of this module
 pub struct Object {
     pub header: Obj,
-    pub size: u32,     // Number of elements
+    pub size: u32,        // Number of elements
     pub hash_blob: Value, // Pointer to a blob containing the hashes of the object field labels.
 }
 

From ca920edfbce492db306aff73ad89a76b7439bbdd Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 15:32:27 +0200
Subject: [PATCH 058/260] Allow adding stable actor fields

---
 rts/motoko-rts/src/persistence.rs | 20 ++++++++++++++
 rts/motoko-rts/src/types.rs       |  7 +++++
 src/codegen/compile.ml            | 43 +++++++++++++++++++++++--------
 3 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index f3ff0ffef9e..50861495996 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -130,6 +130,26 @@ pub(crate) unsafe fn stable_actor_location() -> *mut Value {
     &mut (*metadata).stable_actor as *mut Value
 }
 
+#[no_mangle]
+#[cfg(feature = "ic")]
+pub unsafe extern "C" fn contains_field(actor: Value, field_hash: u32) -> bool {
+    use crate::constants::WORD_SIZE;
+
+    let object = actor.as_object();
+    let hash_blob = (*object).hash_blob.as_blob();
+    assert_eq!(hash_blob.len().as_u32() % WORD_SIZE, 0);
+    let number_of_fields = hash_blob.len().as_u32() / WORD_SIZE;
+    let mut current_address = hash_blob.payload_const() as u32;
+    for _ in 0..number_of_fields {
+        let hash_address = current_address as *mut u32;
+        if *hash_address == field_hash {
+            return true;
+        }
+        current_address += WORD_SIZE;
+    }
+    false
+}
+
 unsafe fn alloc_null(mem: &mut M) -> Value {
     let value = mem.alloc_words(size_of::());
     debug_assert!(value.get_ptr() >= HEAP_START);
diff --git a/rts/motoko-rts/src/types.rs b/rts/motoko-rts/src/types.rs
index 8784b2eacab..5d1800736f3 100644
--- a/rts/motoko-rts/src/types.rs
+++ b/rts/motoko-rts/src/types.rs
@@ -333,6 +333,13 @@ impl Value {
         self.forward().get_ptr() as *mut Array
     }
 
+    /// Get the pointer as `Object` using forwarding. In debug mode panics if the value is not a pointer.
+    pub unsafe fn as_object(self) -> *mut Object {
+        debug_assert!(self.get().is_ptr());
+        self.check_forwarding_pointer();
+        self.forward().get_ptr() as *mut Object
+    }
+
     /// Get the pointer as `Concat` using forwarding. In debug mode panics if the value is not a pointer or the
     /// pointed object is not a `Concat`.
     pub unsafe fn as_concat(self) -> *const Concat {
diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 057096758b8..ed3d760a918 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -864,6 +864,7 @@ module RTS = struct
     E.add_func_import env "rts" "running_gc" [] [I32Type];
     E.add_func_import env "rts" "load_stable_actor" [] [I32Type];
     E.add_func_import env "rts" "save_stable_actor" [I32Type] [];
+    E.add_func_import env "rts" "contains_field" [I32Type; I32Type] [I32Type];
     E.add_func_import env "rts" "set_static_root" [I32Type] [];
     E.add_func_import env "rts" "get_static_root" [] [I32Type];
     E.add_func_import env "rts" "null_singleton" [] [I32Type];
@@ -3549,6 +3550,12 @@ module Object = struct
       (* Return the pointer to the object *)
       get_ri ^^
       Tagged.allocation_barrier env
+
+  (* Reflection used by orthogonal persistence: 
+     Check whether an (actor) object contains a specific field *)
+  let contains_field env field =
+    compile_unboxed_const (E.hash env field) ^^
+    E.call_import env "rts" "contains_field"
  
   (* Returns a pointer to the object field (without following the field indirection) *)
   let idx_hash_raw env low_bound =
@@ -6778,24 +6785,38 @@ module Stabilization = struct
   let load_stable_actor env = E.call_import env "rts" "load_stable_actor"
     
   let save_stable_actor env = E.call_import env "rts" "save_stable_actor"
-    
-  let empty_actor env t =
-    let (_, fs) = Type.as_obj t in
-    let fs' = List.map
-      (fun f -> (f.Type.lab, fun () -> Opt.null_lit env))
-      fs
+
+  let create_actor env actor_type get_field_value =
+    let (_, field_declarations) = Type.as_obj actor_type in
+    let field_initializers = List.map
+      (fun field -> (field.Type.lab, fun () -> (get_field_value field.Type.lab)))
+      field_declarations
     in
-    Object.lit_raw env fs'
+    Object.lit_raw env field_initializers
+
+  let empty_actor env actor_type =
+    create_actor env actor_type (fun _ ->  Opt.null_lit env)
 
-  let stabilize env t =
+  let recover_actor env actor_type =
+    let recover_field field = 
+      load_stable_actor env ^^
+      Object.contains_field env field ^^
+      (G.if1 I32Type
+        (load_stable_actor env ^^ Object.load_idx_raw env field)
+        (Opt.null_lit env)
+      ) in
+    create_actor env actor_type recover_field
+
+  let stabilize env actor_type =
     save_stable_actor env
 
-  let destabilize env t =
+  let destabilize env actor_type =
     load_stable_actor env ^^
     G.i (Test (Wasm.Values.I32 I32Op.Eqz)) ^^
     (G.if1 I32Type
-      (empty_actor env t)
-      (load_stable_actor env)) ^^
+      (empty_actor env actor_type)
+      (recover_actor env actor_type)
+    ) ^^
     restore_stable_memory env
 end
 

From 114950fe05268f658ffc6b8bfd8570fd38a80cab Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 15:40:01 +0200
Subject: [PATCH 059/260] Adjust test

---
 test/run-drun/destabilization-crash.drun         |  3 ---
 test/run-drun/large-destabilization.drun         |  3 +++
 .../large-destabilization.mo}                    |  1 -
 test/run-drun/ok/destabilization-crash.drun.ok   | 16 ----------------
 test/run-drun/ok/large-destabilization.drun.ok   | 16 ++++++++++++++++
 5 files changed, 19 insertions(+), 20 deletions(-)
 delete mode 100644 test/run-drun/destabilization-crash.drun
 create mode 100644 test/run-drun/large-destabilization.drun
 rename test/run-drun/{destabilization-crash/destabilization-crash.mo => large-destabilization/large-destabilization.mo} (95%)
 delete mode 100644 test/run-drun/ok/destabilization-crash.drun.ok
 create mode 100644 test/run-drun/ok/large-destabilization.drun.ok

diff --git a/test/run-drun/destabilization-crash.drun b/test/run-drun/destabilization-crash.drun
deleted file mode 100644
index 1ef09703b28..00000000000
--- a/test/run-drun/destabilization-crash.drun
+++ /dev/null
@@ -1,3 +0,0 @@
-# SKIP ic-ref-run
-install $ID destabilization-crash/destabilization-crash.mo ""
-upgrade $ID destabilization-crash/destabilization-crash.mo ""
diff --git a/test/run-drun/large-destabilization.drun b/test/run-drun/large-destabilization.drun
new file mode 100644
index 00000000000..069817462cb
--- /dev/null
+++ b/test/run-drun/large-destabilization.drun
@@ -0,0 +1,3 @@
+# SKIP ic-ref-run
+install $ID large-destabilization/large-destabilization.mo ""
+upgrade $ID large-destabilization/large-destabilization.mo ""
diff --git a/test/run-drun/destabilization-crash/destabilization-crash.mo b/test/run-drun/large-destabilization/large-destabilization.mo
similarity index 95%
rename from test/run-drun/destabilization-crash/destabilization-crash.mo
rename to test/run-drun/large-destabilization/large-destabilization.mo
index 120a4a0d341..652e6ab2c95 100644
--- a/test/run-drun/destabilization-crash/destabilization-crash.mo
+++ b/test/run-drun/large-destabilization/large-destabilization.mo
@@ -38,7 +38,6 @@ actor a {
    system func postupgrade() {
      // if we get here, destabilization has succeeded
      Prim.debugPrint "postupgrade!";
-     assert false; // trap to avoid saving state to disk
    }
 
 }
diff --git a/test/run-drun/ok/destabilization-crash.drun.ok b/test/run-drun/ok/destabilization-crash.drun.ok
deleted file mode 100644
index dade05bb901..00000000000
--- a/test/run-drun/ok/destabilization-crash.drun.ok
+++ /dev/null
@@ -1,16 +0,0 @@
-ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
-ingress Completed: Reply: 0x4449444c0000
-debug.print: preupgrade!
-debug.print: {heap_MB = 64}
-debug.print: {heap_MB = 128}
-debug.print: {heap_MB = 192}
-debug.print: {heap_MB = 256}
-debug.print: {heap_MB = 320}
-debug.print: {heap_MB = 384}
-debug.print: {heap_MB = 448}
-debug.print: {heap_MB = 512}
-debug.print: {heap_MB = 576}
-debug.print: {heap_MB = 640}
-debug.print: {heap_MB = 704}
-debug.print: postupgrade!
-ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: assertion failed at destabilization-crash.mo:41.6-41.18
diff --git a/test/run-drun/ok/large-destabilization.drun.ok b/test/run-drun/ok/large-destabilization.drun.ok
new file mode 100644
index 00000000000..ae09687e79d
--- /dev/null
+++ b/test/run-drun/ok/large-destabilization.drun.ok
@@ -0,0 +1,16 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: preupgrade!
+debug.print: {heap_MB = 68}
+debug.print: {heap_MB = 132}
+debug.print: {heap_MB = 196}
+debug.print: {heap_MB = 260}
+debug.print: {heap_MB = 324}
+debug.print: {heap_MB = 388}
+debug.print: {heap_MB = 452}
+debug.print: {heap_MB = 516}
+debug.print: {heap_MB = 580}
+debug.print: {heap_MB = 644}
+debug.print: {heap_MB = 708}
+debug.print: postupgrade!
+ingress Completed: Reply: 0x4449444c0000

From 12d8caf4431ed630c9aa6c219cd23358ab9a5bfe Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 16:52:43 +0200
Subject: [PATCH 060/260] Improve error for too large data segments

---
 src/exes/moc.ml            | 6 +++++-
 src/linking/linkModule.ml  | 3 ++-
 src/linking/linkModule.mli | 1 +
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/exes/moc.ml b/src/exes/moc.ml
index b0ed5cefcb5..f8cc5299fde 100644
--- a/src/exes/moc.ml
+++ b/src/exes/moc.ml
@@ -297,7 +297,11 @@ let () =
   process_metadata_names "public" !Flags.public_metadata_names;
   process_metadata_names "omit" !Flags.omit_metadata_names;
   try
-    process_files !args
+    match process_files !args with
+      (* TODO: Find a better place to gracefully handle the input-dependent linker error *)
+    | exception Linking.LinkModule.TooLargeDataSegments error_message ->
+      Printf.eprintf "Error: %s" error_message; ()
+    | () -> ()
   with
   | Sys_error msg ->
     (* IO error *)
diff --git a/src/linking/linkModule.ml b/src/linking/linkModule.ml
index 7f6656a8163..65d18c9d97b 100644
--- a/src/linking/linkModule.ml
+++ b/src/linking/linkModule.ml
@@ -274,6 +274,7 @@ let remove_non_ic_exports (em : extended_module) : extended_module =
 (* Generic linking logic *)
 
 exception LinkError of string
+exception TooLargeDataSegments of string
 
 type renumbering = int32 -> int32
 
@@ -768,7 +769,7 @@ let link (em1 : extended_module) libname (em2 : extended_module) =
 
   (* Data segments must fit below 4MB according to the persistent heap layout. *)
   (if (Int32.to_int new_heap_start) > 4 * 1024 * 1024 then
-    (raise (LinkError "The Wasm data segment size exceeds the supported maxmimum of 2MB."))
+    (raise (TooLargeDataSegments "The Wasm data segment size exceeds the supported maxmimum of 2MB."))
   else
     ()
   );
diff --git a/src/linking/linkModule.mli b/src/linking/linkModule.mli
index 407aa2d59c5..ecab52c83ae 100644
--- a/src/linking/linkModule.mli
+++ b/src/linking/linkModule.mli
@@ -4,5 +4,6 @@
    - the module containing that library
 *)
 exception LinkError of string
+exception TooLargeDataSegments of string
 
 val link : Wasm_exts.CustomModule.extended_module -> string -> Wasm_exts.CustomModule.extended_module -> Wasm_exts.CustomModule.extended_module

From 225738561cd294cbc239566616d69cbc889aa9ec Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 17:02:06 +0200
Subject: [PATCH 061/260] Minor refactoring

---
 rts/motoko-rts/src/persistence.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index 50861495996..c8f917726c9 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -124,12 +124,14 @@ pub unsafe fn save_stable_actor(mem: &mut M, actor: Value) {
     write_with_barrier(mem, location, actor);
 }
 
-// GC root pointer required for GC marking and updating.
+/// GC root pointer required for GC marking and updating.
 pub(crate) unsafe fn stable_actor_location() -> *mut Value {
     let metadata = PersistentMetadata::get();
     &mut (*metadata).stable_actor as *mut Value
 }
 
+/// Determine whether an object contains a specific field.
+/// Used for upgrading to an actor with additional stable fields.
 #[no_mangle]
 #[cfg(feature = "ic")]
 pub unsafe extern "C" fn contains_field(actor: Value, field_hash: u32) -> bool {

From 5424d168ea65a448839e653e54df48f82eed8977 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Fri, 25 Aug 2023 17:07:41 +0200
Subject: [PATCH 062/260] Comment refactoring

---
 rts/motoko-rts/src/persistence.rs | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index c8f917726c9..04beb93179e 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -16,30 +16,33 @@ const FINGERPRINT: [char; 32] = [
     'E', 'R', 'S', 'I', 'S', 'T', 'E', 'N', 'C', 'E', ' ', '3', '2',
 ];
 const VERSION: usize = 1;
-// The `Value` representation in the default-initialized Wasm memory.
-// The GC ignores this value since it is a scalar representation.
+/// The `Value` representation in the default-initialized Wasm memory.
+/// The GC ignores this value since it is a scalar representation.
 const DEFAULT_VALUE: Value = Value::from_scalar(0);
 
-// Use a long-term representation by relying on C layout.
-// The `Value` references belong to the GC root set and require forwarding pointer resolution.
+/// The persistent metadata stored at the defined location `METADTA_ADDRESS` in memory.
+/// Use a long-term representation by relying on C layout.
+/// The `Value` references belong to the GC root set and require forwarding pointer resolution.
 #[repr(C)]
 struct PersistentMetadata {
-    // Predefined character sequence in the memory to double check the orthogonal persistence mode.
+    /// Predefined character sequence in the memory to double check the orthogonal persistence mode.
     fingerprint: [char; 32],
-    // Version of the orthogonal persistence. To be increased on every persistent memory layout modification.
+    /// Version of the orthogonal persistence. To be increased on every persistent memory layout modification.
     version: usize,
-    // Reference to the stable sub-record of the actor, comprising all stable actor fields. Set before upgrade.
-    // Constitutes a GC root and requires pointer forwarding.
+    /// Reference to the stable sub-record of the actor, comprising all stable actor fields. Set before upgrade.
+    /// Constitutes a GC root and requires pointer forwarding.
     stable_actor: Value,
-    // The state of the incremental GC including the partitioned heap description.
-    // The GC continues work after upgrades.
+    /// The state of the incremental GC including the partitioned heap description.
+    /// The GC continues work after upgrades.
     incremental_gc_state: State,
-    // Singleton of the top-level null value. To be retained across upgrades.
-    // Constitutes a GC root and requires pointer forwarding.
+    /// Singleton of the top-level null value. To be retained across upgrades.
+    /// Constitutes a GC root and requires pointer forwarding.
     null_singleton: Value,
 }
 
+/// Location of the persistent metadata. Prereseved and fixed forever.
 const METATDATA_ADDRESS: usize = 4 * 1024 * 1024;
+/// The reserved maximum size of the metadata, contains a reserve for future extension of the metadata.
 const METADATA_RESERVE: usize = 128 * 1024;
 
 // TODO: Include partition table in reserved space.

From cd8131e7e4c756fccf651a098c6c8447fc93b2f7 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 28 Aug 2023 09:53:27 +0200
Subject: [PATCH 063/260] Prepare memory compatibility check

---
 rts/motoko-rts-tests/src/gc.rs                |  1 +
 .../src/gc/incremental/roots.rs               |  1 +
 rts/motoko-rts/src/gc/incremental/roots.rs    |  5 ++-
 rts/motoko-rts/src/persistence.rs             | 40 +++++++++++++++++--
 .../src/persistence/compatibility.rs          |  8 ++++
 src/codegen/compile.ml                        |  6 +++
 6 files changed, 55 insertions(+), 6 deletions(-)
 create mode 100644 rts/motoko-rts/src/persistence/compatibility.rs

diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs
index 7f903700fbe..11842591be2 100644
--- a/rts/motoko-rts-tests/src/gc.rs
+++ b/rts/motoko-rts-tests/src/gc.rs
@@ -468,6 +468,7 @@ fn run(heap: &mut MotokoHeap) -> bool {
                 continuation_table_location,
                 unused_root,
                 unused_root,
+                unused_root,
             ];
             IncrementalGC::instance(heap, get_incremental_gc_state())
                 .empty_call_stack_increment(roots);
diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs
index 5454f2d55d1..27d320b559a 100644
--- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs
+++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs
@@ -69,6 +69,7 @@ unsafe fn get_roots(heap: &MotokoHeap) -> Roots {
         continuation_table_location,
         unused_root,
         unused_root,
+        unused_root,
     ]
 }
 
diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs
index e973a702151..d758e978031 100644
--- a/rts/motoko-rts/src/gc/incremental/roots.rs
+++ b/rts/motoko-rts/src/gc/incremental/roots.rs
@@ -9,18 +9,19 @@ use crate::{types::Value, visitor::is_pointer_field};
 static mut STATIC_ROOT: Value = Value::from_scalar(0);
 
 /// GC root set.
-pub type Roots = [*mut Value; 4];
+pub type Roots = [*mut Value; 5];
 
 #[cfg(feature = "ic")]
 pub unsafe fn root_set() -> Roots {
     use crate::{
         continuation_table::continuation_table_loc,
-        persistence::{null_singleton_location, stable_actor_location},
+        persistence::{null_singleton_location, stable_actor_location, stable_type_location},
     };
     [
         static_root_location(),
         continuation_table_loc(),
         stable_actor_location(),
+        stable_type_location(),
         null_singleton_location(),
     ]
 }
diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index 04beb93179e..1d2454cabf8 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -2,13 +2,16 @@
 //!
 //! Persistent metadata table, located at 4MB, in the static partition space.
 
+mod compatibility;
+
 use motoko_rts_macros::ic_mem_fn;
 
 use crate::{
     barriers::{allocation_barrier, write_with_barrier},
     gc::incremental::{IncrementalGC, State},
     memory::Memory,
-    types::{size_of, Null, Value, TAG_NULL},
+    persistence::compatibility::check_memory_compatibility,
+    types::{size_of, Null, Value, TAG_BLOB, TAG_NULL},
 };
 
 const FINGERPRINT: [char; 32] = [
@@ -32,6 +35,9 @@ struct PersistentMetadata {
     /// Reference to the stable sub-record of the actor, comprising all stable actor fields. Set before upgrade.
     /// Constitutes a GC root and requires pointer forwarding.
     stable_actor: Value,
+    /// Reference to the stable type descriptor blob, serving for heap compatibility checks on upgrades.
+    /// Constitutes a GC root and requires pointer forwarding.
+    stable_type: Value,
     /// The state of the incremental GC including the partitioned heap description.
     /// The GC continues work after upgrades.
     incremental_gc_state: State,
@@ -63,6 +69,7 @@ impl PersistentMetadata {
             initialized
                 || (*self).fingerprint == ['\0'; 32]
                     && (*self).stable_actor == DEFAULT_VALUE
+                    && (*self).stable_type == DEFAULT_VALUE
                     && (*self).null_singleton == DEFAULT_VALUE
         );
         initialized
@@ -83,6 +90,7 @@ impl PersistentMetadata {
         (*self).fingerprint = FINGERPRINT;
         (*self).version = VERSION;
         (*self).stable_actor = DEFAULT_VALUE;
+        (*self).stable_type = DEFAULT_VALUE;
         (*self).incremental_gc_state = IncrementalGC::initial_gc_state(mem, HEAP_START);
         (*self).null_singleton = DEFAULT_VALUE;
     }
@@ -107,7 +115,7 @@ pub unsafe fn initialize_memory(mem: &mut M) {
 unsafe fn allocate_initial_objects(mem: &mut M) {
     let metadata = PersistentMetadata::get();
     debug_assert!((*metadata).null_singleton == DEFAULT_VALUE);
-    (*metadata).null_singleton = alloc_null(mem);
+    (*metadata).null_singleton = allocate_null(mem);
 }
 
 /// Returns the stable sub-record of the actor of the upgraded canister version.
@@ -115,6 +123,7 @@ unsafe fn allocate_initial_objects(mem: &mut M) {
 #[no_mangle]
 pub unsafe extern "C" fn load_stable_actor() -> Value {
     let metadata = PersistentMetadata::get();
+    assert!((*metadata).stable_type.forward_if_possible() != DEFAULT_VALUE);
     (*metadata).stable_actor.forward_if_possible()
 }
 
@@ -122,7 +131,8 @@ pub unsafe extern "C" fn load_stable_actor() -> Value {
 #[ic_mem_fn]
 pub unsafe fn save_stable_actor(mem: &mut M, actor: Value) {
     assert!(actor != DEFAULT_VALUE);
-    let metadata = PersistentMetadata::get();
+    let metadata: *mut PersistentMetadata = PersistentMetadata::get();
+    assert!((*metadata).stable_type.forward_if_possible() != DEFAULT_VALUE);
     let location = &mut (*metadata).stable_actor as *mut Value;
     write_with_barrier(mem, location, actor);
 }
@@ -155,7 +165,7 @@ pub unsafe extern "C" fn contains_field(actor: Value, field_hash: u32) -> bool {
     false
 }
 
-unsafe fn alloc_null(mem: &mut M) -> Value {
+unsafe fn allocate_null(mem: &mut M) -> Value {
     let value = mem.alloc_words(size_of::());
     debug_assert!(value.get_ptr() >= HEAP_START);
     let null = value.get_ptr() as *mut Null;
@@ -165,6 +175,28 @@ unsafe fn alloc_null(mem: &mut M) -> Value {
     value
 }
 
+/// Register the stable actor type on canister installation and upgrade.
+/// The type is stored in the persistent metadata memory for later retrieval on canister upgrades.
+/// On an upgrade, the memory compatibility between the new and existing stable type is checked.
+/// The `new_type` value points to a blob encoding the new stable actor type.
+#[ic_mem_fn]
+pub unsafe fn register_stable_type(mem: &mut M, new_type: Value) {
+    assert_eq!(new_type.tag(), TAG_BLOB);
+    let metadata = PersistentMetadata::get();
+    let old_type = (*metadata).stable_type.forward_if_possible();
+    if old_type != DEFAULT_VALUE {
+        check_memory_compatibility(old_type, new_type);
+    }
+    let location = &mut (*metadata).stable_type as *mut Value;
+    write_with_barrier(mem, location, new_type);
+}
+
+/// GC root pointer required for GC marking and updating.
+pub(crate) unsafe fn stable_type_location() -> *mut Value {
+    let metadata = PersistentMetadata::get();
+    &mut (*metadata).stable_type as *mut Value
+}
+
 /// Get the null singleton used for top-level optional types.
 /// Serves for optimized null checks by pointer comparison.
 /// The forwarding pointer of this object is already resolved.
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
new file mode 100644
index 00000000000..59464f8fbc8
--- /dev/null
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -0,0 +1,8 @@
+use crate::types::Value;
+
+/// Check whether the existing persistent stable type is compatible to the new stable type.
+/// Traps if the check fails.
+/// Both arguments point to blobs encoding a stable actor type.
+pub fn check_memory_compatibility(_old_type: Value, _new_type: Value) {
+    // TODO: Implement
+}
diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index ed3d760a918..31f7b8f52a7 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -862,6 +862,7 @@ module RTS = struct
     E.add_func_import env "rts" "write_with_barrier" [I32Type; I32Type] [];
     E.add_func_import env "rts" "allocation_barrier" [I32Type] [I32Type];
     E.add_func_import env "rts" "running_gc" [] [I32Type];
+    E.add_func_import env "rts" "register_stable_type" [I32Type] [];
     E.add_func_import env "rts" "load_stable_actor" [] [I32Type];
     E.add_func_import env "rts" "save_stable_actor" [I32Type] [];
     E.add_func_import env "rts" "contains_field" [I32Type; I32Type] [I32Type];
@@ -6786,6 +6787,10 @@ module Stabilization = struct
     
   let save_stable_actor env = E.call_import env "rts" "save_stable_actor"
 
+  let register_stable_type env actor_type = 
+    Blob.lit env "" ^^
+    E.call_import env "rts" "register_stable_type"
+
   let create_actor env actor_type get_field_value =
     let (_, field_declarations) = Type.as_obj actor_type in
     let field_initializers = List.map
@@ -6811,6 +6816,7 @@ module Stabilization = struct
     save_stable_actor env
 
   let destabilize env actor_type =
+    register_stable_type env actor_type ^^
     load_stable_actor env ^^
     G.i (Test (Wasm.Values.I32 I32Op.Eqz)) ^^
     (G.if1 I32Type

From 4d26bd10422d7229ee8f3a80f6ef34194a45b370 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 28 Aug 2023 16:21:18 +0200
Subject: [PATCH 064/260] Draft the memory compatibility check

---
 rts/motoko-rts/src/persistence.rs             |   6 +-
 .../src/persistence/compatibility.rs          | 248 +++++++++++++++++-
 src/codegen/compile.ml                        |   5 +-
 src/codegen/persistence.ml                    | 116 ++++++++
 4 files changed, 365 insertions(+), 10 deletions(-)
 create mode 100644 src/codegen/persistence.ml

diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index 1d2454cabf8..a43fa180264 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -10,7 +10,7 @@ use crate::{
     barriers::{allocation_barrier, write_with_barrier},
     gc::incremental::{IncrementalGC, State},
     memory::Memory,
-    persistence::compatibility::check_memory_compatibility,
+    persistence::compatibility::memory_compatible,
     types::{size_of, Null, Value, TAG_BLOB, TAG_NULL},
 };
 
@@ -184,8 +184,8 @@ pub unsafe fn register_stable_type(mem: &mut M, new_type: Value) {
     assert_eq!(new_type.tag(), TAG_BLOB);
     let metadata = PersistentMetadata::get();
     let old_type = (*metadata).stable_type.forward_if_possible();
-    if old_type != DEFAULT_VALUE {
-        check_memory_compatibility(old_type, new_type);
+    if old_type != DEFAULT_VALUE && !memory_compatible(old_type, new_type) {
+        panic!("Memory-incompatible program upgrade");
     }
     let location = &mut (*metadata).stable_type as *mut Value;
     write_with_barrier(mem, location, new_type);
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index 59464f8fbc8..d35da8b5844 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -1,8 +1,246 @@
-use crate::types::Value;
+//! Persistent type compatibility check.
+//! Determines whether the a new actor type is compatible with the existing persistent state.
+//!
+//! This is determined by focusing on the stable actor fields (comprised in `stable_actor`).
+//!
+//! To be compatible, all the fields of an actor must be compatible to the previous state.
+//! A field is compatible if at least one of the condition holds:
+//! * The field is declared flexible, i.e. not stable, in the previous or new actor version.
+//! * The previous actor does not contain a field with that same name.
+//! * The field type is compatible to the type of the equally named field in the previous version.
+//!
+//! A new type is compatible to a previous type if
+//! * the types are identical
+//! * The new type is sub-type of the previous field.
+//!
+//! The existing sub-type relations are also memory-compatible at the runtime system level:
+//! * Nat can be interpreted as Int.
+//! * If record fields are dropped in a declaration, old records with more fields can still
+//!   be accessed.
+//!   -> However, redundant old fields should be removed in the future, e.g. by the GC.
+//! * Variant options can be added, without invalidating the existing variant tags.
+//!
+//! Binary format of the stable type encoding, generated by the Motoko compiler backend, `persistence.ml`.
+//!
+//! All composed non-primitive types are encoded in a table without defined order, except for the
+//! first entry.
+//! The first type in the table denotes the stable sub-type of the actor, ie. all its stable field
+//! declarations, without option wrapping.
+//!
+//! Non-primitive types are referred to by a positive index according to the type table.
+//! Primitive types are encoded by predefined negative indices.
+//! All numbers (type indices etc.) are encoded as little endian i32.
+//!
+//! ```
+//!  ::= length:i32 ()^length
+//!  ::= 
+//!  ::= 1l field_list
+//!  ::= length:i32 ()^length
+//!  ::= label_hash:i32 type_index:i32
+//! ```
+//!
+//! Predefined primitive type indices
+//!
+//! Type    | Index
+//! ------- | --------
+//! Nat     | -1l
 
-/// Check whether the existing persistent stable type is compatible to the new stable type.
-/// Traps if the check fails.
+use crate::types::{size_of, Value};
+
+const OBJECT_ENCODING_TAG: i32 = 1;
+
+const ACTOR_TYPE_INDEX: i32 = 0;
+
+const OBJECT_ENCODING_HEADER: usize = 2; // object_tag field_list_length
+const FIELD_ENCODING_LENGTH: usize = 2; // label_hash type_index
+
+struct EncodedData {
+    words: *const i32,
+    size: usize,
+}
+
+impl EncodedData {
+    pub fn new(words: *const i32, size: usize) -> EncodedData {
+        EncodedData { words, size }
+    }
+
+    unsafe fn read(&self, offset: usize) -> i32 {
+        assert!(offset < self.size);
+        let location = self.words.add(offset);
+        *location
+    }
+
+    unsafe fn sub_view(&self, offset: usize, length: usize) -> EncodedData {
+        assert!(offset + length <= self.size);
+        EncodedData {
+            words: self.words.add(offset),
+            size: length,
+        }
+    }
+}
+
+struct TypeTable {
+    data: EncodedData,
+}
+
+impl TypeTable {
+    unsafe fn new(value: Value) -> TypeTable {
+        let blob = value.as_blob();
+        assert_eq!(blob.len().as_usize() % size_of::().as_usize(), 0);
+        let words = blob.payload_const() as *const i32;
+        let size = blob.len().as_usize() / size_of::().as_usize();
+        let data = EncodedData::new(words, size);
+        TypeTable { data }
+    }
+
+    unsafe fn count_types(&self) -> usize {
+        let count = self.data.read(0);
+        assert!(count >= 0);
+        count as usize
+    }
+
+    unsafe fn get_type(&self, type_index: i32) -> Type {
+        assert!(type_index <= self.count_types() as i32);
+        assert_eq!(type_index, 0); // TODO: Skip over other types
+        let start = 1;
+        let tag = self.data.read(start);
+        assert_eq!(tag, OBJECT_ENCODING_TAG);
+        let count_fields = self.data.read(start + 1);
+        assert!(count_fields >= 0);
+        let size = OBJECT_ENCODING_HEADER + count_fields as usize * FIELD_ENCODING_LENGTH;
+        let type_view = self.data.sub_view(start, size);
+        let object_type = ObjectType::new(type_view);
+        Type::Object(object_type)
+    }
+
+    unsafe fn get_actor(&self) -> ObjectType {
+        match self.get_type(ACTOR_TYPE_INDEX) {
+            Type::Object(object_type) => object_type,
+            // _ => panic!("Invalid stable type encoding")
+        }
+    }
+}
+
+enum Type {
+    Object(ObjectType),
+}
+
+struct ObjectType {
+    data: EncodedData,
+}
+
+impl ObjectType {
+    unsafe fn new(data: EncodedData) -> ObjectType {
+        assert_eq!(data.read(0), OBJECT_ENCODING_TAG);
+        ObjectType { data }
+    }
+
+    unsafe fn count_fields(&self) -> usize {
+        assert_eq!(self.data.read(0), OBJECT_ENCODING_TAG);
+        let count = self.data.read(1);
+        assert!(count >= 0);
+        count as usize
+    }
+
+    unsafe fn get_field(&self, field_index: usize) -> Field {
+        let field_offset = OBJECT_ENCODING_HEADER + field_index * FIELD_ENCODING_LENGTH;
+        let label_hash = self.data.read(field_offset);
+        let type_index = self.data.read(field_offset + 1);
+        Field {
+            label_hash,
+            type_index,
+        }
+    }
+
+    unsafe fn find_field(&self, label_hash: i32) -> Option {
+        for field_index in 0..self.count_fields() {
+            let field = self.get_field(field_index);
+            if field.label_hash == label_hash {
+                return Some(field);
+            }
+        }
+        None
+    }
+}
+
+struct Field {
+    label_hash: i32,
+    type_index: i32,
+}
+
+fn type_compatible(
+    _new_type_table: &TypeTable,
+    new_type_index: i32,
+    _old_type_table: &TypeTable,
+    old_type_index: i32,
+) -> bool {
+    unsafe {
+        println!(100, "COMPATIBLE {new_type_index} {old_type_index}");
+    }
+    if new_type_index < 0 {
+        new_type_index == old_type_index
+    } else {
+        false
+    }
+}
+
+// TODO: Unit test this funcionality
+
+// TODO: Use visited table for handling recursion and avoiding repeated type checks.
+
+/// Test whether the new stable type complies with the existing old stable type.
 /// Both arguments point to blobs encoding a stable actor type.
-pub fn check_memory_compatibility(_old_type: Value, _new_type: Value) {
-    // TODO: Implement
+pub unsafe fn memory_compatible(old_type: Value, new_type: Value) -> bool {
+    let new_type_table = TypeTable::new(new_type);
+    let old_type_table = TypeTable::new(old_type);
+    let new_actor = new_type_table.get_actor();
+    let old_actor = old_type_table.get_actor();
+    for new_field_index in 0..new_actor.count_fields() {
+        let new_field = new_actor.get_field(new_field_index);
+        println!(100, "CHECK ACTOR FIELD {} {}", new_field.label_hash, old_actor.find_field(new_field.label_hash).is_some());
+        match old_actor.find_field(new_field.label_hash) {
+            Some(old_field) => {
+                if !type_compatible(
+                    &new_type_table,
+                    new_field.type_index,
+                    &old_type_table,
+                    old_field.type_index,
+                ) {
+                    return false;
+                }
+            }
+            None => {}
+        }
+    }
+    true
+
+    // // TODO: Implement supported sub-type relations
+
+    // // Identical types
+    // let old_descriptor = old_type.as_blob();
+    // let new_descriptor = new_type.as_blob();
+    // let old_size = old_descriptor.len().as_usize();
+    // let new_size = new_descriptor.len().as_usize();
+    // if old_size != new_size {
+    //     return false;
+    // }
+
+    // assert_eq!(old_size, new_size);
+    // let comparison = memcmp(
+    //     old_descriptor.payload_const() as *const c_void,
+    //     new_descriptor.payload_const() as *const c_void,
+    //     old_size,
+    // );
+
+    // comparison == 0
 }
+
+// pub unsafe fn print_type_descriptor(value: Value) {
+//     let descriptor = value.as_blob();
+//     let size = descriptor.len().as_usize();
+//     println!(100, "OUTPUT {}", size);
+//     for index in 0..size {
+//         println!(100, "{:#x} ", *descriptor.payload_const().add(index));
+//     }
+//     println!(100, "---");
+// }
diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 31f7b8f52a7..359c8e5fe68 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -6787,8 +6787,9 @@ module Stabilization = struct
     
   let save_stable_actor env = E.call_import env "rts" "save_stable_actor"
 
-  let register_stable_type env actor_type = 
-    Blob.lit env "" ^^
+  let register_stable_type env actor_type =
+    let type_descriptor = Persistence.encode_stable_type actor_type in
+    Blob.lit env type_descriptor ^^
     E.call_import env "rts" "register_stable_type"
 
   let create_actor env actor_type get_field_value =
diff --git a/src/codegen/persistence.ml b/src/codegen/persistence.ml
new file mode 100644
index 00000000000..1889e442162
--- /dev/null
+++ b/src/codegen/persistence.ml
@@ -0,0 +1,116 @@
+(* Support for orthogonal persistence using stable heap and in-place memory upgrades 
+   without serialization or deserialization to secondary stable memory. *)
+
+(*
+  // The first type denotes the stable actor object.
+  // Non-primitive types are referred to by a positive index according to the type table.
+  // Primitive types are encoded by negative indices.
+  // All numbers (type indices etc.) are encoded as little endian i32.
+   ::= length:i32 ()^length
+   ::= 
+   ::= 1l field_list
+   ::= length:i32 ()^length
+   ::= label_hash:i32 type_index:i32
+  
+  Predefined primitive type indices:
+  Nat -1l
+*)
+
+open Mo_types
+
+let encode_i32 number =
+  let buffer = Buffer.create 4 in
+  Buffer.add_int32_le buffer number;
+  Buffer.contents buffer
+
+let list_to_string list = List.fold_left (^) "" list
+
+module TypeTable = struct
+  let empty = []
+
+  let length table = List.length table
+
+  let index_of table typ =
+    match Lib.List.index_of typ table with
+    | Some index -> index
+    | None -> assert false
+
+  let contains_type table typ =
+    match List.find_opt (fun entry -> entry = typ) table with
+    | Some _ -> true
+    | None -> false
+
+  let add_type table typ =
+    let open Type in
+    match typ with
+    | Prim _ -> table
+    | _ when contains_type table typ -> table
+    | _ -> 
+      Printf.printf "ADD TYPE %s\n" (Type.string_of_typ typ);
+      List.append table [typ]
+end
+
+let rec collect_type table typ =
+  let table = TypeTable.add_type table typ in
+  let open Type in
+  match typ with
+  | Prim _ -> table
+  | Obj (Object, field_list) ->
+    let field_types = List.map (fun field -> field.typ) field_list in
+    collect_types table field_types
+  | _ -> 
+    Printf.printf "ERROR TYPE %s\n" (Type.string_of_typ typ);
+    assert false
+
+and collect_types table type_list =
+  match type_list with
+  | [] -> table
+  | first::remainder -> collect_types (collect_type table first) remainder
+  
+let encode_list encode_element list = 
+  let length = Int32.of_int (List.length list) in
+  let elements = List.map encode_element list in
+  encode_i32 length ^ list_to_string elements
+
+let type_index table typ =
+  let open Type in
+  match typ with
+  | Prim Nat -> -1l
+  | Prim _ -> assert false
+  | _ -> Int32.of_int (TypeTable.index_of table typ)
+
+let encode_field table field =
+  let open Type in
+  let field_hash = Hash.hash field.lab in
+  encode_i32 field_hash ^ 
+  encode_i32 (type_index table field.typ)
+  
+let encode_complex_type table typ =
+  let open Type in
+  match typ with
+  | Obj (Object, field_list) -> 
+    encode_i32 1l ^ 
+    encode_list (encode_field table) field_list
+  | _ -> assert false
+
+let encode_type_table table =
+  encode_list (encode_complex_type table) table
+
+let unwrap_optional typ =
+  let open Type in
+  match typ with
+  | Opt inner_type -> inner_type
+  | _ -> assert false
+
+(* Encode the stable type to enable a memory compatibility check on upgrade. *)
+(* See `persistence::compatibility` in the runtime system for the encoding format. *)
+let encode_stable_type (stable_type: Type.typ) : string =
+  let open Type in 
+  match stable_type with
+  | Obj (Memory, field_list) -> 
+      let unwrap_field field = {lab = field.lab; typ = unwrap_optional field.typ; depr = field.depr} in
+      let stable_fields = List.map unwrap_field field_list in
+      let stable_actor = Obj (Object, stable_fields) in
+      let table = collect_type TypeTable.empty stable_actor in
+      encode_type_table table
+  | _ -> assert false

From 5bb8424754fd1f9fb3b5a2168e2de9d5c1488626 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 28 Aug 2023 16:59:46 +0200
Subject: [PATCH 065/260] Add sub-type cache

---
 rts/motoko-rts/src/mem_utils.rs               |   4 +
 rts/motoko-rts/src/persistence.rs             |   2 +-
 .../src/persistence/compatibility.rs          | 132 ++++++++++++------
 3 files changed, 95 insertions(+), 43 deletions(-)

diff --git a/rts/motoko-rts/src/mem_utils.rs b/rts/motoko-rts/src/mem_utils.rs
index 9cdab95bca7..b71d1b8d253 100644
--- a/rts/motoko-rts/src/mem_utils.rs
+++ b/rts/motoko-rts/src/mem_utils.rs
@@ -11,3 +11,7 @@ pub(crate) unsafe fn memcpy_bytes(to: usize, from: usize, n: Bytes) {
 pub(crate) unsafe fn memzero(to: usize, n: Words) {
     libc::memset(to as *mut _, 0, n.to_bytes().as_usize());
 }
+
+pub(crate) unsafe fn memzero_bytes(to: usize, n: Bytes) {
+    libc::memset(to as *mut _, 0, n.as_usize());
+}
diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index a43fa180264..f19c393a02a 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -184,7 +184,7 @@ pub unsafe fn register_stable_type(mem: &mut M, new_type: Value) {
     assert_eq!(new_type.tag(), TAG_BLOB);
     let metadata = PersistentMetadata::get();
     let old_type = (*metadata).stable_type.forward_if_possible();
-    if old_type != DEFAULT_VALUE && !memory_compatible(old_type, new_type) {
+    if old_type != DEFAULT_VALUE && !memory_compatible(mem, old_type, new_type) {
         panic!("Memory-incompatible program upgrade");
     }
     let location = &mut (*metadata).stable_type as *mut Value;
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index d35da8b5844..53dbbf9e860 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -45,7 +45,11 @@
 //! ------- | --------
 //! Nat     | -1l
 
-use crate::types::{size_of, Value};
+use crate::{
+    mem_utils::memzero_bytes,
+    memory::{alloc_blob, Memory},
+    types::{size_of, Bytes, Value},
+};
 
 const OBJECT_ENCODING_TAG: i32 = 1;
 
@@ -168,7 +172,58 @@ struct Field {
     type_index: i32,
 }
 
-fn type_compatible(
+/// To break recursion and optimize repeated checks
+struct SubTypeCache {
+    bitmap: *mut u8,
+    count_new_types: usize,
+    count_old_types: usize,
+}
+
+impl SubTypeCache {
+    pub unsafe fn new(
+        mem: &mut M,
+        new_types: &TypeTable,
+        old_types: &TypeTable,
+    ) -> SubTypeCache {
+        let count_new_types = new_types.count_types();
+        let count_old_types = old_types.count_types();
+        let bit_size = count_new_types * count_old_types;
+        let byte_size = Bytes((bit_size as u32 + u8::BITS - 1) / u8::BITS);
+        let blob = alloc_blob(mem, byte_size);
+        let bitmap = blob.as_blob_mut().payload_addr();
+        memzero_bytes(bitmap as usize, byte_size);
+        SubTypeCache {
+            bitmap,
+            count_new_types,
+            count_old_types,
+        }
+    }
+
+    pub unsafe fn visited(&self, new_type_index: i32, old_type_index: i32) -> bool {
+        let (byte, bit_index) = self.position(new_type_index, old_type_index);
+        (*byte >> bit_index) & 0b1 != 0
+    }
+
+    pub unsafe fn visit(&mut self, new_type_index: i32, old_type_index: i32) {
+        let (byte, bit_index) = self.position(new_type_index, old_type_index);
+        *byte |= 0b1 << bit_index;
+    }
+
+    unsafe fn position(&self, new_type_index: i32, old_type_index: i32) -> (*mut u8, usize) {
+        assert!(new_type_index >= 0);
+        assert!(old_type_index >= 0);
+        assert!((new_type_index as usize) < self.count_new_types);
+        assert!((old_type_index as usize) < self.count_old_types);
+        let index = new_type_index as usize * self.count_old_types + old_type_index as usize;
+        let byte_index = index / u8::BITS as usize;
+        let bit_index = index % u8::BITS as usize;
+        let byte = self.bitmap.add(byte_index);
+        (byte, bit_index)
+    }
+}
+
+unsafe fn type_compatible(
+    cache: &mut SubTypeCache,
     _new_type_table: &TypeTable,
     new_type_index: i32,
     _old_type_table: &TypeTable,
@@ -177,30 +232,41 @@ fn type_compatible(
     unsafe {
         println!(100, "COMPATIBLE {new_type_index} {old_type_index}");
     }
-    if new_type_index < 0 {
+    if new_type_index >= 0 && old_type_index >= 0 {
+        if cache.visited(new_type_index, old_type_index) {
+            return true;
+        }
+        cache.visit(new_type_index, old_type_index);
+
+        // TODO: Implement check
+        unimplemented!()
+    } else if new_type_index < 0 {
         new_type_index == old_type_index
     } else {
         false
     }
 }
 
-// TODO: Unit test this funcionality
-
-// TODO: Use visited table for handling recursion and avoiding repeated type checks.
-
-/// Test whether the new stable type complies with the existing old stable type.
-/// Both arguments point to blobs encoding a stable actor type.
-pub unsafe fn memory_compatible(old_type: Value, new_type: Value) -> bool {
-    let new_type_table = TypeTable::new(new_type);
-    let old_type_table = TypeTable::new(old_type);
+unsafe fn compatible_actor_fields(
+    mem: &mut M,
+    new_type_table: TypeTable,
+    old_type_table: TypeTable,
+) -> bool {
+    let mut cache = SubTypeCache::new(mem, &new_type_table, &old_type_table);
     let new_actor = new_type_table.get_actor();
     let old_actor = old_type_table.get_actor();
     for new_field_index in 0..new_actor.count_fields() {
         let new_field = new_actor.get_field(new_field_index);
-        println!(100, "CHECK ACTOR FIELD {} {}", new_field.label_hash, old_actor.find_field(new_field.label_hash).is_some());
+        println!(
+            100,
+            "CHECK ACTOR FIELD {} {}",
+            new_field.label_hash,
+            old_actor.find_field(new_field.label_hash).is_some()
+        );
         match old_actor.find_field(new_field.label_hash) {
             Some(old_field) => {
                 if !type_compatible(
+                    &mut cache,
                     &new_type_table,
                     new_field.type_index,
                     &old_type_table,
@@ -213,34 +279,16 @@ pub unsafe fn memory_compatible(old_type: Value, new_type: Value) -> bool {
         }
     }
     true
-
-    // // TODO: Implement supported sub-type relations
-
-    // // Identical types
-    // let old_descriptor = old_type.as_blob();
-    // let new_descriptor = new_type.as_blob();
-    // let old_size = old_descriptor.len().as_usize();
-    // let new_size = new_descriptor.len().as_usize();
-    // if old_size != new_size {
-    //     return false;
-    // }
-
-    // assert_eq!(old_size, new_size);
-    // let comparison = memcmp(
-    //     old_descriptor.payload_const() as *const c_void,
-    //     new_descriptor.payload_const() as *const c_void,
-    //     old_size,
-    // );
-
-    // comparison == 0
 }
 
-// pub unsafe fn print_type_descriptor(value: Value) {
-//     let descriptor = value.as_blob();
-//     let size = descriptor.len().as_usize();
-//     println!(100, "OUTPUT {}", size);
-//     for index in 0..size {
-//         println!(100, "{:#x} ", *descriptor.payload_const().add(index));
-//     }
-//     println!(100, "---");
-// }
+// TODO: Unit test this funcionality
+
+// TODO: Use visited table for handling recursion and avoiding repeated type checks.
+
+/// Test whether the new stable type complies with the existing old stable type.
+/// Both arguments point to blobs encoding a stable actor type.
+pub unsafe fn memory_compatible(mem: &mut M, old_type: Value, new_type: Value) -> bool {
+    let new_type_table = TypeTable::new(new_type);
+    let old_type_table = TypeTable::new(old_type);
+    compatible_actor_fields(mem, new_type_table, old_type_table)
+}

From 589542e94c49ff019a2928b6a9372724e184be6d Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Mon, 28 Aug 2023 17:06:54 +0200
Subject: [PATCH 066/260] Code refactoring

---
 .../src/persistence/compatibility.rs          | 27 +++++++------------
 src/codegen/persistence.ml                    |  6 ++---
 2 files changed, 11 insertions(+), 22 deletions(-)

diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index 53dbbf9e860..d5cc1f3992f 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -172,19 +172,21 @@ struct Field {
     type_index: i32,
 }
 
-/// To break recursion and optimize repeated checks
-struct SubTypeCache {
+/// Cache for remembering previous type compatibility checks.
+/// Necessary to avoid infinite loops on type recursion.
+/// Helpful to optimize repeated checks.
+struct TypeCheckCache {
     bitmap: *mut u8,
     count_new_types: usize,
     count_old_types: usize,
 }
 
-impl SubTypeCache {
+impl TypeCheckCache {
     pub unsafe fn new(
         mem: &mut M,
         new_types: &TypeTable,
         old_types: &TypeTable,
-    ) -> SubTypeCache {
+    ) -> TypeCheckCache {
         let count_new_types = new_types.count_types();
         let count_old_types = old_types.count_types();
         let bit_size = count_new_types * count_old_types;
@@ -192,7 +194,7 @@ impl SubTypeCache {
         let blob = alloc_blob(mem, byte_size);
         let bitmap = blob.as_blob_mut().payload_addr();
         memzero_bytes(bitmap as usize, byte_size);
-        SubTypeCache {
+        TypeCheckCache {
             bitmap,
             count_new_types,
             count_old_types,
@@ -223,15 +225,12 @@ impl SubTypeCache {
 }
 
 unsafe fn type_compatible(
-    cache: &mut SubTypeCache,
+    cache: &mut TypeCheckCache,
     _new_type_table: &TypeTable,
     new_type_index: i32,
     _old_type_table: &TypeTable,
     old_type_index: i32,
 ) -> bool {
-    unsafe {
-        println!(100, "COMPATIBLE {new_type_index} {old_type_index}");
-    }
     if new_type_index >= 0 && old_type_index >= 0 {
         if cache.visited(new_type_index, old_type_index) {
             return true;
@@ -252,17 +251,11 @@ unsafe fn compatible_actor_fields(
     new_type_table: TypeTable,
     old_type_table: TypeTable,
 ) -> bool {
-    let mut cache = SubTypeCache::new(mem, &new_type_table, &old_type_table);
+    let mut cache = TypeCheckCache::new(mem, &new_type_table, &old_type_table);
     let new_actor = new_type_table.get_actor();
     let old_actor = old_type_table.get_actor();
     for new_field_index in 0..new_actor.count_fields() {
         let new_field = new_actor.get_field(new_field_index);
-        println!(
-            100,
-            "CHECK ACTOR FIELD {} {}",
-            new_field.label_hash,
-            old_actor.find_field(new_field.label_hash).is_some()
-        );
         match old_actor.find_field(new_field.label_hash) {
             Some(old_field) => {
                 if !type_compatible(
@@ -283,8 +276,6 @@ unsafe fn compatible_actor_fields(
 
 // TODO: Unit test this funcionality
 
-// TODO: Use visited table for handling recursion and avoiding repeated type checks.
-
 /// Test whether the new stable type complies with the existing old stable type.
 /// Both arguments point to blobs encoding a stable actor type.
 pub unsafe fn memory_compatible(mem: &mut M, old_type: Value, new_type: Value) -> bool {
diff --git a/src/codegen/persistence.ml b/src/codegen/persistence.ml
index 1889e442162..a05f320a8a4 100644
--- a/src/codegen/persistence.ml
+++ b/src/codegen/persistence.ml
@@ -45,9 +45,7 @@ module TypeTable = struct
     match typ with
     | Prim _ -> table
     | _ when contains_type table typ -> table
-    | _ -> 
-      Printf.printf "ADD TYPE %s\n" (Type.string_of_typ typ);
-      List.append table [typ]
+    | _ -> List.append table [typ]
 end
 
 let rec collect_type table typ =
@@ -59,7 +57,7 @@ let rec collect_type table typ =
     let field_types = List.map (fun field -> field.typ) field_list in
     collect_types table field_types
   | _ -> 
-    Printf.printf "ERROR TYPE %s\n" (Type.string_of_typ typ);
+    Printf.printf "UNSUPPORTED PERSISTENT TYPE %s\n" (Type.string_of_typ typ);
     assert false
 
 and collect_types table type_list =

From be38c4e4d600d12d481cb251db7d66d6e023f45d Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 29 Aug 2023 09:27:03 +0200
Subject: [PATCH 067/260] Add test case

---
 test/run-drun/add-remove-stable-fields.drun   | 19 +++++++++++++
 .../add-remove-stable-fields/version0.mo      | 13 +++++++++
 .../add-remove-stable-fields/version1.mo      | 16 +++++++++++
 .../add-remove-stable-fields/version2.mo      | 13 +++++++++
 .../ok/add-remove-stable-fields.drun.ok       | 27 +++++++++++++++++++
 5 files changed, 88 insertions(+)
 create mode 100644 test/run-drun/add-remove-stable-fields.drun
 create mode 100644 test/run-drun/add-remove-stable-fields/version0.mo
 create mode 100644 test/run-drun/add-remove-stable-fields/version1.mo
 create mode 100644 test/run-drun/add-remove-stable-fields/version2.mo
 create mode 100644 test/run-drun/ok/add-remove-stable-fields.drun.ok

diff --git a/test/run-drun/add-remove-stable-fields.drun b/test/run-drun/add-remove-stable-fields.drun
new file mode 100644
index 00000000000..fa59f82bbc3
--- /dev/null
+++ b/test/run-drun/add-remove-stable-fields.drun
@@ -0,0 +1,19 @@
+# SKIP ic-ref-run
+install $ID add-remove-stable-fields/version0.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID add-remove-stable-fields/version0.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID add-remove-stable-fields/version1.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID add-remove-stable-fields/version1.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID add-remove-stable-fields/version2.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID add-remove-stable-fields/version2.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
diff --git a/test/run-drun/add-remove-stable-fields/version0.mo b/test/run-drun/add-remove-stable-fields/version0.mo
new file mode 100644
index 00000000000..585ee7f7630
--- /dev/null
+++ b/test/run-drun/add-remove-stable-fields/version0.mo
@@ -0,0 +1,13 @@
+import Prim "mo:prim";
+
+actor {
+    stable var firstValue = 0;
+
+    public func increase() : async () {
+        firstValue += 0;
+    };
+
+    public func show() : async () {
+        Prim.debugPrint("firstValue " # debug_show (firstValue));
+    };
+};
diff --git a/test/run-drun/add-remove-stable-fields/version1.mo b/test/run-drun/add-remove-stable-fields/version1.mo
new file mode 100644
index 00000000000..c11b3762b66
--- /dev/null
+++ b/test/run-drun/add-remove-stable-fields/version1.mo
@@ -0,0 +1,16 @@
+import Prim "mo:prim";
+
+actor {
+    stable var firstValue = 0;
+    stable var secondValue = 0;
+
+    public func increase() : async () {
+        firstValue += 1;
+        secondValue += 1;
+    };
+
+    public func show() : async () {
+        Prim.debugPrint("firstValue=" # debug_show (firstValue));
+        Prim.debugPrint("secondValue=" # debug_show (secondValue));
+    };
+};
diff --git a/test/run-drun/add-remove-stable-fields/version2.mo b/test/run-drun/add-remove-stable-fields/version2.mo
new file mode 100644
index 00000000000..55274e4b171
--- /dev/null
+++ b/test/run-drun/add-remove-stable-fields/version2.mo
@@ -0,0 +1,13 @@
+import Prim "mo:prim";
+
+actor {
+    stable var secondValue = 0;
+
+    public func increase() : async () {
+        secondValue += 1;
+    };
+
+    public func show() : async () {
+        Prim.debugPrint("secondValue=" # debug_show (secondValue));
+    };
+};
diff --git a/test/run-drun/ok/add-remove-stable-fields.drun.ok b/test/run-drun/ok/add-remove-stable-fields.drun.ok
new file mode 100644
index 00000000000..39bb51dad33
--- /dev/null
+++ b/test/run-drun/ok/add-remove-stable-fields.drun.ok
@@ -0,0 +1,27 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: firstValue 0
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: firstValue 0
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: firstValue=1
+debug.print: secondValue=1
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: firstValue=2
+debug.print: secondValue=2
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: secondValue=3
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: secondValue=4
+ingress Completed: Reply: 0x4449444c0000

From eb0366d9a9557f6030c0eebf7ce2ee82813f5428 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 29 Aug 2023 11:30:23 +0200
Subject: [PATCH 068/260] Add unit test

---
 rts/motoko-rts-tests/src/main.rs              |   3 +
 rts/motoko-rts-tests/src/persistence.rs       |  21 ++
 .../src/persistence/compatibility.rs          | 257 ++++++++++++++++++
 rts/motoko-rts/src/lib.rs                     |   1 -
 rts/motoko-rts/src/persistence.rs             |  16 +-
 5 files changed, 292 insertions(+), 6 deletions(-)
 create mode 100644 rts/motoko-rts-tests/src/persistence.rs
 create mode 100644 rts/motoko-rts-tests/src/persistence/compatibility.rs

diff --git a/rts/motoko-rts-tests/src/main.rs b/rts/motoko-rts-tests/src/main.rs
index 3b2e92fadbc..ec4ea2ae139 100644
--- a/rts/motoko-rts-tests/src/main.rs
+++ b/rts/motoko-rts-tests/src/main.rs
@@ -7,6 +7,7 @@ mod crc32;
 mod gc;
 mod leb128;
 mod memory;
+mod persistence;
 mod principal_id;
 mod stream;
 mod text;
@@ -21,6 +22,8 @@ fn main() {
     }
 
     unsafe {
+        persistence::test();
+
         test_read_write_64_bit();
         bigint::test();
         bitrel::test();
diff --git a/rts/motoko-rts-tests/src/persistence.rs b/rts/motoko-rts-tests/src/persistence.rs
new file mode 100644
index 00000000000..19c64c672d9
--- /dev/null
+++ b/rts/motoko-rts-tests/src/persistence.rs
@@ -0,0 +1,21 @@
+mod compatibility;
+
+use motoko_rts::persistence::allocate_null;
+use motoko_rts::types::TAG_NULL;
+
+use crate::memory::{initialize_test_memory, reset_test_memory};
+
+pub fn test() {
+    println!("Testing orthogonal persistence ...");
+    unsafe {
+        compatibility::test();
+        test_null_allocation();
+    }
+}
+
+unsafe fn test_null_allocation() {
+    let mut heap = initialize_test_memory();
+    let value = allocate_null(&mut heap);
+    assert_eq!(value.tag(), TAG_NULL);
+    reset_test_memory();
+}
diff --git a/rts/motoko-rts-tests/src/persistence/compatibility.rs b/rts/motoko-rts-tests/src/persistence/compatibility.rs
new file mode 100644
index 00000000000..02e5b0876ae
--- /dev/null
+++ b/rts/motoko-rts-tests/src/persistence/compatibility.rs
@@ -0,0 +1,257 @@
+use motoko_rts::memory::{alloc_blob, Memory};
+use motoko_rts::persistence::compatibility::memory_compatible;
+use motoko_rts::types::{Bytes, Value};
+use std::hash::Hasher;
+use std::{collections::hash_map::DefaultHasher, hash::Hash};
+
+use crate::memory::{initialize_test_memory, reset_test_memory, TestMemory};
+
+struct BinaryData {
+    byte_sequence: Vec,
+}
+
+impl BinaryData {
+    fn new() -> BinaryData {
+        BinaryData {
+            byte_sequence: vec![],
+        }
+    }
+
+    fn write_i32(&mut self, value: i32) {
+        for byte in value.to_le_bytes() {
+            self.byte_sequence.push(byte);
+        }
+    }
+
+    fn write_hash(&mut self, value: &str) {
+        let mut hasher = DefaultHasher::new();
+        value.hash(&mut hasher);
+        self.write_i32(hasher.finish() as i32);
+    }
+
+    unsafe fn make_blob(&self, mem: &mut M) -> Value {
+        let length = Bytes(self.byte_sequence.len() as u32);
+        let value = alloc_blob(mem, length);
+        let target = value.as_blob_mut();
+
+        for index in 0..length.as_usize() {
+            let byte = target.payload_addr().add(index);
+            *byte = *self.byte_sequence.get(index).unwrap();
+        }
+        value
+    }
+}
+
+#[derive(PartialEq, Clone)]
+enum Type {
+    Nat,
+    Object(ObjectType),
+}
+
+impl Type {
+    fn is_primitive(&self) -> bool {
+        match &self {
+            Self::Nat => true,
+            Self::Object(_) => false,
+        }
+    }
+
+    fn inner_types(&self) -> Vec {
+        match &self {
+            Self::Nat => vec![],
+            Self::Object(object_type) => object_type.inner_types(),
+        }
+    }
+
+    fn serialize(&self, output: &mut BinaryData, table: &TypeTable) {
+        match &self {
+            Self::Nat => unreachable!(),
+            Self::Object(object_type) => object_type.serialize(output, table),
+        }
+    }
+
+    fn type_id(&self, table: &TypeTable) -> i32 {
+        const NAT_TYPE_ID: i32 = -1;
+        match &self {
+            Self::Nat => NAT_TYPE_ID,
+            Self::Object(_) => table.index_of(self),
+        }
+    }
+}
+
+#[derive(PartialEq, Clone)]
+struct Field {
+    name: String,
+    field_type: Type,
+}
+
+impl Field {
+    fn serialize(&self, output: &mut BinaryData, table: &TypeTable) {
+        output.write_hash(&self.name);
+        output.write_i32(self.field_type.type_id(table));
+    }
+}
+
+#[derive(PartialEq, Clone)]
+struct ObjectType {
+    fields: Vec,
+}
+
+impl ObjectType {
+    fn inner_types(&self) -> Vec {
+        self.fields
+            .iter()
+            .map(|field| field.field_type.clone())
+            .collect()
+    }
+
+    fn serialize(&self, output: &mut BinaryData, table: &TypeTable) {
+        const OBJECT_TAG: i32 = 1;
+        output.write_i32(OBJECT_TAG);
+        output.write_i32(self.fields.len() as i32);
+        for field in &self.fields {
+            field.serialize(output, table);
+        }
+    }
+}
+
+struct TypeTable {
+    types: Vec,
+}
+
+impl TypeTable {
+    fn new(actor_type: Type) -> TypeTable {
+        let mut table = TypeTable { types: vec![] };
+        table.collect_types(actor_type);
+        table
+    }
+
+    fn collect_types(&mut self, current_type: Type) {
+        if !current_type.is_primitive() && !self.types.contains(¤t_type) {
+            self.types.push(current_type.clone());
+            for inner_type in current_type.inner_types() {
+                self.collect_types(inner_type);
+            }
+        }
+    }
+
+    fn index_of(&self, search_type: &Type) -> i32 {
+        assert!(!search_type.is_primitive());
+        for index in 0..self.types.len() {
+            if self.types.get(index).unwrap() == search_type {
+                return index as i32;
+            }
+        }
+        unreachable!()
+    }
+
+    fn serialize(&self) -> BinaryData {
+        let mut output = BinaryData::new();
+        output.write_i32(self.types.len() as i32);
+        for current_type in &self.types {
+            current_type.serialize(&mut output, &self);
+        }
+        output
+    }
+}
+
+unsafe fn build(mem: &mut M, actor_type: Type) -> Value {
+    TypeTable::new(actor_type).serialize().make_blob(mem)
+}
+
+unsafe fn is_compatible(mem: &mut M, old_type: Type, new_type: Type) -> bool {
+    let old_type_blob = build(mem, old_type);
+    let new_type_blob = build(mem, new_type);
+    memory_compatible(mem, old_type_blob, new_type_blob)
+}
+
+pub unsafe fn test() {
+    println!("  Testing memory compatibility...");
+    let mut heap = initialize_test_memory();
+    test_sucessful_cases(&mut heap);
+    test_failing_cases(&mut heap);
+    reset_test_memory();
+}
+
+unsafe fn test_sucessful_cases(heap: &mut TestMemory) {
+    test_empty_actor(heap);
+    test_reordered_actor_fields(heap);
+    test_removed_actor_fields(heap);
+    test_added_actor_fields(heap);
+}
+
+unsafe fn test_empty_actor(heap: &mut TestMemory) {
+    let old_type = Type::Object(ObjectType { fields: vec![] });
+    let new_type = Type::Object(ObjectType { fields: vec![] });
+    assert!(is_compatible(heap, old_type, new_type));
+}
+
+unsafe fn test_reordered_actor_fields(heap: &mut TestMemory) {
+    let field1 = Field {
+        name: String::from("Field1"),
+        field_type: Type::Nat,
+    };
+    let field2 = Field {
+        name: String::from("Field2"),
+        field_type: Type::Nat,
+    };
+
+    let old_type = Type::Object(ObjectType {
+        fields: vec![field1.clone(), field2.clone()],
+    });
+    let new_type = Type::Object(ObjectType {
+        fields: vec![field2.clone(), field1.clone()],
+    });
+
+    assert!(is_compatible(heap, old_type, new_type));
+}
+
+unsafe fn test_removed_actor_fields(heap: &mut TestMemory) {
+    let field1 = Field {
+        name: String::from("Field1"),
+        field_type: Type::Nat,
+    };
+    let field2 = Field {
+        name: String::from("Field2"),
+        field_type: Type::Nat,
+    };
+    let field3 = Field {
+        name: String::from("Field3"),
+        field_type: Type::Nat,
+    };
+
+    let old_type = Type::Object(ObjectType {
+        fields: vec![field1.clone(), field2.clone(), field3.clone()],
+    });
+    let new_type = Type::Object(ObjectType {
+        fields: vec![field2.clone()],
+    });
+
+    assert!(is_compatible(heap, old_type, new_type));
+}
+
+unsafe fn test_added_actor_fields(heap: &mut TestMemory) {
+    let field1 = Field {
+        name: String::from("Field1"),
+        field_type: Type::Nat,
+    };
+    let field2 = Field {
+        name: String::from("Field2"),
+        field_type: Type::Nat,
+    };
+    let field3 = Field {
+        name: String::from("Field3"),
+        field_type: Type::Nat,
+    };
+
+    let old_type = Type::Object(ObjectType {
+        fields: vec![field2.clone()],
+    });
+    let new_type = Type::Object(ObjectType {
+        fields: vec![field1.clone(), field2.clone(), field3.clone()],
+    });
+
+    assert!(is_compatible(heap, old_type, new_type));
+}
+
+unsafe fn test_failing_cases(_heap: &mut TestMemory) {}
diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs
index 0dcde17bb0a..f0b9886aa05 100644
--- a/rts/motoko-rts/src/lib.rs
+++ b/rts/motoko-rts/src/lib.rs
@@ -31,7 +31,6 @@ mod idl;
 pub mod leb128;
 mod mem_utils;
 pub mod memory;
-#[cfg(feature = "ic")]
 pub mod persistence;
 pub mod principal_id;
 mod static_checks;
diff --git a/rts/motoko-rts/src/persistence.rs b/rts/motoko-rts/src/persistence.rs
index f19c393a02a..b9960baa6c7 100644
--- a/rts/motoko-rts/src/persistence.rs
+++ b/rts/motoko-rts/src/persistence.rs
@@ -2,22 +2,24 @@
 //!
 //! Persistent metadata table, located at 4MB, in the static partition space.
 
-mod compatibility;
+pub mod compatibility;
 
 use motoko_rts_macros::ic_mem_fn;
 
 use crate::{
     barriers::{allocation_barrier, write_with_barrier},
-    gc::incremental::{IncrementalGC, State},
+    gc::incremental::State,
     memory::Memory,
     persistence::compatibility::memory_compatible,
     types::{size_of, Null, Value, TAG_BLOB, TAG_NULL},
 };
 
+#[cfg(feature = "ic")]
 const FINGERPRINT: [char; 32] = [
     'M', 'O', 'T', 'O', 'K', 'O', ' ', 'O', 'R', 'T', 'H', 'O', 'G', 'O', 'N', 'A', 'L', ' ', 'P',
     'E', 'R', 'S', 'I', 'S', 'T', 'E', 'N', 'C', 'E', ' ', '3', '2',
 ];
+#[cfg(feature = "ic")]
 const VERSION: usize = 1;
 /// The `Value` representation in the default-initialized Wasm memory.
 /// The GC ignores this value since it is a scalar representation.
@@ -52,7 +54,6 @@ const METATDATA_ADDRESS: usize = 4 * 1024 * 1024;
 const METADATA_RESERVE: usize = 128 * 1024;
 
 // TODO: Include partition table in reserved space.
-#[cfg(feature = "ic")]
 pub const HEAP_START: usize = METATDATA_ADDRESS + METADATA_RESERVE;
 
 const _: () = assert!(core::mem::size_of::() <= METADATA_RESERVE);
@@ -62,6 +63,7 @@ impl PersistentMetadata {
         METATDATA_ADDRESS as *mut Self
     }
 
+    #[cfg(feature = "ic")]
     unsafe fn is_initialized(self: *mut Self) -> bool {
         // Wasm memory is zero-initialized according to the Wasm specification.
         let initialized = (*self).version != 0;
@@ -75,6 +77,7 @@ impl PersistentMetadata {
         initialized
     }
 
+    #[cfg(feature = "ic")]
     unsafe fn check_version(self: *const Self) {
         if (*self).version != VERSION {
             panic!(
@@ -85,7 +88,9 @@ impl PersistentMetadata {
         }
     }
 
+    #[cfg(feature = "ic")]
     unsafe fn initialize(self: *mut Self, mem: &mut M) {
+        use crate::gc::incremental::IncrementalGC;
         debug_assert!(!self.is_initialized());
         (*self).fingerprint = FINGERPRINT;
         (*self).version = VERSION;
@@ -137,6 +142,7 @@ pub unsafe fn save_stable_actor(mem: &mut M, actor: Value) {
     write_with_barrier(mem, location, actor);
 }
 
+#[cfg(feature = "ic")]
 /// GC root pointer required for GC marking and updating.
 pub(crate) unsafe fn stable_actor_location() -> *mut Value {
     let metadata = PersistentMetadata::get();
@@ -165,9 +171,8 @@ pub unsafe extern "C" fn contains_field(actor: Value, field_hash: u32) -> bool {
     false
 }
 
-unsafe fn allocate_null(mem: &mut M) -> Value {
+pub unsafe fn allocate_null(mem: &mut M) -> Value {
     let value = mem.alloc_words(size_of::());
-    debug_assert!(value.get_ptr() >= HEAP_START);
     let null = value.get_ptr() as *mut Null;
     (*null).header.tag = TAG_NULL;
     (*null).header.init_forward(value);
@@ -192,6 +197,7 @@ pub unsafe fn register_stable_type(mem: &mut M, new_type: Value) {
 }
 
 /// GC root pointer required for GC marking and updating.
+#[cfg(feature = "ic")]
 pub(crate) unsafe fn stable_type_location() -> *mut Value {
     let metadata = PersistentMetadata::get();
     &mut (*metadata).stable_type as *mut Value

From 40e1f35d186b0646a9679516e93fb672737c0e39 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 29 Aug 2023 13:43:53 +0200
Subject: [PATCH 069/260] Unit test for recursive types

---
 .../src/persistence/compatibility.rs          | 199 +++++++++++-------
 .../src/persistence/compatibility.rs          | 105 ++++++---
 2 files changed, 201 insertions(+), 103 deletions(-)

diff --git a/rts/motoko-rts-tests/src/persistence/compatibility.rs b/rts/motoko-rts-tests/src/persistence/compatibility.rs
index 02e5b0876ae..44cb8b2de05 100644
--- a/rts/motoko-rts-tests/src/persistence/compatibility.rs
+++ b/rts/motoko-rts-tests/src/persistence/compatibility.rs
@@ -42,75 +42,55 @@ impl BinaryData {
     }
 }
 
-#[derive(PartialEq, Clone)]
+#[derive(Clone)]
 enum Type {
-    Nat,
     Object(ObjectType),
 }
 
 impl Type {
-    fn is_primitive(&self) -> bool {
+    fn serialize(&self, output: &mut BinaryData) {
         match &self {
-            Self::Nat => true,
-            Self::Object(_) => false,
-        }
-    }
-
-    fn inner_types(&self) -> Vec {
-        match &self {
-            Self::Nat => vec![],
-            Self::Object(object_type) => object_type.inner_types(),
+            Self::Object(object_type) => object_type.serialize(output),
         }
     }
+}
 
-    fn serialize(&self, output: &mut BinaryData, table: &TypeTable) {
-        match &self {
-            Self::Nat => unreachable!(),
-            Self::Object(object_type) => object_type.serialize(output, table),
-        }
-    }
+#[derive(Clone)]
+struct TypeReference {
+    index: i32,
+}
 
-    fn type_id(&self, table: &TypeTable) -> i32 {
-        const NAT_TYPE_ID: i32 = -1;
-        match &self {
-            Self::Nat => NAT_TYPE_ID,
-            Self::Object(_) => table.index_of(self),
-        }
+impl TypeReference {
+    fn nat() -> TypeReference {
+        TypeReference { index: -1 }
     }
 }
 
-#[derive(PartialEq, Clone)]
+#[derive(Clone)]
 struct Field {
     name: String,
-    field_type: Type,
+    field_type: TypeReference,
 }
 
 impl Field {
-    fn serialize(&self, output: &mut BinaryData, table: &TypeTable) {
+    fn serialize(&self, output: &mut BinaryData) {
         output.write_hash(&self.name);
-        output.write_i32(self.field_type.type_id(table));
+        output.write_i32(self.field_type.index);
     }
 }
 
-#[derive(PartialEq, Clone)]
+#[derive(Clone)]
 struct ObjectType {
     fields: Vec,
 }
 
 impl ObjectType {
-    fn inner_types(&self) -> Vec {
-        self.fields
-            .iter()
-            .map(|field| field.field_type.clone())
-            .collect()
-    }
-
-    fn serialize(&self, output: &mut BinaryData, table: &TypeTable) {
+    fn serialize(&self, output: &mut BinaryData) {
         const OBJECT_TAG: i32 = 1;
         output.write_i32(OBJECT_TAG);
         output.write_i32(self.fields.len() as i32);
         for field in &self.fields {
-            field.serialize(output, table);
+            field.serialize(output);
         }
     }
 }
@@ -120,51 +100,38 @@ struct TypeTable {
 }
 
 impl TypeTable {
-    fn new(actor_type: Type) -> TypeTable {
-        let mut table = TypeTable { types: vec![] };
-        table.collect_types(actor_type);
-        table
-    }
-
-    fn collect_types(&mut self, current_type: Type) {
-        if !current_type.is_primitive() && !self.types.contains(¤t_type) {
-            self.types.push(current_type.clone());
-            for inner_type in current_type.inner_types() {
-                self.collect_types(inner_type);
-            }
-        }
-    }
-
-    fn index_of(&self, search_type: &Type) -> i32 {
-        assert!(!search_type.is_primitive());
-        for index in 0..self.types.len() {
-            if self.types.get(index).unwrap() == search_type {
-                return index as i32;
-            }
-        }
-        unreachable!()
+    fn new(types: Vec) -> TypeTable {
+        TypeTable { types }
     }
 
     fn serialize(&self) -> BinaryData {
         let mut output = BinaryData::new();
         output.write_i32(self.types.len() as i32);
         for current_type in &self.types {
-            current_type.serialize(&mut output, &self);
+            current_type.serialize(&mut output);
         }
         output
     }
 }
 
-unsafe fn build(mem: &mut M, actor_type: Type) -> Value {
-    TypeTable::new(actor_type).serialize().make_blob(mem)
+unsafe fn build(mem: &mut M, types: Vec) -> Value {
+    TypeTable::new(types).serialize().make_blob(mem)
 }
 
-unsafe fn is_compatible(mem: &mut M, old_type: Type, new_type: Type) -> bool {
-    let old_type_blob = build(mem, old_type);
-    let new_type_blob = build(mem, new_type);
+unsafe fn are_compatible(
+    mem: &mut M,
+    old_types: Vec,
+    new_types: Vec,
+) -> bool {
+    let old_type_blob = build(mem, old_types);
+    let new_type_blob = build(mem, new_types);
     memory_compatible(mem, old_type_blob, new_type_blob)
 }
 
+unsafe fn is_compatible(mem: &mut M, old_type: Type, new_type: Type) -> bool {
+    are_compatible(mem, vec![old_type], vec![new_type])
+}
+
 pub unsafe fn test() {
     println!("  Testing memory compatibility...");
     let mut heap = initialize_test_memory();
@@ -178,6 +145,8 @@ unsafe fn test_sucessful_cases(heap: &mut TestMemory) {
     test_reordered_actor_fields(heap);
     test_removed_actor_fields(heap);
     test_added_actor_fields(heap);
+    test_direct_recursive_type(heap);
+    test_indirect_recursive_type(heap);
 }
 
 unsafe fn test_empty_actor(heap: &mut TestMemory) {
@@ -189,11 +158,11 @@ unsafe fn test_empty_actor(heap: &mut TestMemory) {
 unsafe fn test_reordered_actor_fields(heap: &mut TestMemory) {
     let field1 = Field {
         name: String::from("Field1"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
     let field2 = Field {
         name: String::from("Field2"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
 
     let old_type = Type::Object(ObjectType {
@@ -209,15 +178,15 @@ unsafe fn test_reordered_actor_fields(heap: &mut TestMemory) {
 unsafe fn test_removed_actor_fields(heap: &mut TestMemory) {
     let field1 = Field {
         name: String::from("Field1"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
     let field2 = Field {
         name: String::from("Field2"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
     let field3 = Field {
         name: String::from("Field3"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
 
     let old_type = Type::Object(ObjectType {
@@ -233,15 +202,15 @@ unsafe fn test_removed_actor_fields(heap: &mut TestMemory) {
 unsafe fn test_added_actor_fields(heap: &mut TestMemory) {
     let field1 = Field {
         name: String::from("Field1"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
     let field2 = Field {
         name: String::from("Field2"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
     let field3 = Field {
         name: String::from("Field3"),
-        field_type: Type::Nat,
+        field_type: TypeReference::nat(),
     };
 
     let old_type = Type::Object(ObjectType {
@@ -254,4 +223,82 @@ unsafe fn test_added_actor_fields(heap: &mut TestMemory) {
     assert!(is_compatible(heap, old_type, new_type));
 }
 
-unsafe fn test_failing_cases(_heap: &mut TestMemory) {}
+unsafe fn test_direct_recursive_type(heap: &mut TestMemory) {
+    let actor_field = Field {
+        name: String::from("ActorField"),
+        field_type: TypeReference { index: 1 },
+    };
+    let actor_type = Type::Object(ObjectType {
+        fields: vec![actor_field],
+    });
+    let recursive_field = Field {
+        name: String::from("RecursiveField"),
+        field_type: TypeReference { index: 1 },
+    };
+    let recursive_type = Type::Object(ObjectType {
+        fields: vec![recursive_field],
+    });
+    let types = vec![actor_type, recursive_type];
+    assert!(are_compatible(heap, types.clone(), types.clone()));
+}
+
+unsafe fn test_indirect_recursive_type(heap: &mut TestMemory) {
+    let actor_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let first_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("Field1"),
+            field_type: TypeReference { index: 2 },
+        }],
+    });
+    let second_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("Field2"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let types = vec![actor_type, first_type, second_type];
+    assert!(are_compatible(heap, types.clone(), types.clone()));
+}
+
+unsafe fn test_failing_cases(heap: &mut TestMemory) {
+    test_recursion_mismatch(heap);
+}
+
+unsafe fn test_recursion_mismatch(heap: &mut TestMemory) {
+    let old_actor_field = Field {
+        name: String::from("ActorField"),
+        field_type: TypeReference { index: 1 },
+    };
+    let old_actor = Type::Object(ObjectType {
+        fields: vec![old_actor_field],
+    });
+    let recursive_field = Field {
+        name: String::from("Field"),
+        field_type: TypeReference { index: 1 },
+    };
+    let recursive_type = Type::Object(ObjectType {
+        fields: vec![recursive_field],
+    });
+    let new_actor_field = Field {
+        name: String::from("ActorField"),
+        field_type: TypeReference { index: 1 },
+    };
+    let new_actor = Type::Object(ObjectType {
+        fields: vec![new_actor_field],
+    });
+    let non_recursive_field = Field {
+        name: String::from("Field"),
+        field_type: TypeReference::nat(),
+    };
+    let non_recursive_type = Type::Object(ObjectType {
+        fields: vec![non_recursive_field],
+    });
+    let old_types = vec![old_actor, recursive_type];
+    let new_types = vec![new_actor, non_recursive_type];
+    assert!(!are_compatible(heap, old_types, new_types));
+}
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index d5cc1f3992f..a8a2a268bb4 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -74,11 +74,11 @@ impl EncodedData {
         *location
     }
 
-    unsafe fn sub_view(&self, offset: usize, length: usize) -> EncodedData {
-        assert!(offset + length <= self.size);
+    unsafe fn sub_view(&self, offset: usize) -> EncodedData {
+        assert!(offset <= self.size);
         EncodedData {
             words: self.words.add(offset),
-            size: length,
+            size: self.size - offset,
         }
     }
 }
@@ -105,16 +105,13 @@ impl TypeTable {
 
     unsafe fn get_type(&self, type_index: i32) -> Type {
         assert!(type_index <= self.count_types() as i32);
-        assert_eq!(type_index, 0); // TODO: Skip over other types
-        let start = 1;
-        let tag = self.data.read(start);
-        assert_eq!(tag, OBJECT_ENCODING_TAG);
-        let count_fields = self.data.read(start + 1);
-        assert!(count_fields >= 0);
-        let size = OBJECT_ENCODING_HEADER + count_fields as usize * FIELD_ENCODING_LENGTH;
-        let type_view = self.data.sub_view(start, size);
-        let object_type = ObjectType::new(type_view);
-        Type::Object(object_type)
+        let mut start = 1;
+        for _ in 0..type_index {
+            let type_view = self.data.sub_view(start);
+            start += Type::size(type_view);
+        }
+        let type_view = self.data.sub_view(start);
+        Type::get_type(type_view)
     }
 
     unsafe fn get_actor(&self) -> ObjectType {
@@ -129,6 +126,22 @@ enum Type {
     Object(ObjectType),
 }
 
+impl Type {
+    unsafe fn size(data: EncodedData) -> usize {
+        match Self::get_type(data) {
+            Self::Object(object_type) => object_type.size(),
+        }
+    }
+
+    unsafe fn get_type(data: EncodedData) -> Type {
+        let tag = data.read(0);
+        match tag {
+            OBJECT_ENCODING_TAG => Self::Object(ObjectType::new(data)),
+            _ => unimplemented!(),
+        }
+    }
+}
+
 struct ObjectType {
     data: EncodedData,
 }
@@ -139,6 +152,10 @@ impl ObjectType {
         ObjectType { data }
     }
 
+    unsafe fn size(&self) -> usize {
+        OBJECT_ENCODING_HEADER + self.count_fields() * FIELD_ENCODING_LENGTH
+    }
+
     unsafe fn count_fields(&self) -> usize {
         assert_eq!(self.data.read(0), OBJECT_ENCODING_TAG);
         let count = self.data.read(1);
@@ -224,25 +241,59 @@ impl TypeCheckCache {
     }
 }
 
+unsafe fn object_compatible(
+    cache: &mut TypeCheckCache,
+    new_type_table: &TypeTable,
+    new_object: &ObjectType,
+    old_type_table: &TypeTable,
+    old_object: &ObjectType,
+) -> bool {
+    for field_index in 0..new_object.count_fields() {
+        let new_field = new_object.get_field(field_index);
+        match old_object.find_field(new_field.label_hash) {
+            None => {
+                return false;
+            }
+            Some(old_field) => {
+                if !type_compatible(
+                    cache,
+                    new_type_table,
+                    new_field.type_index,
+                    old_type_table,
+                    old_field.type_index,
+                ) {
+                    return false;
+                }
+            }
+        }
+    }
+    true
+}
+
 unsafe fn type_compatible(
     cache: &mut TypeCheckCache,
-    _new_type_table: &TypeTable,
+    new_type_table: &TypeTable,
     new_type_index: i32,
-    _old_type_table: &TypeTable,
+    old_type_table: &TypeTable,
     old_type_index: i32,
 ) -> bool {
-    if new_type_index >= 0 && old_type_index >= 0 {
-        if cache.visited(new_type_index, old_type_index) {
-            return true;
-        }
-        cache.visit(new_type_index, old_type_index);
-
-        // TODO: Implement check
-        unimplemented!()
-    } else if new_type_index < 0 {
-        new_type_index == old_type_index
-    } else {
-        false
+    if new_type_index < 0 || old_type_index < 0 {
+        return new_type_index == old_type_index;
+    }
+    if cache.visited(new_type_index, old_type_index) {
+        return true;
+    }
+    cache.visit(new_type_index, old_type_index);
+    let new_type = new_type_table.get_type(new_type_index);
+    let old_type = old_type_table.get_type(old_type_index);
+    match (&new_type, &old_type) {
+        (Type::Object(new_object), Type::Object(old_object)) => object_compatible(
+            cache,
+            new_type_table,
+            new_object,
+            old_type_table,
+            old_object,
+        ),
     }
 }
 

From 3d708e2f04b4a734aac2806a970526453c8249ba Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 29 Aug 2023 13:53:49 +0200
Subject: [PATCH 070/260] Code refactoring, added test case

---
 .../src/persistence/compatibility.rs          |  68 +++++++++
 .../src/persistence/compatibility.rs          | 137 ++++++++----------
 2 files changed, 132 insertions(+), 73 deletions(-)

diff --git a/rts/motoko-rts-tests/src/persistence/compatibility.rs b/rts/motoko-rts-tests/src/persistence/compatibility.rs
index 44cb8b2de05..734b67b8b0e 100644
--- a/rts/motoko-rts-tests/src/persistence/compatibility.rs
+++ b/rts/motoko-rts-tests/src/persistence/compatibility.rs
@@ -145,6 +145,7 @@ unsafe fn test_sucessful_cases(heap: &mut TestMemory) {
     test_reordered_actor_fields(heap);
     test_removed_actor_fields(heap);
     test_added_actor_fields(heap);
+    test_removed_object_fields(heap);
     test_direct_recursive_type(heap);
     test_indirect_recursive_type(heap);
 }
@@ -223,6 +224,39 @@ unsafe fn test_added_actor_fields(heap: &mut TestMemory) {
     assert!(is_compatible(heap, old_type, new_type));
 }
 
+unsafe fn test_removed_object_fields(heap: &mut TestMemory) {
+    let actor_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+
+    let field1 = Field {
+        name: String::from("Field1"),
+        field_type: TypeReference::nat(),
+    };
+    let field2 = Field {
+        name: String::from("Field2"),
+        field_type: TypeReference::nat(),
+    };
+    let field3 = Field {
+        name: String::from("Field3"),
+        field_type: TypeReference::nat(),
+    };
+
+    let old_type = Type::Object(ObjectType {
+        fields: vec![field1.clone(), field2.clone(), field3.clone()],
+    });
+    let new_type = Type::Object(ObjectType {
+        fields: vec![field2.clone()],
+    });
+
+    let old_types = vec![actor_type.clone(), old_type];
+    let new_types = vec![actor_type.clone(), new_type];
+    assert!(are_compatible(heap, old_types, new_types));
+}
+
 unsafe fn test_direct_recursive_type(heap: &mut TestMemory) {
     let actor_field = Field {
         name: String::from("ActorField"),
@@ -266,6 +300,7 @@ unsafe fn test_indirect_recursive_type(heap: &mut TestMemory) {
 }
 
 unsafe fn test_failing_cases(heap: &mut TestMemory) {
+    test_added_object_fields(heap);
     test_recursion_mismatch(heap);
 }
 
@@ -302,3 +337,36 @@ unsafe fn test_recursion_mismatch(heap: &mut TestMemory) {
     let new_types = vec![new_actor, non_recursive_type];
     assert!(!are_compatible(heap, old_types, new_types));
 }
+
+unsafe fn test_added_object_fields(heap: &mut TestMemory) {
+    let actor_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+
+    let field1 = Field {
+        name: String::from("Field1"),
+        field_type: TypeReference::nat(),
+    };
+    let field2 = Field {
+        name: String::from("Field2"),
+        field_type: TypeReference::nat(),
+    };
+    let field3 = Field {
+        name: String::from("Field3"),
+        field_type: TypeReference::nat(),
+    };
+
+    let old_type = Type::Object(ObjectType {
+        fields: vec![field2.clone()],
+    });
+    let new_type = Type::Object(ObjectType {
+        fields: vec![field1.clone(), field2.clone(), field3.clone()],
+    });
+
+    let old_types = vec![actor_type.clone(), old_type];
+    let new_types = vec![actor_type.clone(), new_type];
+    assert!(!are_compatible(heap, old_types, new_types));
+}
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index a8a2a268bb4..eae19e2417a 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -241,96 +241,87 @@ impl TypeCheckCache {
     }
 }
 
-unsafe fn object_compatible(
-    cache: &mut TypeCheckCache,
-    new_type_table: &TypeTable,
-    new_object: &ObjectType,
-    old_type_table: &TypeTable,
-    old_object: &ObjectType,
-) -> bool {
-    for field_index in 0..new_object.count_fields() {
-        let new_field = new_object.get_field(field_index);
-        match old_object.find_field(new_field.label_hash) {
-            None => {
-                return false;
-            }
-            Some(old_field) => {
-                if !type_compatible(
-                    cache,
-                    new_type_table,
-                    new_field.type_index,
-                    old_type_table,
-                    old_field.type_index,
-                ) {
-                    return false;
-                }
-            }
-        }
-    }
-    true
+struct CompatibilityChecker {
+    cache: TypeCheckCache,
+    new_type_table: TypeTable,
+    old_type_table: TypeTable,
 }
 
-unsafe fn type_compatible(
-    cache: &mut TypeCheckCache,
-    new_type_table: &TypeTable,
-    new_type_index: i32,
-    old_type_table: &TypeTable,
-    old_type_index: i32,
-) -> bool {
-    if new_type_index < 0 || old_type_index < 0 {
-        return new_type_index == old_type_index;
-    }
-    if cache.visited(new_type_index, old_type_index) {
-        return true;
-    }
-    cache.visit(new_type_index, old_type_index);
-    let new_type = new_type_table.get_type(new_type_index);
-    let old_type = old_type_table.get_type(old_type_index);
-    match (&new_type, &old_type) {
-        (Type::Object(new_object), Type::Object(old_object)) => object_compatible(
+impl CompatibilityChecker {
+    unsafe fn new(
+        mem: &mut M,
+        new_type_table: TypeTable,
+        old_type_table: TypeTable,
+    ) -> CompatibilityChecker {
+        let cache = TypeCheckCache::new(mem, &new_type_table, &old_type_table);
+        CompatibilityChecker {
             cache,
             new_type_table,
-            new_object,
             old_type_table,
-            old_object,
-        ),
+        }
     }
-}
 
-unsafe fn compatible_actor_fields(
-    mem: &mut M,
-    new_type_table: TypeTable,
-    old_type_table: TypeTable,
-) -> bool {
-    let mut cache = TypeCheckCache::new(mem, &new_type_table, &old_type_table);
-    let new_actor = new_type_table.get_actor();
-    let old_actor = old_type_table.get_actor();
-    for new_field_index in 0..new_actor.count_fields() {
-        let new_field = new_actor.get_field(new_field_index);
-        match old_actor.find_field(new_field.label_hash) {
-            Some(old_field) => {
-                if !type_compatible(
-                    &mut cache,
-                    &new_type_table,
-                    new_field.type_index,
-                    &old_type_table,
-                    old_field.type_index,
-                ) {
+    unsafe fn object_compatible(
+        &mut self,
+        new_object: &ObjectType,
+        old_object: &ObjectType,
+    ) -> bool {
+        for field_index in 0..new_object.count_fields() {
+            let new_field = new_object.get_field(field_index);
+            match old_object.find_field(new_field.label_hash) {
+                None => {
                     return false;
                 }
+                Some(old_field) => {
+                    if !self.type_compatible(new_field.type_index, old_field.type_index) {
+                        return false;
+                    }
+                }
+            }
+        }
+        true
+    }
+
+    unsafe fn type_compatible(&mut self, new_type_index: i32, old_type_index: i32) -> bool {
+        if new_type_index < 0 || old_type_index < 0 {
+            return new_type_index == old_type_index;
+        }
+        if self.cache.visited(new_type_index, old_type_index) {
+            return true;
+        }
+        self.cache.visit(new_type_index, old_type_index);
+        let new_type = self.new_type_table.get_type(new_type_index);
+        let old_type = self.old_type_table.get_type(old_type_index);
+        match (&new_type, &old_type) {
+            (Type::Object(new_object), Type::Object(old_object)) => {
+                self.object_compatible(new_object, old_object)
             }
-            None => {}
         }
     }
-    true
-}
 
-// TODO: Unit test this funcionality
+    unsafe fn compatible_actor_fields(&mut self) -> bool {
+        let new_actor = self.new_type_table.get_actor();
+        let old_actor = self.old_type_table.get_actor();
+        for new_field_index in 0..new_actor.count_fields() {
+            let new_field = new_actor.get_field(new_field_index);
+            match old_actor.find_field(new_field.label_hash) {
+                Some(old_field) => {
+                    if !self.type_compatible(new_field.type_index, old_field.type_index) {
+                        return false;
+                    }
+                }
+                None => {}
+            }
+        }
+        true
+    }
+}
 
 /// Test whether the new stable type complies with the existing old stable type.
 /// Both arguments point to blobs encoding a stable actor type.
 pub unsafe fn memory_compatible(mem: &mut M, old_type: Value, new_type: Value) -> bool {
     let new_type_table = TypeTable::new(new_type);
     let old_type_table = TypeTable::new(old_type);
-    compatible_actor_fields(mem, new_type_table, old_type_table)
+    let mut checker = CompatibilityChecker::new(mem, new_type_table, old_type_table);
+    checker.compatible_actor_fields()
 }

From b3ac218c3be032846118cc7b01ba25efb0dd5580 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 29 Aug 2023 15:00:54 +0200
Subject: [PATCH 071/260] Upgrade check: Support mutable variables

---
 .../src/persistence/compatibility.rs          | 81 ++++++++++++++++++-
 .../src/persistence/compatibility.rs          | 43 +++++++++-
 src/codegen/persistence.ml                    | 10 ++-
 .../ok/upgrade-remove-add-fields.drun.ok      | 33 ++++++++
 test/run-drun/upgrade-remove-add-fields.drun  | 19 +++++
 .../upgrade-remove-add-fields/version0.mo     | 21 +++++
 .../upgrade-remove-add-fields/version1.mo     | 15 ++++
 .../upgrade-remove-add-fields/version2.mo     | 18 +++++
 8 files changed, 231 insertions(+), 9 deletions(-)
 create mode 100644 test/run-drun/ok/upgrade-remove-add-fields.drun.ok
 create mode 100644 test/run-drun/upgrade-remove-add-fields.drun
 create mode 100644 test/run-drun/upgrade-remove-add-fields/version0.mo
 create mode 100644 test/run-drun/upgrade-remove-add-fields/version1.mo
 create mode 100644 test/run-drun/upgrade-remove-add-fields/version2.mo

diff --git a/rts/motoko-rts-tests/src/persistence/compatibility.rs b/rts/motoko-rts-tests/src/persistence/compatibility.rs
index 734b67b8b0e..521096ebb6a 100644
--- a/rts/motoko-rts-tests/src/persistence/compatibility.rs
+++ b/rts/motoko-rts-tests/src/persistence/compatibility.rs
@@ -1,5 +1,7 @@
 use motoko_rts::memory::{alloc_blob, Memory};
-use motoko_rts::persistence::compatibility::memory_compatible;
+use motoko_rts::persistence::compatibility::{
+    memory_compatible, MUTABLE_ENCODING_TAG, OBJECT_ENCODING_TAG,
+};
 use motoko_rts::types::{Bytes, Value};
 use std::hash::Hasher;
 use std::{collections::hash_map::DefaultHasher, hash::Hash};
@@ -45,12 +47,14 @@ impl BinaryData {
 #[derive(Clone)]
 enum Type {
     Object(ObjectType),
+    Mutable(MutableType),
 }
 
 impl Type {
     fn serialize(&self, output: &mut BinaryData) {
         match &self {
             Self::Object(object_type) => object_type.serialize(output),
+            Self::Mutable(mutable_type) => mutable_type.serialize(output),
         }
     }
 }
@@ -86,8 +90,7 @@ struct ObjectType {
 
 impl ObjectType {
     fn serialize(&self, output: &mut BinaryData) {
-        const OBJECT_TAG: i32 = 1;
-        output.write_i32(OBJECT_TAG);
+        output.write_i32(OBJECT_ENCODING_TAG);
         output.write_i32(self.fields.len() as i32);
         for field in &self.fields {
             field.serialize(output);
@@ -95,6 +98,18 @@ impl ObjectType {
     }
 }
 
+#[derive(Clone)]
+struct MutableType {
+    variable_type: TypeReference,
+}
+
+impl MutableType {
+    fn serialize(&self, output: &mut BinaryData) {
+        output.write_i32(MUTABLE_ENCODING_TAG);
+        output.write_i32(self.variable_type.index);
+    }
+}
+
 struct TypeTable {
     types: Vec,
 }
@@ -144,6 +159,7 @@ unsafe fn test_sucessful_cases(heap: &mut TestMemory) {
     test_empty_actor(heap);
     test_reordered_actor_fields(heap);
     test_removed_actor_fields(heap);
+    test_mutable_fields(heap);
     test_added_actor_fields(heap);
     test_removed_object_fields(heap);
     test_direct_recursive_type(heap);
@@ -224,6 +240,21 @@ unsafe fn test_added_actor_fields(heap: &mut TestMemory) {
     assert!(is_compatible(heap, old_type, new_type));
 }
 
+unsafe fn test_mutable_fields(heap: &mut TestMemory) {
+    let actor_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let mutable_type = Type::Mutable(MutableType {
+        variable_type: TypeReference::nat(),
+    });
+    let old_types = vec![actor_type.clone(), mutable_type.clone()];
+    let new_types = vec![actor_type.clone(), mutable_type.clone()];
+    assert!(are_compatible(heap, old_types, new_types));
+}
+
 unsafe fn test_removed_object_fields(heap: &mut TestMemory) {
     let actor_type = Type::Object(ObjectType {
         fields: vec![Field {
@@ -301,6 +332,8 @@ unsafe fn test_indirect_recursive_type(heap: &mut TestMemory) {
 
 unsafe fn test_failing_cases(heap: &mut TestMemory) {
     test_added_object_fields(heap);
+    test_mutable_mismatch(heap);
+    test_immutable_mismatch(heap);
     test_recursion_mismatch(heap);
 }
 
@@ -370,3 +403,45 @@ unsafe fn test_added_object_fields(heap: &mut TestMemory) {
     let new_types = vec![actor_type.clone(), new_type];
     assert!(!are_compatible(heap, old_types, new_types));
 }
+
+unsafe fn test_mutable_mismatch(heap: &mut TestMemory) {
+    let old_actor = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let mutable_type = Type::Mutable(MutableType {
+        variable_type: TypeReference::nat(),
+    });
+    let new_actor = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference::nat(),
+        }],
+    });
+    let old_types = vec![old_actor, mutable_type];
+    let new_types = vec![new_actor];
+    assert!(!are_compatible(heap, old_types, new_types));
+}
+
+unsafe fn test_immutable_mismatch(heap: &mut TestMemory) {
+    let old_actor = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference::nat(),
+        }],
+    });
+    let new_actor = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("ActorField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let mutable_type = Type::Mutable(MutableType {
+        variable_type: TypeReference::nat(),
+    });
+    let old_types = vec![old_actor];
+    let new_types = vec![new_actor, mutable_type];
+    assert!(!are_compatible(heap, old_types, new_types));
+}
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index eae19e2417a..5936a9443ac 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -33,8 +33,9 @@
 //!
 //! ```
 //!  ::= length:i32 ()^length
-//!  ::= 
-//!  ::= 1l field_list
+//!  ::=  | 
+//!  ::= 1l 
+//!  ::= 2l type_index:i32
 //!  ::= length:i32 ()^length
 //!  ::= label_hash:i32 type_index:i32
 //! ```
@@ -51,12 +52,14 @@ use crate::{
     types::{size_of, Bytes, Value},
 };
 
-const OBJECT_ENCODING_TAG: i32 = 1;
+pub const OBJECT_ENCODING_TAG: i32 = 1;
+pub const MUTABLE_ENCODING_TAG: i32 = 2;
 
 const ACTOR_TYPE_INDEX: i32 = 0;
 
 const OBJECT_ENCODING_HEADER: usize = 2; // object_tag field_list_length
 const FIELD_ENCODING_LENGTH: usize = 2; // label_hash type_index
+const MUTABLE_ENCODING_LENGTH: usize = 2; // object_tag type_index
 
 struct EncodedData {
     words: *const i32,
@@ -117,19 +120,21 @@ impl TypeTable {
     unsafe fn get_actor(&self) -> ObjectType {
         match self.get_type(ACTOR_TYPE_INDEX) {
             Type::Object(object_type) => object_type,
-            // _ => panic!("Invalid stable type encoding")
+            _ => panic!("Invalid stable type encoding"),
         }
     }
 }
 
 enum Type {
     Object(ObjectType),
+    Mutable(MutableType),
 }
 
 impl Type {
     unsafe fn size(data: EncodedData) -> usize {
         match Self::get_type(data) {
             Self::Object(object_type) => object_type.size(),
+            Self::Mutable(mutable_type) => mutable_type.size(),
         }
     }
 
@@ -137,6 +142,7 @@ impl Type {
         let tag = data.read(0);
         match tag {
             OBJECT_ENCODING_TAG => Self::Object(ObjectType::new(data)),
+            MUTABLE_ENCODING_TAG => Self::Mutable(MutableType::new(data)),
             _ => unimplemented!(),
         }
     }
@@ -189,6 +195,22 @@ struct Field {
     type_index: i32,
 }
 
+struct MutableType {
+    type_index: i32,
+}
+
+impl MutableType {
+    unsafe fn new(data: EncodedData) -> MutableType {
+        assert_eq!(data.read(0), MUTABLE_ENCODING_TAG);
+        let type_index = data.read(1);
+        MutableType { type_index }
+    }
+
+    unsafe fn size(&self) -> usize {
+        MUTABLE_ENCODING_LENGTH
+    }
+}
+
 /// Cache for remembering previous type compatibility checks.
 /// Necessary to avoid infinite loops on type recursion.
 /// Helpful to optimize repeated checks.
@@ -282,6 +304,14 @@ impl CompatibilityChecker {
         true
     }
 
+    unsafe fn mutable_compatible(
+        &mut self,
+        new_mutable: &MutableType,
+        old_mutable: &MutableType,
+    ) -> bool {
+        self.type_compatible(new_mutable.type_index, old_mutable.type_index)
+    }
+
     unsafe fn type_compatible(&mut self, new_type_index: i32, old_type_index: i32) -> bool {
         if new_type_index < 0 || old_type_index < 0 {
             return new_type_index == old_type_index;
@@ -296,6 +326,11 @@ impl CompatibilityChecker {
             (Type::Object(new_object), Type::Object(old_object)) => {
                 self.object_compatible(new_object, old_object)
             }
+            (Type::Object(_), _) => false,
+            (Type::Mutable(new_mutable), Type::Mutable(old_mutable)) => {
+                self.mutable_compatible(new_mutable, old_mutable)
+            }
+            (Type::Mutable(_), _) => false,
         }
     }
 
diff --git a/src/codegen/persistence.ml b/src/codegen/persistence.ml
index a05f320a8a4..20833217994 100644
--- a/src/codegen/persistence.ml
+++ b/src/codegen/persistence.ml
@@ -7,8 +7,9 @@
   // Primitive types are encoded by negative indices.
   // All numbers (type indices etc.) are encoded as little endian i32.
    ::= length:i32 ()^length
-   ::= 
-   ::= 1l field_list
+   ::=  | 
+   ::= 1l 
+   ::= 2l type_index:i32
    ::= length:i32 ()^length
    ::= label_hash:i32 type_index:i32
   
@@ -56,6 +57,8 @@ let rec collect_type table typ =
   | Obj (Object, field_list) ->
     let field_types = List.map (fun field -> field.typ) field_list in
     collect_types table field_types
+  | Mut var_type ->
+    collect_type table var_type
   | _ -> 
     Printf.printf "UNSUPPORTED PERSISTENT TYPE %s\n" (Type.string_of_typ typ);
     assert false
@@ -89,6 +92,9 @@ let encode_complex_type table typ =
   | Obj (Object, field_list) -> 
     encode_i32 1l ^ 
     encode_list (encode_field table) field_list
+  | Mut var_type ->
+    encode_i32 2l ^
+    encode_i32 (type_index table var_type)
   | _ -> assert false
 
 let encode_type_table table =
diff --git a/test/run-drun/ok/upgrade-remove-add-fields.drun.ok b/test/run-drun/ok/upgrade-remove-add-fields.drun.ok
new file mode 100644
index 00000000000..61be9d4a059
--- /dev/null
+++ b/test/run-drun/ok/upgrade-remove-add-fields.drun.ok
@@ -0,0 +1,33 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: firstField=1
+debug.print: secondField=1
+debug.print: thirdField=1
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: firstField=2
+debug.print: secondField=2
+debug.print: thirdField=2
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: secondField=3
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: secondField=4
+ingress Completed: Reply: 0x4449444c0000
+debug.print: Memory-incompatible program upgrade
+debug.print: panic occurred in file 'src/persistence.rs' at line 193
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: RTS error: RTS panicked
+ingress Completed: Reply: 0x4449444c0000
+debug.print: secondField=5
+ingress Completed: Reply: 0x4449444c0000
+debug.print: Memory-incompatible program upgrade
+debug.print: panic occurred in file 'src/persistence.rs' at line 193
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: RTS error: RTS panicked
+ingress Completed: Reply: 0x4449444c0000
+debug.print: secondField=6
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/upgrade-remove-add-fields.drun b/test/run-drun/upgrade-remove-add-fields.drun
new file mode 100644
index 00000000000..c9fa228534a
--- /dev/null
+++ b/test/run-drun/upgrade-remove-add-fields.drun
@@ -0,0 +1,19 @@
+# SKIP ic-ref-run
+install $ID upgrade-remove-add-fields/version0.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID upgrade-remove-add-fields/version0.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID upgrade-remove-add-fields/version1.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID upgrade-remove-add-fields/version1.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID upgrade-remove-add-fields/version2.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
+upgrade $ID upgrade-remove-add-fields/version2.mo ""
+ingress $ID increase "DIDL\x00\x01\x7d\x0F"
+ingress $ID show "DIDL\x00\x00"
diff --git a/test/run-drun/upgrade-remove-add-fields/version0.mo b/test/run-drun/upgrade-remove-add-fields/version0.mo
new file mode 100644
index 00000000000..cc7330c63bd
--- /dev/null
+++ b/test/run-drun/upgrade-remove-add-fields/version0.mo
@@ -0,0 +1,21 @@
+import Prim "mo:prim";
+
+actor {
+    stable var instance = {
+        var firstField = 0;
+        var secondField = 0;
+        var thirdField = 0;
+    };
+
+    public func increase() : async () {
+        instance.firstField += 1;
+        instance.secondField += 1;
+        instance.thirdField += 1;
+    };
+
+    public func show() : async () {
+        Prim.debugPrint("firstField=" # debug_show (instance.firstField));
+        Prim.debugPrint("secondField=" # debug_show (instance.secondField));
+        Prim.debugPrint("thirdField=" # debug_show (instance.thirdField));
+    };
+};
diff --git a/test/run-drun/upgrade-remove-add-fields/version1.mo b/test/run-drun/upgrade-remove-add-fields/version1.mo
new file mode 100644
index 00000000000..6a21d341207
--- /dev/null
+++ b/test/run-drun/upgrade-remove-add-fields/version1.mo
@@ -0,0 +1,15 @@
+import Prim "mo:prim";
+
+actor {
+    stable var instance = {
+        var secondField = 0;
+    };
+
+    public func increase() : async () {
+        instance.secondField += 1;
+    };
+
+    public func show() : async () {
+        Prim.debugPrint("secondField=" # debug_show (instance.secondField));
+    };
+};
diff --git a/test/run-drun/upgrade-remove-add-fields/version2.mo b/test/run-drun/upgrade-remove-add-fields/version2.mo
new file mode 100644
index 00000000000..fe72a1fdc7f
--- /dev/null
+++ b/test/run-drun/upgrade-remove-add-fields/version2.mo
@@ -0,0 +1,18 @@
+import Prim "mo:prim";
+
+actor {
+    stable var instance = {
+        var secondField = 0;
+        var newField = 0;
+    };
+
+    public func increase() : async () {
+        instance.secondField += 1;
+        instance.newField += 1;
+    };
+
+    public func show() : async () {
+        Prim.debugPrint("secondField=" # debug_show (instance.secondField));
+        Prim.debugPrint("newField=" # debug_show (instance.newField));
+    };
+};

From 31df1321aee76e906805d8cd0410677bd4713765 Mon Sep 17 00:00:00 2001
From: luc-blaeser 
Date: Tue, 29 Aug 2023 19:31:49 +0200
Subject: [PATCH 072/260] Continue compatibility check

---
 .../src/persistence/compatibility.rs          | 53 ++++++++++++++++++-
 .../src/persistence/compatibility.rs          | 39 +++++++++++++-
 src/codegen/persistence.ml                    | 52 +++++++++++-------
 3 files changed, 123 insertions(+), 21 deletions(-)

diff --git a/rts/motoko-rts-tests/src/persistence/compatibility.rs b/rts/motoko-rts-tests/src/persistence/compatibility.rs
index 521096ebb6a..f4eb9375b6b 100644
--- a/rts/motoko-rts-tests/src/persistence/compatibility.rs
+++ b/rts/motoko-rts-tests/src/persistence/compatibility.rs
@@ -1,6 +1,6 @@
 use motoko_rts::memory::{alloc_blob, Memory};
 use motoko_rts::persistence::compatibility::{
-    memory_compatible, MUTABLE_ENCODING_TAG, OBJECT_ENCODING_TAG,
+    memory_compatible, MUTABLE_ENCODING_TAG, OBJECT_ENCODING_TAG, OPTION_ENCODING_TAG,
 };
 use motoko_rts::types::{Bytes, Value};
 use std::hash::Hasher;
@@ -48,6 +48,7 @@ impl BinaryData {
 enum Type {
     Object(ObjectType),
     Mutable(MutableType),
+    Option(OptionType),
 }
 
 impl Type {
@@ -55,6 +56,7 @@ impl Type {
         match &self {
             Self::Object(object_type) => object_type.serialize(output),
             Self::Mutable(mutable_type) => mutable_type.serialize(output),
+            Self::Option(option_type) => option_type.serialize(output),
         }
     }
 }
@@ -110,6 +112,18 @@ impl MutableType {
     }
 }
 
+#[derive(Clone)]
+struct OptionType {
+    option_type: TypeReference,
+}
+
+impl OptionType {
+    fn serialize(&self, output: &mut BinaryData) {
+        output.write_i32(OPTION_ENCODING_TAG);
+        output.write_i32(self.option_type.index);
+    }
+}
+
 struct TypeTable {
     types: Vec,
 }
@@ -164,6 +178,7 @@ unsafe fn test_sucessful_cases(heap: &mut TestMemory) {
     test_removed_object_fields(heap);
     test_direct_recursive_type(heap);
     test_indirect_recursive_type(heap);
+    test_option_types(heap);
 }
 
 unsafe fn test_empty_actor(heap: &mut TestMemory) {
@@ -330,11 +345,26 @@ unsafe fn test_indirect_recursive_type(heap: &mut TestMemory) {
     assert!(are_compatible(heap, types.clone(), types.clone()));
 }
 
+unsafe fn test_option_types(heap: &mut TestMemory) {
+    let actor_type = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("OptionalField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let option_type = Type::Option(OptionType {
+        option_type: TypeReference::nat(),
+    });
+    let types = vec![actor_type, option_type];
+    assert!(are_compatible(heap, types.clone(), types.clone()));
+}
+
 unsafe fn test_failing_cases(heap: &mut TestMemory) {
     test_added_object_fields(heap);
     test_mutable_mismatch(heap);
     test_immutable_mismatch(heap);
     test_recursion_mismatch(heap);
+    test_option_mismatch(heap);
 }
 
 unsafe fn test_recursion_mismatch(heap: &mut TestMemory) {
@@ -445,3 +475,24 @@ unsafe fn test_immutable_mismatch(heap: &mut TestMemory) {
     let new_types = vec![new_actor, mutable_type];
     assert!(!are_compatible(heap, old_types, new_types));
 }
+
+unsafe fn test_option_mismatch(heap: &mut TestMemory) {
+    let old_actor = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("OptionalField"),
+            field_type: TypeReference { index: 1 },
+        }],
+    });
+    let option_type = Type::Option(OptionType {
+        option_type: TypeReference::nat(),
+    });
+    let new_actor = Type::Object(ObjectType {
+        fields: vec![Field {
+            name: String::from("OptionalField"),
+            field_type: TypeReference::nat(),
+        }],
+    });
+    let old_types = vec![old_actor, option_type];
+    let new_types = vec![new_actor];
+    assert!(!are_compatible(heap, old_types, new_types));
+}
diff --git a/rts/motoko-rts/src/persistence/compatibility.rs b/rts/motoko-rts/src/persistence/compatibility.rs
index 5936a9443ac..0fd56f79889 100644
--- a/rts/motoko-rts/src/persistence/compatibility.rs
+++ b/rts/motoko-rts/src/persistence/compatibility.rs
@@ -33,9 +33,10 @@
 //!
 //! ```
 //!  ::= length:i32 ()^length
-//!  ::=  | 
+//!  ::=  |  |