Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support IC low Wasm memory hook #4849

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Motoko compiler changelog

## 0.13.6 (tbd)

* motoko (`moc`)

* Support low Wasm memory hook: `system func onLowMemory() : async () { ... }`.

## 0.13.5 (2024-12-06)

* motoko (`moc`)
Expand Down
26 changes: 26 additions & 0 deletions doc/md/canister-maintenance/memory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
sidebar_position: 5
---

# Memory diagnostics

## Low memory hook

The IC allows to implement a low memory hook, which is a warning trigger when main memory is becoming scarce.

For this purpose, a Motoko actor or actor class instance can implement the system function `onLowMemory()`. This system function is scheduled when canister's free main memory space has fallen below the defined threshold `wasm_memory_threshold`, that is is part of the canister settings. In Motoko, `onLowMemory()` implements the `canister_on_low_wasm_memory` hook defined in the IC specification.

Example of using the low memory hook:
```
actor {
system func onLowMemory() : async () {
Debug.print("Low memory!");
}
}
```

The following properties apply to the low memory hook:
* The execution of `onLowMemory` happens with a certain delay, as it is scheduled as a separate asynchronous message that runs after the message in which the threshold was crossed.
* Once executed, `onLowMemory` is only triggered again when the main memory free space went above the threshold (e.g. by lowering the threshold or shrinking the main memory through canister reinstallation) and when the free space again fell below the threshold.
luc-blaeser marked this conversation as resolved.
Show resolved Hide resolved
* Traps or unhandled errors in `onLowMemory` are ignored. They only revert the changes done in `onLowMemory`.
luc-blaeser marked this conversation as resolved.
Show resolved Hide resolved
* Due to its `async` return type, the `onLowMemory` function may send further messages and await results.
15 changes: 2 additions & 13 deletions nix/drun.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pkgs:
outputHashes = {
"build-info-0.0.27" = "sha256-SkwWwDNrTsntkNiCv6rsyTFGazhpRDnKtVzPpYLKF9U=";
"cloudflare-0.12.0" = "sha256-FxCAK7gUKp/63fdvzI5Ufsy4aur74fO4R/K3YFiUw0Y=";
"ic-bn-lib-0.1.0" = "sha256-wqWfF70B+YQWg63yiEvIxOq+LN1AasrNXcyPkDM4/jw=";
"ic-canister-sig-creation-1.1.0" = "sha256-c47Fh4kZbmezWCYVHMci2BMXJfESaOGsyNlWh8YR6oU=";
"icrc1-test-env-0.1.1" = "sha256-2PB7e64Owin/Eji3k8UoeWs+pfDfOOTaAyXjvjOZ/4g=";
"jsonrpc-0.12.1" = "sha256-3FtdZlt2PqVDkE5iKWYIp1eiIELsaYlUPRSP2Xp8ejM=";
"lmdb-rkv-0.14.99" = "sha256-5WcUzapkrc/s3wCBNCuUDhtbp17n67rTbm2rx0qtITg=";
Expand All @@ -42,19 +44,6 @@ pkgs:
EOF
cd -

# static linking of libunwind fails under nix Linux
patch rs/monitoring/backtrace/build.rs << EOF
@@ -1,8 +1,2 @@
fn main() {
- if std::env::var("TARGET").unwrap() == "x86_64-unknown-linux-gnu" {
- println!("cargo:rustc-link-lib=static=unwind");
- println!("cargo:rustc-link-lib=static=unwind-ptrace");
- println!("cargo:rustc-link-lib=static=unwind-x86_64");
- println!("cargo:rustc-link-lib=dylib=lzma");
- }
}
EOF

mkdir -p .cargo
cat > .cargo/config.toml << EOF
[target.x86_64-apple-darwin]
Expand Down
8 changes: 4 additions & 4 deletions nix/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
"version": "3.2.25"
},
"ic": {
"branch": "luc/adjust-drun",
"branch": "luc/latest-drun",
"description": "Internet Computer blockchain source: the client/replica software run by nodes",
"homepage": "",
"owner": "luc-blaeser",
"repo": "ic",
"rev": "bebe89514a6abd26e940b295323823169911a965",
"sha256": "1g68fyi5acbcgs2kjribk97fj8ki5g6pd99nwl5azz1rw1b0xycx",
"rev": "afc94db6269ec269ddb1eb5aded38e95fb019958",
"sha256": "1clc0183gsk824dg1qp8cqjfp44ywp9yll0a9a1frxgsbwllyk10",
"type": "tarball",
"url": "https://github.com/luc-blaeser/ic/archive/bebe89514a6abd26e940b295323823169911a965.tar.gz",
"url": "https://github.com/luc-blaeser/ic/archive/afc94db6269ec269ddb1eb5aded38e95fb019958.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"ic-hs": {
Expand Down
21 changes: 21 additions & 0 deletions src/codegen/compile_classical.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5277,6 +5277,18 @@ module IC = struct
edesc = nr (FuncExport (nr fi))
})

let export_low_memory env =
assert (E.mode env = Flags.ICMode || E.mode env = Flags.RefMode);
let fi = E.add_fun env "canister_on_low_wasm_memory"
(Func.of_body env [] [] (fun env ->
G.i (Call (nr (E.built_in env "low_memory_exp"))) ^^
GC.collect_garbage env))
in
E.add_export env (nr {
name = Lib.Utf8.decode "canister_on_low_wasm_memory";
edesc = nr (FuncExport (nr fi))
})

let export_wasi_start env =
assert (E.mode env = Flags.WASIMode);
let fi = E.add_fun env "_start" (Func.of_body env [] [] (fun env1 ->
Expand Down Expand Up @@ -13014,6 +13026,15 @@ and main_actor as_opt mod_env ds fs up =
IC.export_inspect env;
end;

(* Export low memory hook (but only when required) *)
begin match up.low_memory.it with
| Ir.PrimE (Ir.TupPrim, []) -> ()
| _ ->
Func.define_built_in env "low_memory_exp" [] [] (fun env ->
compile_exp_as env ae2 SR.unit up.low_memory);
IC.export_low_memory env;
end;

(* Helper function to build the stable actor wrapper *)
Func.define_built_in mod_env IC.get_actor_to_persist_function_name [] [I32Type] (fun env ->
compile_exp_as env ae2 SR.Vanilla build_stable_actor
Expand Down
21 changes: 21 additions & 0 deletions src/codegen/compile_enhanced.ml
Original file line number Diff line number Diff line change
Expand Up @@ -4972,6 +4972,18 @@ module IC = struct
edesc = nr (FuncExport (nr fi))
})

let export_low_memory env =
assert (E.mode env = Flags.ICMode || E.mode env = Flags.RefMode);
let fi = E.add_fun env "canister_on_low_wasm_memory"
(Func.of_body env [] [] (fun env ->
G.i (Call (nr (E.built_in env "low_memory_exp"))) ^^
GC.collect_garbage env))
in
E.add_export env (nr {
name = Lib.Utf8.decode "canister_on_low_wasm_memory";
edesc = nr (FuncExport (nr fi))
})

let initialize_main_actor_function_name = "@initialize_main_actor"

let initialize_main_actor env =
Expand Down Expand Up @@ -13106,6 +13118,15 @@ and main_actor as_opt mod_env ds fs up =
IC.export_inspect env;
end;

(* Export low memory hook (but only when required) *)
begin match up.low_memory.it with
| Ir.PrimE (Ir.TupPrim, []) -> ()
| _ ->
Func.define_built_in env "low_memory_exp" [] [] (fun env ->
compile_exp_as env ae2 SR.unit up.low_memory);
IC.export_low_memory env;
end;

(* Helper function to build the stable actor wrapper *)
Func.define_built_in mod_env IC.get_actor_to_persist_function_name [] [I64Type] (fun env ->
compile_exp_as env ae2 SR.Vanilla build_stable_actor
Expand Down
3 changes: 2 additions & 1 deletion src/ir_def/arrange_ir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ let rec exp e = match e.it with
| TryE (e, cs, None) -> "TryE" $$ [exp e] @ List.map case cs
| TryE (e, cs, Some (i, _)) -> "TryE" $$ [exp e] @ List.map case cs @ Atom ";" :: [id i]

and system { meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type} = (* TODO: show meta? *)
and system { meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type} = (* TODO: show meta? *)
"System" $$ [
"Pre" $$ [exp preupgrade];
"Post" $$ [exp postupgrade];
"Heartbeat" $$ [exp heartbeat];
"Timer" $$ [exp timer];
"Inspect" $$ [exp inspect];
"OnLowMemory" $$ [exp low_memory];
"StableRecord" $$ [exp stable_record];
"StableType" $$ [typ stable_type]
]
Expand Down
8 changes: 6 additions & 2 deletions src/ir_def/check_ir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ let rec check_exp env (exp:Ir.exp) : unit =
typ exp_r <: T.(Construct.err_contT unit);
typ exp_c <: Construct.clean_contT;
| ActorE (ds, fs,
{ preupgrade; postupgrade; meta; heartbeat; timer; inspect; stable_record; stable_type }, t0) ->
{ preupgrade; postupgrade; meta; heartbeat; timer; inspect; low_memory; stable_record; stable_type }, t0) ->
(* TODO: check meta *)
let env' = { env with async = None } in
let scope1 = gather_block_decs env' ds in
Expand All @@ -828,12 +828,14 @@ let rec check_exp env (exp:Ir.exp) : unit =
check_exp env'' heartbeat;
check_exp env'' timer;
check_exp env'' inspect;
check_exp env'' low_memory;
check_exp env'' stable_record;
typ preupgrade <: T.unit;
typ postupgrade <: T.unit;
typ heartbeat <: T.unit;
typ timer <: T.unit;
typ inspect <: T.unit;
typ low_memory <: T.unit;
typ stable_record <: stable_type;
check (T.is_obj t0) "bad annotation (object type expected)";
let (s0, tfs0) = T.as_obj t0 in
Expand Down Expand Up @@ -1160,7 +1162,7 @@ let check_comp_unit env = function
let env' = adjoin env scope in
check_decs env' ds
| ActorU (as_opt, ds, fs,
{ preupgrade; postupgrade; meta; heartbeat; timer; inspect; stable_type; stable_record }, t0) ->
{ preupgrade; postupgrade; meta; heartbeat; timer; inspect; low_memory; stable_type; stable_record }, t0) ->
let check p = check env no_region p in
let (<:) t1 t2 = check_sub env no_region t1 t2 in
let env' = match as_opt with
Expand All @@ -1179,11 +1181,13 @@ let check_comp_unit env = function
check_exp env'' timer;
check_exp env'' inspect;
check_exp env'' stable_record;
check_exp env'' low_memory;
typ preupgrade <: T.unit;
typ postupgrade <: T.unit;
typ heartbeat <: T.unit;
typ timer <: T.unit;
typ inspect <: T.unit;
typ low_memory <: T.unit;
typ stable_record <: stable_type;
check (T.is_obj t0) "bad annotation (object type expected)";
let (s0, tfs0) = T.as_obj t0 in
Expand Down
3 changes: 2 additions & 1 deletion src/ir_def/freevars.ml
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,13 @@ let rec exp e : f = match e.it with

and actor ds fs u = close (decs ds +++ fields fs +++ system u)

and system {meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; _} =
and system {meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; _} =
under_lambda (exp preupgrade) ++
under_lambda (exp postupgrade) ++
under_lambda (exp heartbeat) ++
under_lambda (exp timer) ++
under_lambda (exp inspect) ++
under_lambda (exp low_memory) ++
under_lambda (exp stable_record)

and exps es : f = unions exp es
Expand Down
1 change: 1 addition & 0 deletions src/ir_def/ir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ and system = {
heartbeat : exp;
timer : exp; (* TODO: use an option type: (Default of exp | UserDefined of exp) option *)
inspect : exp;
low_memory : exp;
stable_record: exp;
stable_type: Type.typ;
}
Expand Down
6 changes: 4 additions & 2 deletions src/ir_def/rename.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ and exp' rho = function
| VarE (m, i) -> VarE (m, id rho i)
| LitE _ as e -> e
| PrimE (p, es) -> PrimE (prim rho p, List.map (exp rho) es)
| ActorE (ds, fs, { meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, t) ->
| ActorE (ds, fs, { meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, t) ->
let ds', rho' = decs rho ds in
ActorE
(ds',
Expand All @@ -44,6 +44,7 @@ and exp' rho = function
heartbeat = exp rho' heartbeat;
timer = exp rho' timer;
inspect = exp rho' inspect;
low_memory = exp rho' low_memory;
stable_type = stable_type;
stable_record = exp rho' stable_record;
},
Expand Down Expand Up @@ -200,7 +201,7 @@ let comp_unit rho cu = match cu with
| LibU (ds, e) ->
let ds', rho' = decs rho ds
in LibU (ds', exp rho' e)
| ActorU (as_opt, ds, fs, { meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type }, t) ->
| ActorU (as_opt, ds, fs, { meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type }, t) ->
let as_opt', rho' = match as_opt with
| None -> None, rho
| Some as_ ->
Expand All @@ -215,6 +216,7 @@ let comp_unit rho cu = match cu with
heartbeat = exp rho'' heartbeat;
timer = exp rho'' timer;
inspect = exp rho'' inspect;
low_memory = exp rho'' low_memory;
stable_record = exp rho'' stable_record;
stable_type = stable_type;
}, t)
6 changes: 4 additions & 2 deletions src/ir_passes/async.ml
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,15 @@ let transform prog =
| (Returns | Replies), _ -> assert false
end
end
| ActorE (ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, typ) ->
| ActorE (ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, typ) ->
ActorE (t_decs ds, t_fields fs,
{meta;
preupgrade = t_exp preupgrade;
postupgrade = t_exp postupgrade;
heartbeat = t_exp heartbeat;
timer = t_exp timer;
inspect = t_exp inspect;
low_memory = t_exp low_memory;
stable_record = t_exp stable_record;
stable_type = t_typ stable_type;
},
Expand Down Expand Up @@ -523,14 +524,15 @@ let transform prog =
and t_comp_unit = function
| LibU _ -> raise (Invalid_argument "cannot compile library")
| ProgU ds -> ProgU (t_decs ds)
| ActorU (args_opt, ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, t) ->
| ActorU (args_opt, ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, t) ->
ActorU (Option.map t_args args_opt, t_decs ds, t_fields fs,
{ meta;
preupgrade = t_exp preupgrade;
postupgrade = t_exp postupgrade;
heartbeat = t_exp heartbeat;
timer = t_exp timer;
inspect = t_exp inspect;
low_memory = t_exp low_memory;
stable_record = t_exp stable_record;
stable_type = t_typ stable_type;
},
Expand Down
6 changes: 4 additions & 2 deletions src/ir_passes/await.ml
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,15 @@ and t_exp' context exp =
assert (not (T.is_shared_func (typ exp)));
let context' = LabelEnv.singleton Return Label in
FuncE (x, s, c, typbinds, pat, typs, t_exp context' exp1)
| ActorE (ds, ids, { meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, t) ->
| ActorE (ds, ids, { meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, t) ->
ActorE (t_decs context ds, ids,
{ meta;
preupgrade = t_exp LabelEnv.empty preupgrade;
postupgrade = t_exp LabelEnv.empty postupgrade;
heartbeat = t_ignore_throw LabelEnv.empty heartbeat;
timer = t_ignore_throw LabelEnv.empty timer;
inspect = t_exp LabelEnv.empty inspect;
low_memory = t_ignore_throw LabelEnv.empty low_memory;
stable_record = t_exp LabelEnv.empty stable_record;
stable_type;
},
Expand Down Expand Up @@ -647,14 +648,15 @@ and t_comp_unit context = function
expD (c_block context' ds (tupE []) (meta (T.unit) (fun v1 -> tupE [])))
]
end
| ActorU (as_opt, ds, ids, { meta = m; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, t) ->
| ActorU (as_opt, ds, ids, { meta = m; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, t) ->
ActorU (as_opt, t_decs context ds, ids,
{ meta = m;
preupgrade = t_exp LabelEnv.empty preupgrade;
postupgrade = t_exp LabelEnv.empty postupgrade;
heartbeat = t_ignore_throw LabelEnv.empty heartbeat;
timer = t_ignore_throw LabelEnv.empty timer;
inspect = t_exp LabelEnv.empty inspect;
low_memory = t_ignore_throw LabelEnv.empty low_memory;
stable_record = t_exp LabelEnv.empty stable_record;
stable_type;
},
Expand Down
6 changes: 4 additions & 2 deletions src/ir_passes/const.ml
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,15 @@ let rec exp lvl (env : env) e : Lbool.t =
surely_false
| NewObjE _ -> (* mutable objects *)
surely_false
| ActorE (ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, _typ) ->
| ActorE (ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, _typ) ->
(* this may well be “the” top-level actor, so don’t update lvl here *)
let (env', _) = decs lvl env ds in
exp_ lvl env' preupgrade;
exp_ lvl env' postupgrade;
exp_ lvl env' heartbeat;
exp_ lvl env' timer;
exp_ lvl env' inspect;
exp_ lvl env' low_memory;
exp_ lvl env' stable_record;
surely_false
in
Expand Down Expand Up @@ -228,7 +229,7 @@ and block lvl env (ds, body) =
and comp_unit = function
| LibU _ -> raise (Invalid_argument "cannot compile library")
| ProgU ds -> decs_ TopLvl M.empty ds
| ActorU (as_opt, ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; stable_record; stable_type}, typ) ->
| ActorU (as_opt, ds, fs, {meta; preupgrade; postupgrade; heartbeat; timer; inspect; low_memory; stable_record; stable_type}, typ) ->
let env = match as_opt with
| None -> M.empty
| Some as_ -> args TopLvl M.empty as_
Expand All @@ -239,6 +240,7 @@ and comp_unit = function
exp_ TopLvl env' heartbeat;
exp_ TopLvl env' timer;
exp_ TopLvl env' inspect;
exp_ TopLvl env' low_memory;
exp_ TopLvl env' stable_record

let analyze ((cu, _flavor) : prog) =
Expand Down
Loading
Loading