diff --git a/rts/motoko-rts/src/constants.rs b/rts/motoko-rts/src/constants.rs index 1044932eae8..9cdb1e428e5 100644 --- a/rts/motoko-rts/src/constants.rs +++ b/rts/motoko-rts/src/constants.rs @@ -12,3 +12,8 @@ pub const WASM_HEAP_SIZE: Words = Words(1024 * 1024 * 1024); /// Wasm memory size (4 GiB) in bytes. Note: Represented as `u64` in order not to overflow. pub const WASM_MEMORY_BYTE_SIZE: Bytes = Bytes(4 * 1024 * 1024 * 1024); + +/// Byte constants +pub const KB: usize = 1024; +pub const MB: usize = 1024 * KB; +pub const GB: usize = 1024 * MB; diff --git a/rts/motoko-rts/src/gc/generational.rs b/rts/motoko-rts/src/gc/generational.rs index bd23a87551f..a3b86531549 100644 --- a/rts/motoko-rts/src/gc/generational.rs +++ b/rts/motoko-rts/src/gc/generational.rs @@ -117,7 +117,8 @@ static mut OLD_GENERATION_THRESHOLD: usize = 32 * 1024 * 1024; static mut PASSED_CRITICAL_LIMIT: bool = false; #[cfg(feature = "ic")] -const CRITICAL_MEMORY_LIMIT: usize = (4096 - 512) * 1024 * 1024 - crate::memory::MEMORY_RESERVE; +const CRITICAL_MEMORY_LIMIT: usize = + (4096 - 512) * 1024 * 1024 - crate::memory::GENERAL_MEMORY_RESERVE; #[cfg(feature = "ic")] unsafe fn decide_strategy(limits: &Limits) -> Option { diff --git a/rts/motoko-rts/src/gc/incremental.rs b/rts/motoko-rts/src/gc/incremental.rs index 50db7d43f07..4532dbe8f3d 100644 --- a/rts/motoko-rts/src/gc/incremental.rs +++ b/rts/motoko-rts/src/gc/incremental.rs @@ -70,18 +70,22 @@ static mut LAST_ALLOCATIONS: Bytes = Bytes(0u64); #[cfg(feature = "ic")] unsafe fn should_start() -> bool { use self::partitioned_heap::PARTITION_SIZE; - use crate::memory::{ic::partitioned_memory, MEMORY_RESERVE}; + use crate::constants::{GB, MB}; + use crate::memory::ic::partitioned_memory; - const CRITICAL_HEAP_LIMIT: Bytes = - Bytes(u32::MAX - 768 * 1024 * 1024 - MEMORY_RESERVE as u32); + const CRITICAL_HEAP_LIMIT: Bytes = Bytes((2 * GB + 256 * MB) as u32); const CRITICAL_GROWTH_THRESHOLD: f64 = 0.01; - const NORMAL_GROWTH_THRESHOLD: f64 = 0.65; + const MEDIUM_HEAP_LIMIT: Bytes = Bytes(1 * GB as u32); + const MEDIUM_GROWTH_THRESHOLD: f64 = 0.35; + const LOW_GROWTH_THRESHOLD: f64 = 0.65; let heap_size = partitioned_memory::get_heap_size(); let growth_threshold = if heap_size > CRITICAL_HEAP_LIMIT { CRITICAL_GROWTH_THRESHOLD + } else if heap_size > MEDIUM_HEAP_LIMIT { + MEDIUM_GROWTH_THRESHOLD } else { - NORMAL_GROWTH_THRESHOLD + LOW_GROWTH_THRESHOLD }; let current_allocations = partitioned_memory::get_total_allocations(); @@ -122,11 +126,11 @@ unsafe fn record_gc_stop() { /// Finally, all the evacuated and temporary partitions are freed. /// The temporary partitions store mark bitmaps. -/// The limit on the GC increment has a fix base with a linear increase depending on the number of +/// The limit on the GC increment has a fixed base with a linear increase depending on the number of /// allocations that were performed during a running GC. The allocation-proportional term adapts /// to the allocation rate and helps the GC to reduce reclamation latency. -const INCREMENT_BASE_LIMIT: usize = 3_500_000; // Increment limit without concurrent allocations. -const INCREMENT_ALLOCATION_FACTOR: usize = 10; // Additional time factor per concurrent allocation. +const INCREMENT_BASE_LIMIT: usize = 5_000_000; // Increment limit without concurrent allocations. +const INCREMENT_ALLOCATION_FACTOR: usize = 50; // Additional time factor per concurrent allocation. // Performance note: Storing the phase-specific state in the enum would be nicer but it is much slower. #[derive(PartialEq)] @@ -144,6 +148,7 @@ pub struct State { allocation_count: usize, // Number of allocations during an active GC run. mark_state: Option, iterator_state: Option, + running_increment: bool, // GC increment is active. } /// GC state retained over multiple GC increments. @@ -153,6 +158,7 @@ static mut STATE: RefCell = RefCell::new(State { allocation_count: 0, mark_state: None, iterator_state: None, + running_increment: false, }); /// Incremental GC. @@ -173,6 +179,7 @@ impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { state.allocation_count = 0; state.mark_state = None; state.iterator_state = None; + state.running_increment = false; } /// Each GC schedule point can get a new GC instance that shares the common GC state. @@ -193,6 +200,8 @@ 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) { + debug_assert!(!self.state.running_increment); + self.state.running_increment = true; assert!(self.state.phase != Phase::Stop); if self.pausing() { self.start_marking(roots); @@ -215,6 +224,7 @@ impl<'a, M: Memory + 'a> IncrementalGC<'a, M> { if self.updating_completed() { self.complete_run(roots); } + self.state.running_increment = false; } unsafe fn pausing(&mut self) -> bool { @@ -407,3 +417,24 @@ pub unsafe fn get_partitioned_heap() -> &'static mut PartitionedHeap { debug_assert!(STATE.get_mut().partitioned_heap.is_initialized()); &mut STATE.get_mut().partitioned_heap } + +#[cfg(feature = "ic")] +use crate::constants::MB; + +/// Additional memory reserve in bytes for the GC. +/// * To allow mark bitmap allocation, i.e. max. 128 MB in 4 GB address space. +/// * 512 MB of free space for evacuations/compactions. +#[cfg(feature = "ic")] +const GC_MEMORY_RESERVE: usize = (128 + 512) * MB; + +#[cfg(feature = "ic")] +pub unsafe fn memory_reserve() -> usize { + use crate::memory::GENERAL_MEMORY_RESERVE; + + let additional_reserve = if STATE.borrow().running_increment { + 0 + } else { + GC_MEMORY_RESERVE + }; + GENERAL_MEMORY_RESERVE + additional_reserve +} diff --git a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs index 78d6b84ace4..7fa29d3eb92 100644 --- a/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs +++ b/rts/motoko-rts/src/gc/incremental/partitioned_heap.rs @@ -41,8 +41,11 @@ use core::{array::from_fn, ops::Range, ptr::null_mut}; use crate::{ - constants::WASM_MEMORY_BYTE_SIZE, gc::incremental::mark_bitmap::BITMAP_ITERATION_END, - memory::Memory, rts_trap_with, types::*, + constants::{MB, WASM_MEMORY_BYTE_SIZE}, + gc::incremental::mark_bitmap::BITMAP_ITERATION_END, + memory::Memory, + rts_trap_with, + types::*, }; use super::{ @@ -57,7 +60,7 @@ use super::{ /// due to the increased frequency of large object handling. /// -> Large partitions above 32 MB are a waste for small programs, since the WASM memory is /// allocated in that granularity and GC is then triggered later. -pub const PARTITION_SIZE: usize = 32 * 1024 * 1024; +pub const PARTITION_SIZE: usize = 32 * MB; /// Total number of partitions in the memory. /// For simplicity, the last partition is left unused, to avoid a numeric overflow when diff --git a/rts/motoko-rts/src/memory.rs b/rts/motoko-rts/src/memory.rs index ce2d601fd34..8f7929f362a 100644 --- a/rts/motoko-rts/src/memory.rs +++ b/rts/motoko-rts/src/memory.rs @@ -5,12 +5,15 @@ use crate::constants::WASM_HEAP_SIZE; use crate::rts_trap_with; use crate::types::*; +#[cfg(feature = "ic")] +use crate::constants::MB; + use motoko_rts_macros::ic_mem_fn; // Memory reserve in bytes ensured during update and initialization calls. // For use by queries and upgrade calls. #[cfg(feature = "ic")] -pub(crate) const MEMORY_RESERVE: usize = 256 * 1024 * 1024; +pub(crate) const GENERAL_MEMORY_RESERVE: usize = 256 * MB; /// A trait for heap allocation. RTS functions allocate in heap via this trait. /// diff --git a/rts/motoko-rts/src/memory/ic.rs b/rts/motoko-rts/src/memory/ic.rs index d60eb6fab2e..3a330cbd4ce 100644 --- a/rts/motoko-rts/src/memory/ic.rs +++ b/rts/motoko-rts/src/memory/ic.rs @@ -7,7 +7,6 @@ pub mod partitioned_memory; use super::Memory; use crate::constants::WASM_PAGE_SIZE; -use crate::memory::MEMORY_RESERVE; use crate::rts_trap_with; use crate::types::{Bytes, Value}; use core::arch::wasm32; @@ -39,12 +38,14 @@ pub struct IcMemory; /// Page allocation. Ensures that the memory up to, but excluding, the given pointer is allocated. /// Ensure a memory reserve of at least one Wasm page depending on the canister state. -unsafe fn grow_memory(ptr: u64) { +/// `memory_reserve`: A memory reserve in bytes ensured during update and initialization calls. +// For use by queries and upgrade calls. The reserve may vary depending on the GC and the phase of the GC. +unsafe fn grow_memory(ptr: u64, memory_reserve: usize) { const LAST_PAGE_LIMIT: usize = 0xFFFF_0000; debug_assert_eq!(LAST_PAGE_LIMIT, usize::MAX - WASM_PAGE_SIZE.as_usize() + 1); let limit = if keep_memory_reserve() { // Spare a memory reserve during update and initialization calls for use by queries and upgrades. - usize::MAX - MEMORY_RESERVE + 1 + usize::MAX - memory_reserve + 1 } else { // Spare the last Wasm memory page on queries and upgrades to support the Rust call stack boundary checks. LAST_PAGE_LIMIT diff --git a/rts/motoko-rts/src/memory/ic/linear_memory.rs b/rts/motoko-rts/src/memory/ic/linear_memory.rs index 14c94de5adc..b906150e4f1 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 crate::{memory::GENERAL_MEMORY_RESERVE, types::*}; /// Amount of garbage collected so far. pub(crate) static mut RECLAIMED: Bytes = Bytes(0); @@ -65,6 +65,6 @@ impl Memory for IcMemory { #[inline(never)] unsafe fn grow_memory(&mut self, ptr: u64) { - super::grow_memory(ptr); + super::grow_memory(ptr, GENERAL_MEMORY_RESERVE); } } diff --git a/rts/motoko-rts/src/memory/ic/partitioned_memory.rs b/rts/motoko-rts/src/memory/ic/partitioned_memory.rs index b4d2b4d5f2a..792a840ae7a 100644 --- a/rts/motoko-rts/src/memory/ic/partitioned_memory.rs +++ b/rts/motoko-rts/src/memory/ic/partitioned_memory.rs @@ -24,6 +24,7 @@ impl Memory for IcMemory { #[inline(never)] unsafe fn grow_memory(&mut self, ptr: u64) { - super::grow_memory(ptr); + let memory_reserve = crate::gc::incremental::memory_reserve(); + super::grow_memory(ptr, memory_reserve); } } diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 74485f790e7..eedfa6ab029 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -627,10 +627,10 @@ module E = struct | Flags.Generational -> "generational" | Flags.Incremental -> "incremental" - let collect_garbage env = + let collect_garbage env force = (* 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 + let gc_fn = if force || !Flags.force_gc then name else "schedule_" ^ name in call_import env "rts" (gc_fn ^ "_gc") (* See Note [Candid subtype checks] *) @@ -1193,7 +1193,7 @@ module GC = struct let collect_garbage env = record_mutator_instructions env ^^ - E.collect_garbage env ^^ + E.collect_garbage env false ^^ record_collector_instructions env end (* GC *) @@ -1425,6 +1425,12 @@ module Stack = struct f get_x ^^ dynamic_free_words env get_n + let dynamic_with_bytes env name f = + (* round up to nearest wordsize *) + compile_add_const (Int32.sub Heap.word_size 1l) ^^ + compile_divU_const Heap.word_size ^^ + dynamic_with_words env name f + (* Stack Frames *) (* Traditional frame pointer for accessing statically allocated locals/args (all words) @@ -4717,11 +4723,7 @@ module IC = struct let fi = E.add_fun env "canister_heartbeat" (Func.of_body env [] [] (fun env -> G.i (Call (nr (E.built_in env "heartbeat_exp"))) ^^ - (* TODO(3622) - Until DTS is implemented for heartbeats, don't collect garbage here, - just record mutator_instructions and leave GC scheduling to the - already scheduled async message running `system` function `heartbeat` *) - GC.record_mutator_instructions env (* future: GC.collect_garbage env *))) + GC.collect_garbage env)) in E.add_export env (nr { name = Lib.Utf8.decode "canister_heartbeat"; @@ -4734,11 +4736,7 @@ module IC = struct let fi = E.add_fun env "canister_global_timer" (Func.of_body env [] [] (fun env -> G.i (Call (nr (E.built_in env "timer_exp"))) ^^ - (* TODO(3622) - Until DTS is implemented for timers, don't collect garbage here, - just record mutator_instructions and leave GC scheduling to the - already scheduled async message running `system` function `timer` *) - GC.record_mutator_instructions env (* future: GC.collect_garbage env *))) + GC.collect_garbage env)) in E.add_export env (nr { name = Lib.Utf8.decode "canister_global_timer"; @@ -4922,30 +4920,47 @@ module IC = struct E.trap_with env (Printf.sprintf "assertion failed at %s" (string_of_region at)) let async_method_name = Type.(motoko_async_helper_fld.lab) + let gc_trigger_method_name = Type.(motoko_gc_trigger_fld.lab) + + let is_self_call env = + let (set_len_self, get_len_self) = new_local env "len_self" in + let (set_len_caller, get_len_caller) = new_local env "len_caller" in + system_call env "canister_self_size" ^^ set_len_self ^^ + system_call env "msg_caller_size" ^^ set_len_caller ^^ + get_len_self ^^ get_len_caller ^^ G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^ + G.if1 I32Type + begin + get_len_self ^^ Stack.dynamic_with_bytes env "str_self" (fun get_str_self -> + get_len_caller ^^ Stack.dynamic_with_bytes env "str_caller" (fun get_str_caller -> + get_str_caller ^^ compile_unboxed_const 0l ^^ get_len_caller ^^ + system_call env "msg_caller_copy" ^^ + get_str_self ^^ compile_unboxed_const 0l ^^ get_len_self ^^ + system_call env "canister_self_copy" ^^ + get_str_self ^^ get_str_caller ^^ get_len_self ^^ Heap.memcmp env ^^ + compile_eq_const 0l)) + end + begin + compile_unboxed_const 0l + end let assert_caller_self env = - let (set_len1, get_len1) = new_local env "len1" in - let (set_len2, get_len2) = new_local env "len2" in - let (set_str1, get_str1) = new_local env "str1" in - let (set_str2, get_str2) = new_local env "str2" in - system_call env "canister_self_size" ^^ set_len1 ^^ - system_call env "msg_caller_size" ^^ set_len2 ^^ - get_len1 ^^ get_len2 ^^ G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^ - E.else_trap_with env "not a self-call" ^^ - - get_len1 ^^ Blob.dyn_alloc_scratch env ^^ set_str1 ^^ - get_str1 ^^ compile_unboxed_const 0l ^^ get_len1 ^^ - system_call env "canister_self_copy" ^^ - - get_len2 ^^ Blob.dyn_alloc_scratch env ^^ set_str2 ^^ - get_str2 ^^ compile_unboxed_const 0l ^^ get_len2 ^^ - system_call env "msg_caller_copy" ^^ - - - get_str1 ^^ get_str2 ^^ get_len1 ^^ Heap.memcmp env ^^ - compile_eq_const 0l ^^ + is_self_call env ^^ E.else_trap_with env "not a self-call" + let is_controller_call env = + let (set_len_caller, get_len_caller) = new_local env "len_caller" in + system_call env "msg_caller_size" ^^ set_len_caller ^^ + get_len_caller ^^ Stack.dynamic_with_bytes env "str_caller" (fun get_str_caller -> + get_str_caller ^^ compile_unboxed_const 0l ^^ get_len_caller ^^ + system_call env "msg_caller_copy" ^^ + get_str_caller ^^ get_len_caller ^^ is_controller env) + + let assert_caller_self_or_controller env = + is_self_call env ^^ + is_controller_call env ^^ + G.i (Binary (Wasm.Values.I32 I32Op.Or)) ^^ + E.else_trap_with env "not a self-call or call from controller" + (* Cycles *) let cycle_balance env = @@ -9062,6 +9077,41 @@ module FuncDec = struct | _ -> () end + let export_gc_trigger_method env = + let name = IC.gc_trigger_method_name in + begin match E.mode env with + | Flags.ICMode | Flags.RefMode -> + Func.define_built_in env name [] [] (fun env -> + message_start env (Type.Shared Type.Write) ^^ + (* Check that we are called from this or a controller, w/o allocation *) + IC.assert_caller_self_or_controller env ^^ + (* To avoid more failing allocation, don't deserialize args nor serialize reply, + i.e. don't even try to do this: + Serialization.deserialize env [] ^^ + Tuple.compile_unit ^^ + Serialization.serialize env [] ^^ + *) + (* Instead, just ignore the argument and + send a *statically* allocated, nullary reply *) + Blob.lit_ptr_len env "DIDL\x00\x00" ^^ + IC.reply_with_data env ^^ + (* Finally, act like + message_cleanup env (Type.Shared Type.Write) + but *force* collection *) + GC.record_mutator_instructions env ^^ + E.collect_garbage env true ^^ + GC.record_collector_instructions env ^^ + Lifecycle.trans env Lifecycle.Idle + ); + + let fi = E.built_in env name in + E.add_export env (nr { + name = Lib.Utf8.decode ("canister_update " ^ name); + edesc = nr (FuncExport (nr fi)) + }) + | _ -> () + end + end (* FuncDec *) @@ -12057,6 +12107,7 @@ and conclude_module env set_serialization_globals start_fi_o = RTS_Exports.system_exports env; FuncDec.export_async_method env; + FuncDec.export_gc_trigger_method env; (* See Note [Candid subtype checks] *) Serialization.set_delayed_globals env set_serialization_globals; diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 502ea6e5484..9afe83d384f 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -457,6 +457,7 @@ and export_footprint self_id expr = )], [{ it = I.{ name = lab; var = v }; at = no_region; note = typ }]) + and build_actor at ts self_id es obj_typ = let candid = build_candid ts obj_typ in let fs = build_fields obj_typ in diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml index bf9b1dfa9ac..78c224c60e8 100644 --- a/src/mo_types/type.ml +++ b/src/mo_types/type.ml @@ -1352,6 +1352,12 @@ let motoko_stable_var_info_fld = src = empty_src; } +let motoko_gc_trigger_fld = + { lab = "__motoko_gc_trigger"; + typ = Func(Shared Write, Promises, [scope_bind], [], []); + src = empty_src; + } + let get_candid_interface_fld = { lab = "__get_candid_interface_tmp_hack"; typ = Func(Shared Query, Promises, [scope_bind], [], [text]); @@ -1361,6 +1367,7 @@ let get_candid_interface_fld = let well_known_actor_fields = [ motoko_async_helper_fld; motoko_stable_var_info_fld; + motoko_gc_trigger_fld; get_candid_interface_fld ] diff --git a/src/mo_types/type.mli b/src/mo_types/type.mli index 319383098e3..c8b29596723 100644 --- a/src/mo_types/type.mli +++ b/src/mo_types/type.mli @@ -266,6 +266,7 @@ val string_of_stab_sig : field list -> string val motoko_async_helper_fld : field val motoko_stable_var_info_fld : field +val motoko_gc_trigger_fld : field val get_candid_interface_fld : field val well_known_actor_fields : field list diff --git a/test/bench/ok/heap-32.drun-run-opt.ok b/test/bench/ok/heap-32.drun-run-opt.ok index c037547a1f1..71783c5ef77 100644 --- a/test/bench/ok/heap-32.drun-run-opt.ok +++ b/test/bench/ok/heap-32.drun-run-opt.ok @@ -1,5 +1,5 @@ ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 ingress Completed: Reply: 0x4449444c0000 -debug.print: (50_227, +30_261_252, 620_249_119) -debug.print: (50_070, +32_992_212, 671_159_959) +debug.print: (50_227, +30_261_252, 620_249_054) +debug.print: (50_070, +32_992_212, 671_159_894) ingress Completed: Reply: 0x4449444c0000 diff --git a/test/bench/ok/heap-32.drun-run.ok b/test/bench/ok/heap-32.drun-run.ok index 5aa9bea5351..0362701009e 100644 --- a/test/bench/ok/heap-32.drun-run.ok +++ b/test/bench/ok/heap-32.drun-run.ok @@ -1,5 +1,5 @@ ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 ingress Completed: Reply: 0x4449444c0000 -debug.print: (50_227, +30_261_252, 667_502_633) -debug.print: (50_070, +32_992_212, 720_277_440) +debug.print: (50_227, +30_261_252, 667_502_508) +debug.print: (50_070, +32_992_212, 720_277_315) ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/gc-trigger-acl.mo b/test/run-drun/gc-trigger-acl.mo new file mode 100644 index 00000000000..9d7ca53f0a2 --- /dev/null +++ b/test/run-drun/gc-trigger-acl.mo @@ -0,0 +1,57 @@ +import Prim "mo:⛔"; +import Lib "gc-trigger-acl/C"; +// test __motoko_gc_trigger can only be invoked from self or controller +// using an actor class instance +actor Self { + + type GC = actor { + __motoko_gc_trigger: () -> async () + }; + + func toGC(a : actor {}) : GC { + let gc = actor (debug_show (Prim.principalOfActor(a))) : GC; + gc; + }; + + public func go() : async () { + + let GC = toGC(Self); + // test call from self + do { + try { + await GC.__motoko_gc_trigger(); + Prim.debugPrint("Self.gc_trigger()"); + } + catch e { + assert false; + } + }; + + let c = await (Lib.C(GC)); + + // test call from controller (Self is a controller of c) + do { + try { + await toGC(c).__motoko_gc_trigger(); + Prim.debugPrint("controlee.gc_trigger()"); + } + catch e { + assert false; + } + }; + + // test callback from non-controller (c is not a controller of Self nor GC) + do { + try { + await c.callback(); + assert false; + } + catch e { + Prim.debugPrint(Prim.errorMessage(e)); + } + } + + } +} + +//CALL ingress go "DIDL\x00\x00" diff --git a/test/run-drun/gc-trigger-acl/C.mo b/test/run-drun/gc-trigger-acl/C.mo new file mode 100644 index 00000000000..1fe1ffae494 --- /dev/null +++ b/test/run-drun/gc-trigger-acl/C.mo @@ -0,0 +1,10 @@ +actor class C(gc : actor { + __motoko_gc_trigger: () -> async () + }) +{ + + // attempt to call passed in gc_trigger + public shared func callback() : async () { + await gc.__motoko_gc_trigger(); // should fail unless controller or selfaxs + } +} diff --git a/test/run-drun/gc-trigger.mo b/test/run-drun/gc-trigger.mo new file mode 100644 index 00000000000..04ddf7ac455 --- /dev/null +++ b/test/run-drun/gc-trigger.mo @@ -0,0 +1,36 @@ +//MOC-NO-FORCE-GC +//INCREMENTAL-GC-ONLY +import Prim "mo:prim"; + +actor { + let retained = Prim.Array_init(6 * 1024 * 1024, 0); + // GC is triggered during initialization + + var heapSizeWithGarbage = 0; + + public func createGarbage(): async() { + ignore Prim.Array_init(1024 * 1024, 0); + ignore Prim.Array_init(1024 * 1024, 0); + heapSizeWithGarbage := Prim.rts_heap_size(); + // Growth is too little to trigger the incremental GC. + // Note: The generational GC would still run the GC. + }; + + public query func checkBeforeGC(): async() { + assert(Prim.rts_heap_size() >= heapSizeWithGarbage); + }; + + public query func checkAfterGC(): async() { + assert(retained.size() > 0); // ensures that the array is not collected + assert(Prim.rts_heap_size() < heapSizeWithGarbage); + }; +}; +//SKIP run +//SKIP run-low +//SKIP run-ir +//CALL ingress createGarbage "DIDL\x00\x00" +//CALL query checkBeforeGC "DIDL\x00\x00" +//CALL ingress __motoko_gc_trigger "DIDL\x00\x00" +//CALL ingress __motoko_gc_trigger "DIDL\x00\x00" +//CALL ingress __motoko_gc_trigger "DIDL\x00\x00" +//CALL query checkAfterGC "DIDL\x00\x00" diff --git a/test/run-drun/ok/gc-trigger-acl.drun-run.ok b/test/run-drun/ok/gc-trigger-acl.drun-run.ok new file mode 100644 index 00000000000..52609530b06 --- /dev/null +++ b/test/run-drun/ok/gc-trigger-acl.drun-run.ok @@ -0,0 +1,6 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +debug.print: Self.gc_trigger() +debug.print: controlee.gc_trigger() +debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: not a self-call or call from controller +ingress Completed: Reply: 0x4449444c0000 diff --git a/test/run-drun/ok/gc-trigger.drun-run.ok b/test/run-drun/ok/gc-trigger.drun-run.ok new file mode 100644 index 00000000000..3a0ef2a9866 --- /dev/null +++ b/test/run-drun/ok/gc-trigger.drun-run.ok @@ -0,0 +1,8 @@ +ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101 +ingress Completed: Reply: 0x4449444c0000 +ingress Completed: Reply: 0x4449444c0000 +Ok: Reply: 0x4449444c0000 +ingress Completed: Reply: 0x4449444c0000 +ingress Completed: Reply: 0x4449444c0000 +ingress Completed: Reply: 0x4449444c0000 +Ok: Reply: 0x4449444c0000 diff --git a/test/run.sh b/test/run.sh index 50d81e09fb6..c98c25cd76c 100755 --- a/test/run.sh +++ b/test/run.sh @@ -251,6 +251,15 @@ do case $ext in "mo") + if grep -q "//INCREMENTAL-GC-ONLY" $base.mo + then + if [[ $EXTRA_MOC_ARGS != *"--incremental-gc"* ]] + then + $ECHO " Skipped (non-incremental GC)" + continue + fi + fi + # extra flags (allow shell variables there) moc_extra_flags="$(eval echo $(grep '//MOC-FLAG' $base.mo | cut -c11- | paste -sd' '))" moc_extra_env="$(eval echo $(grep '//MOC-ENV' $base.mo | cut -c10- | paste -sd' '))"