From 1511fba2ff050b9fed8482f4bbcf935accb87496 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 13 Sep 2024 12:08:01 +0200 Subject: [PATCH] feat: burning cycles programmatically (#4690) This adds a new primitive `(prim "cyclesBurn")` and a `prim.mo` function `cyclesBurn : Nat -> Nat`. It can be used to burn (some of the) canister's cycles programmatically using the system interface `ic0.cycles_burn128`. --- Building.md | 24 ++++++------ Changelog.md | 5 ++- src/codegen/compile_classical.ml | 46 ++++++++++++++-------- src/codegen/compile_enhanced.ml | 54 ++++++++++++++++---------- src/ir_def/arrange_ir.ml | 1 + src/ir_def/check_ir.ml | 2 +- src/ir_def/construct.ml | 3 +- src/ir_def/ir.ml | 2 + src/lowering/desugar.ml | 2 + src/prelude/prim.mo | 4 ++ test/fail/ok/no-timer-canc.tc.ok | 1 + test/fail/ok/no-timer-set.tc.ok | 1 + test/run-drun/basic-cycles.mo | 2 + test/run-drun/empty-actor-classical.mo | 4 +- test/run-drun/ok/basic-cycles.tc.ok | 2 +- 15 files changed, 99 insertions(+), 54 deletions(-) diff --git a/Building.md b/Building.md index 2c210777c0f..f89b5389e6d 100644 --- a/Building.md +++ b/Building.md @@ -56,11 +56,11 @@ For more details on our CI and CI setup, see `CI.md`. ## Making releases -We make frequent releases, at least weekly. The steps to make a release (say, version 0.12.1) are: +We make frequent releases, at least weekly. The steps to make a release (say, version 0.13.1) are: * Make sure that the top section of `Changelog.md` has a title like - ## 0.12.1 (2024-07-29) + ## 0.13.1 (2024-09-10) with today’s date. @@ -77,18 +77,18 @@ We make frequent releases, at least weekly. The steps to make a release (say, ve * Define a shell variable `export MOC_MINOR=1` - * Look at `git log --first-parent 0.12.$(expr $MOC_MINOR - 1)..HEAD` and check + * Look at `git log --first-parent 0.13.$(expr $MOC_MINOR - 1)..HEAD` and check that everything relevant is mentioned in the changelog section, and possibly clean it up a bit, curating the information for the target audience. - * `git commit -am "chore: Releasing 0.12."$MOC_MINOR` + * `git commit -am "chore: Releasing 0.13."$MOC_MINOR` * Create a PR from this commit, and label it `automerge-squash`. E.g. - with `git push origin HEAD:$USER/0.12.$MOC_MINOR`. Mergify will + with `git push origin HEAD:$USER/0.13.$MOC_MINOR`. Mergify will merge it into `master` without additional approval, but it will take some time as the title (version number) enters into the `nix` dependency tracking. * `git switch master; git pull --rebase`. The release commit should be your `HEAD` - * `git tag 0.12.$MOC_MINOR -m "Motoko 0.12."$MOC_MINOR` - * `git push origin 0.12.$MOC_MINOR` + * `git tag 0.13.$MOC_MINOR -m "Motoko 0.13."$MOC_MINOR` + * `git push origin 0.13.$MOC_MINOR` Pushing the tag should cause GitHub Actions to create a “Release” on the GitHub project. This will fail if the changelog is not in order (in this case, fix and @@ -102,12 +102,12 @@ branch to the `next-moc` branch. * Wait ca. 5min after releasing to give the CI/CD pipeline time to upload the release artifacts * Change into `motoko-base` * `git switch next-moc; git pull` -* `git switch -c $USER/update-moc-0.12.$MOC_MINOR` +* `git switch -c $USER/update-moc-0.13.$MOC_MINOR` * Update the `CHANGELOG.md` file with an entry at the top * Update the `moc_version` env variable in `.github/workflows/{ci, package-set}.yml` and `mops.toml` to the new released version: - `perl -pi -e "s/moc_version: \"0\.12\.\\d+\"/moc_version: \"0.12.$MOC_MINOR\"/g; s/moc = \"0\.12\.\\d+\"/moc = \"0.12.$MOC_MINOR\"/g; s/version = \"0\.12\.\\d+\"/version = \"0.12.$MOC_MINOR\"/g" .github/workflows/ci.yml .github/workflows/package-set.yml mops.toml` -* `git add .github/ CHANGELOG.md mops.toml && git commit -m "Motoko 0.12."$MOC_MINOR` + `perl -pi -e "s/moc_version: \"0\.13\.\\d+\"/moc_version: \"0.13.$MOC_MINOR\"/g; s/moc = \"0\.13\.\\d+\"/moc = \"0.13.$MOC_MINOR\"/g; s/version = \"0\.13\.\\d+\"/version = \"0.13.$MOC_MINOR\"/g" .github/workflows/ci.yml .github/workflows/package-set.yml mops.toml` +* `git add .github/ CHANGELOG.md mops.toml && git commit -m "Motoko 0.13."$MOC_MINOR` * Revise `CHANGELOG.md`, adding a top entry for the release * You can `git push` now @@ -117,8 +117,8 @@ repo by a scheduled `niv-updater-action`. Finally tag the base release (so the documentation interpreter can do the right thing): * `git switch master && git pull` -* `git tag moc-0.12.$MOC_MINOR` -* `git push origin moc-0.12.$MOC_MINOR` +* `git tag moc-0.13.$MOC_MINOR` +* `git push origin moc-0.13.$MOC_MINOR` If you want to update the portal documentation, typically to keep in sync with a `dfx` release, follow the instructions in https://github.com/dfinity/portal/blob/master/MAINTENANCE.md. diff --git a/Changelog.md b/Changelog.md index b3efc5a4a31..8c2e608e4fc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,10 @@ * motoko (`moc`) - * **For beta testing:** Support __enhanced orthogonal persistence__, enabled with new moc flag `--enhanced-orthogonal-persistence` (#4193). + * Added a new primitive `cyclesBurn : Nat -> Nat` for burning the canister's cycles + programmatically (#4690). + + * **For beta testing:** Support __enhanced orthogonal persistence__, enabled with new `moc` flag `--enhanced-orthogonal-persistence` (#4193). This implements scalable and efficient orthogonal persistence (stable variables) for Motoko: * The Wasm main memory (heap) is retained on upgrade with new program versions directly picking up this state. diff --git a/src/codegen/compile_classical.ml b/src/codegen/compile_classical.ml index 18448230778..354c755307a 100644 --- a/src/codegen/compile_classical.ml +++ b/src/codegen/compile_classical.ml @@ -5073,6 +5073,7 @@ module IC = struct E.add_func_import env "ic0" "msg_cycles_available128" [I32Type] []; E.add_func_import env "ic0" "msg_cycles_refunded128" [I32Type] []; E.add_func_import env "ic0" "msg_cycles_accept128" [I64Type; I64Type; I32Type] []; + E.add_func_import env "ic0" "cycles_burn128" [I64Type; I64Type; I32Type] []; E.add_func_import env "ic0" "certified_data_set" (i32s 2) []; E.add_func_import env "ic0" "data_certificate_present" [] [I32Type]; E.add_func_import env "ic0" "data_certificate_size" [] [I32Type]; @@ -5093,8 +5094,7 @@ module IC = struct E.add_func_import env "ic0" "stable64_grow" [I64Type] [I64Type]; E.add_func_import env "ic0" "time" [] [I64Type]; if !Flags.global_timer then - E.add_func_import env "ic0" "global_timer_set" [I64Type] [I64Type]; - () + E.add_func_import env "ic0" "global_timer_set" [I64Type] [I64Type] let system_imports env = match E.mode env with @@ -5490,48 +5490,49 @@ module IC = struct let cycle_balance env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "canister_cycle_balance128" | _ -> E.trap_with env "cannot read balance when running locally" let cycles_add env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "call_cycles_add128" | _ -> E.trap_with env "cannot accept cycles when running locally" let cycles_accept env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "msg_cycles_accept128" | _ -> E.trap_with env "cannot accept cycles when running locally" let cycles_available env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "msg_cycles_available128" | _ -> E.trap_with env "cannot get cycles available when running locally" let cycles_refunded env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "msg_cycles_refunded128" | _ -> E.trap_with env "cannot get cycles refunded when running locally" + let cycles_burn env = + match E.mode env with + | Flags.(ICMode | RefMode) -> + system_call env "cycles_burn128" + | _ -> + E.trap_with env "cannot burn cycles when running locally" + let set_certified_data env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> Blob.as_ptr_len env ^^ system_call env "certified_data_set" | _ -> @@ -5539,8 +5540,7 @@ module IC = struct let get_certificate env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "data_certificate_present" ^^ G.if1 I32Type begin @@ -5649,6 +5649,18 @@ module Cycles = struct ) ) + let burn env = + Func.share_code1 Func.Always env "cycle_burn" ("cycles", I32Type) [I32Type] (fun env get_x -> + Stack.with_words env "dst" 4l (fun get_dst -> + get_x ^^ + to_two_word64 env ^^ + get_dst ^^ + IC.cycles_burn env ^^ + get_dst ^^ + from_word128_ptr env + ) + ) + end (* Cycles *) (* Low-level, almost raw access to IC stable memory. @@ -12067,6 +12079,8 @@ and compile_prim_invocation (env : E.t) ae p es at = SR.Vanilla, Cycles.available env | SystemCyclesRefundedPrim, [] -> SR.Vanilla, Cycles.refunded env + | SystemCyclesBurnPrim, [e1] -> + SR.Vanilla, compile_exp_vanilla env ae e1 ^^ Cycles.burn env | SetCertifiedData, [e1] -> SR.unit, compile_exp_vanilla env ae e1 ^^ IC.set_certified_data env diff --git a/src/codegen/compile_enhanced.ml b/src/codegen/compile_enhanced.ml index 080f1acfb7d..2a1351135ac 100644 --- a/src/codegen/compile_enhanced.ml +++ b/src/codegen/compile_enhanced.ml @@ -4731,6 +4731,7 @@ module IC = struct E.add_func_import env "ic0" "msg_cycles_available128" [I64Type] []; E.add_func_import env "ic0" "msg_cycles_refunded128" [I64Type] []; E.add_func_import env "ic0" "msg_cycles_accept128" (i64s 3) []; + E.add_func_import env "ic0" "cycles_burn128" (i64s 3) []; E.add_func_import env "ic0" "certified_data_set" (i64s 2) []; E.add_func_import env "ic0" "data_certificate_present" [] [I32Type]; E.add_func_import env "ic0" "data_certificate_size" [] [I64Type]; @@ -4751,8 +4752,7 @@ module IC = struct E.add_func_import env "ic0" "stable64_grow" [I64Type] [I64Type]; E.add_func_import env "ic0" "time" [] [I64Type]; if !Flags.global_timer then - E.add_func_import env "ic0" "global_timer_set" [I64Type] [I64Type]; - () + E.add_func_import env "ic0" "global_timer_set" [I64Type] [I64Type] let system_imports env = match E.mode env with @@ -5221,48 +5221,49 @@ module IC = struct let cycle_balance env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "canister_cycle_balance128" | _ -> E.trap_with env "cannot read balance when running locally" let cycles_add env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "call_cycles_add128" | _ -> E.trap_with env "cannot accept cycles when running locally" let cycles_accept env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "msg_cycles_accept128" | _ -> E.trap_with env "cannot accept cycles when running locally" let cycles_available env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "msg_cycles_available128" | _ -> E.trap_with env "cannot get cycles available when running locally" let cycles_refunded env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "msg_cycles_refunded128" | _ -> E.trap_with env "cannot get cycles refunded when running locally" + let cycles_burn env = + match E.mode env with + | Flags.(ICMode | RefMode) -> + system_call env "cycles_burn128" + | _ -> + E.trap_with env "cannot burn cycles when running locally" + let set_certified_data env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> Blob.as_ptr_len env ^^ system_call env "certified_data_set" | _ -> @@ -5270,8 +5271,7 @@ module IC = struct let get_certificate env = match E.mode env with - | Flags.ICMode - | Flags.RefMode -> + | Flags.(ICMode | RefMode) -> system_call env "data_certificate_present" ^^ Bool.from_rts_int32 ^^ E.if1 I64Type @@ -5338,7 +5338,7 @@ module Cycles = struct let balance env = Func.share_code0 Func.Always env "cycle_balance" [I64Type] (fun env -> - Stack.with_words env "dst" 4L (fun get_dst -> + Stack.with_words env "dst" 2L (fun get_dst -> get_dst ^^ IC.cycle_balance env ^^ get_dst ^^ @@ -5355,7 +5355,7 @@ module Cycles = struct let accept env = Func.share_code1 Func.Always env "cycle_accept" ("cycles", I64Type) [I64Type] (fun env get_x -> - Stack.with_words env "dst" 4L (fun get_dst -> + Stack.with_words env "dst" 2L (fun get_dst -> get_x ^^ to_two_word64 env ^^ get_dst ^^ @@ -5367,7 +5367,7 @@ module Cycles = struct let available env = Func.share_code0 Func.Always env "cycle_available" [I64Type] (fun env -> - Stack.with_words env "dst" 4L (fun get_dst -> + Stack.with_words env "dst" 2L (fun get_dst -> get_dst ^^ IC.cycles_available env ^^ get_dst ^^ @@ -5377,7 +5377,7 @@ module Cycles = struct let refunded env = Func.share_code0 Func.Always env "cycle_refunded" [I64Type] (fun env -> - Stack.with_words env "dst" 4L (fun get_dst -> + Stack.with_words env "dst" 2L (fun get_dst -> get_dst ^^ IC.cycles_refunded env ^^ get_dst ^^ @@ -5385,6 +5385,18 @@ module Cycles = struct ) ) + let burn env = + Func.share_code1 Func.Always env "cycle_burn" ("cycles", I64Type) [I64Type] (fun env get_x -> + Stack.with_words env "dst" 2L (fun get_dst -> + get_x ^^ + to_two_word64 env ^^ + get_dst ^^ + IC.cycles_burn env ^^ + get_dst ^^ + from_word128_ptr env + ) + ) + end (* Cycles *) (* Low-level, almost raw access to IC stable memory. @@ -12245,6 +12257,8 @@ and compile_prim_invocation (env : E.t) ae p es at = SR.Vanilla, Cycles.available env | SystemCyclesRefundedPrim, [] -> SR.Vanilla, Cycles.refunded env + | SystemCyclesBurnPrim, [e1] -> + SR.Vanilla, compile_exp_vanilla env ae e1 ^^ Cycles.burn env | SetCertifiedData, [e1] -> SR.unit, compile_exp_vanilla env ae e1 ^^ IC.set_certified_data env diff --git a/src/ir_def/arrange_ir.ml b/src/ir_def/arrange_ir.ml index d476aa83ec7..02cb40bde7d 100644 --- a/src/ir_def/arrange_ir.ml +++ b/src/ir_def/arrange_ir.ml @@ -101,6 +101,7 @@ and prim = function | SystemCyclesAvailablePrim -> Atom "SystemCyclesAvailablePrim" | SystemCyclesBalancePrim -> Atom "SystemCyclesBalancePrim" | SystemCyclesRefundedPrim -> Atom "SystemCyclesRefundedPrim" + | SystemCyclesBurnPrim -> Atom "SystemCyclesBurnPrim" | SetCertifiedData -> Atom "SetCertifiedData" | GetCertificate -> Atom "GetCertificate" | OtherPrim s -> Atom s diff --git a/src/ir_def/check_ir.ml b/src/ir_def/check_ir.ml index a70c10f47d1..0d9f45279a6 100644 --- a/src/ir_def/check_ir.ml +++ b/src/ir_def/check_ir.ml @@ -676,7 +676,7 @@ let rec check_exp env (exp:Ir.exp) : unit = (* Cycles *) | (SystemCyclesBalancePrim | SystemCyclesAvailablePrim | SystemCyclesRefundedPrim), [] -> T.nat <: t - | SystemCyclesAcceptPrim, [e1] -> + | (SystemCyclesAcceptPrim | SystemCyclesBurnPrim), [e1] -> typ e1 <: T.nat; T.nat <: t | SystemCyclesAddPrim, [e1] -> diff --git a/src/ir_def/construct.ml b/src/ir_def/construct.ml index 5de2e5ef0a7..6564490287e 100644 --- a/src/ir_def/construct.ml +++ b/src/ir_def/construct.ml @@ -107,7 +107,8 @@ let primE prim es = | RelPrim _ -> T.bool | SerializePrim _ -> T.blob | SystemCyclesAvailablePrim - | SystemCyclesAcceptPrim -> T.nat + | SystemCyclesAcceptPrim + | SystemCyclesBurnPrim -> T.nat | DeserializePrim ts -> T.seq ts | DeserializeOptPrim ts -> T.Opt (T.seq ts) | OtherPrim "trap" -> T.Non diff --git a/src/ir_def/ir.ml b/src/ir_def/ir.ml index 413b56506dd..18b28c8b9ff 100644 --- a/src/ir_def/ir.ml +++ b/src/ir_def/ir.ml @@ -159,6 +159,7 @@ and prim = | SystemCyclesAvailablePrim | SystemCyclesBalancePrim | SystemCyclesRefundedPrim + | SystemCyclesBurnPrim | SetCertifiedData | GetCertificate @@ -308,6 +309,7 @@ let map_prim t_typ t_id p = | SystemCyclesAvailablePrim | SystemCyclesBalancePrim | SystemCyclesRefundedPrim + | SystemCyclesBurnPrim | SetCertifiedData | GetCertificate | OtherPrim _ -> p diff --git a/src/lowering/desugar.ml b/src/lowering/desugar.ml index 9c498035d41..bcbb995e46d 100644 --- a/src/lowering/desugar.ml +++ b/src/lowering/desugar.ml @@ -183,6 +183,8 @@ and exp' at note = function I.PrimE (I.SystemCyclesAcceptPrim, [exp e]) | S.CallE ({it=S.AnnotE ({it=S.PrimE "cyclesAdd";_},_);_}, _, e) -> I.PrimE (I.SystemCyclesAddPrim, [exp e]) + | S.CallE ({it=S.AnnotE ({it=S.PrimE "cyclesBurn";_},_);_}, _, e) -> + I.PrimE (I.SystemCyclesBurnPrim, [exp e]) (* Certified data *) | S.CallE ({it=S.AnnotE ({it=S.PrimE "setCertifiedData";_},_);_}, _, e) -> I.PrimE (I.SetCertifiedData, [exp e]) diff --git a/src/prelude/prim.mo b/src/prelude/prim.mo index f900e2063cf..209337400e0 100644 --- a/src/prelude/prim.mo +++ b/src/prelude/prim.mo @@ -351,6 +351,10 @@ func cyclesAdd(amount : Nat) : () { }; }; +func cyclesBurn(amount : Nat) : Nat { + (prim "cyclesBurn" : Nat -> Nat) amount; +}; + // certified data func setCertifiedData(data : Blob) = (prim "setCertifiedData" : Blob -> ()) data; func getCertificate() : ?Blob = (prim "getCertificate" : () -> ?Blob)(); diff --git a/test/fail/ok/no-timer-canc.tc.ok b/test/fail/ok/no-timer-canc.tc.ok index 7918c4887dd..e1877945e59 100644 --- a/test/fail/ok/no-timer-canc.tc.ok +++ b/test/fail/ok/no-timer-canc.tc.ok @@ -88,6 +88,7 @@ no-timer-canc.mo:3.10-3.21: type error [M0119], object field cancelTimer is not cyclesAdd : Nat -> (); cyclesAvailable : () -> Nat; cyclesBalance : () -> Nat; + cyclesBurn : Nat -> Nat; cyclesRefunded : () -> Nat; debugPrint : Text -> (); debugPrintChar : Char -> (); diff --git a/test/fail/ok/no-timer-set.tc.ok b/test/fail/ok/no-timer-set.tc.ok index a015c4bae8e..f4951c74796 100644 --- a/test/fail/ok/no-timer-set.tc.ok +++ b/test/fail/ok/no-timer-set.tc.ok @@ -88,6 +88,7 @@ no-timer-set.mo:3.10-3.18: type error [M0119], object field setTimer is not cont cyclesAdd : Nat -> (); cyclesAvailable : () -> Nat; cyclesBalance : () -> Nat; + cyclesBurn : Nat -> Nat; cyclesRefunded : () -> Nat; debugPrint : Text -> (); debugPrintChar : Char -> (); diff --git a/test/run-drun/basic-cycles.mo b/test/run-drun/basic-cycles.mo index 42b3b159282..73cfd970224 100644 --- a/test/run-drun/basic-cycles.mo +++ b/test/run-drun/basic-cycles.mo @@ -8,6 +8,7 @@ actor a { let available : () -> Nat = Prim.cyclesAvailable; let accept : Nat -> Nat = Prim.cyclesAccept; let add : Nat -> () = Prim.cyclesAdd; + let burn : Nat -> Nat = Prim.cyclesBurn; let refunded : () -> Nat = Prim.cyclesRefunded; @@ -91,6 +92,7 @@ actor a { public func go() : async (){ await overflow(); await iter(); + assert 1000 == burn 1000 } }; diff --git a/test/run-drun/empty-actor-classical.mo b/test/run-drun/empty-actor-classical.mo index 564bf9d4ddd..babae5e5f94 100644 --- a/test/run-drun/empty-actor-classical.mo +++ b/test/run-drun/empty-actor-classical.mo @@ -8,11 +8,11 @@ actor {}; // CHECK-NEXT: call $trans_state // CHECK-NEXT: call $init // CHECK-NEXT: i32.const 0 -// CHECK-NEXT: call 32 +// CHECK-NEXT: call 33 // CHECK-NEXT: global.set 4 // CHECK-NEXT: call ${{copying_gc|compacting_gc|generational_gc|incremental_gc}} // CHECK-NEXT: i32.const 0 -// CHECK-NEXT: call 32 +// CHECK-NEXT: call 33 // CHECK-NEXT: global.get 4 // CHECK-NEXT: i64.sub // CHECK-NEXT: global.set 5 diff --git a/test/run-drun/ok/basic-cycles.tc.ok b/test/run-drun/ok/basic-cycles.tc.ok index 6b3df344d9d..d80be283628 100644 --- a/test/run-drun/ok/basic-cycles.tc.ok +++ b/test/run-drun/ok/basic-cycles.tc.ok @@ -1 +1 @@ -basic-cycles.mo:12.7-12.15: warning [M0194], unused identifier refunded (delete or rename to wildcard `_` or `_refunded`) +basic-cycles.mo:13.7-13.15: warning [M0194], unused identifier refunded (delete or rename to wildcard `_` or `_refunded`)