Skip to content

Commit e4e1c6e

Browse files
authored
Backend: Compile captured actor class parameters statically (#2022)
This fixes #1847. This went wrong before: * All top-level functions must be static (i.e not represented by a dynamic closure) * This means that everything they may close over, even values computed in the top-level scope (effectively by the init function) must be in static memory, at a well-known location. For the top-level `decs`, this is done by the `AllocHow` analysis. * Top-level parameters were added later. They can _also_ be captured, but were previously only allocated as locals. This was #1847. * This did not manifest when these parameters (patterns) were “ɩ-expanded”, e.g. if we replace the parameter `p` with `p'` and `let p = p'` in the top-level scope. This was effectively Claudio’s work-around, that we remove here. The fix is to choose the location of the variable of a parameter based on whether it is captured anywhere. This is in a way a simplistic version of the `AllocHow` analysis. As a side-effect, this simplifies the code (no need to pass around `setters`, we can use `Var.set_val` these days.
1 parent db6c1e5 commit e4e1c6e

File tree

8 files changed

+58
-35
lines changed

8 files changed

+58
-35
lines changed

src/codegen/compile.ml

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,16 @@ module MutBox = struct
12331233
(* Mutable heap objects *)
12341234

12351235
let field = Tagged.header_size
1236+
1237+
let alloc env =
1238+
Tagged.obj env Tagged.MutBox [ compile_unboxed_zero ]
1239+
1240+
let static env =
1241+
let tag = bytes_of_int32 (Tagged.int_of_tag Tagged.MutBox) in
1242+
let zero = bytes_of_int32 0l in
1243+
let ptr = E.add_mutable_static_bytes env (tag ^ zero) in
1244+
E.add_static_root env ptr;
1245+
ptr
12361246
end
12371247

12381248

@@ -4966,14 +4976,21 @@ module VarEnv = struct
49664976
(add_local_local env ae name i, i)
49674977

49684978
(* Adds the names to the environment and returns a list of setters *)
4969-
let rec add_argument_locals env (ae : t) = function
4970-
| [] -> (ae, [])
4979+
let rec add_arguments env (ae : t) as_local = function
4980+
| [] -> ae
49714981
| (name :: names) ->
4972-
let i = E.add_anon_local env I32Type in
4973-
E.add_local_name env i name;
4974-
let ae' = { ae with vars = NameEnv.add name (Local i) ae.vars } in
4975-
let (ae_final, setters) = add_argument_locals env ae' names
4976-
in (ae_final, G.i (LocalSet (nr i)) :: setters)
4982+
if as_local name then
4983+
let i = E.add_anon_local env I32Type in
4984+
E.add_local_name env i name;
4985+
let ae' = { ae with vars = NameEnv.add name (Local i) ae.vars } in
4986+
add_arguments env ae' as_local names
4987+
else (* needs to go to static memory *)
4988+
let ptr = MutBox.static env in
4989+
let ae' = add_local_heap_static ae name ptr in
4990+
add_arguments env ae' as_local names
4991+
4992+
let add_argument_locals env (ae : t) =
4993+
add_arguments env ae (fun _ -> true)
49774994

49784995
let add_label (ae : t) name (d : G.depth) =
49794996
{ ae with labels = NameEnv.add name d ae.labels }
@@ -5149,9 +5166,9 @@ module FuncDec = struct
51495166
(* Deserialize argument and add params to the environment *)
51505167
let arg_names = List.map (fun a -> a.it) args in
51515168
let arg_tys = List.map (fun a -> a.note) args in
5152-
let (ae1, setters) = VarEnv.add_argument_locals env ae0 arg_names in
5169+
let ae1 = VarEnv.add_argument_locals env ae0 arg_names in
51535170
Serialization.deserialize env arg_tys ^^
5154-
G.concat (List.rev setters) ^^
5171+
G.concat_map (Var.set_val env ae1) (List.rev arg_names) ^^
51555172
mk_body env ae1 ^^
51565173
message_cleanup env sort
51575174
))
@@ -5640,15 +5657,10 @@ module AllocHow = struct
56405657
(ae1, G.nop)
56415658
| StoreHeap ->
56425659
let (ae1, i) = VarEnv.add_local_with_offset env ae name 1l in
5643-
let alloc_code =
5644-
Tagged.obj env Tagged.MutBox [ compile_unboxed_zero ] ^^
5645-
G.i (LocalSet (nr i)) in
5660+
let alloc_code = MutBox.alloc env ^^ G.i (LocalSet (nr i)) in
56465661
(ae1, alloc_code)
56475662
| StoreStatic ->
5648-
let tag = bytes_of_int32 (Tagged.int_of_tag Tagged.MutBox) in
5649-
let zero = bytes_of_int32 0l in
5650-
let ptr = E.add_mutable_static_bytes env (tag ^ zero) in
5651-
E.add_static_root env ptr;
5663+
let ptr = MutBox.static env in
56525664
let ae1 = VarEnv.add_local_heap_static ae name ptr in
56535665
(ae1, G.nop)
56545666

@@ -7066,8 +7078,7 @@ and compile_exp (env : E.t) ae exp =
70667078
let (ae1, i) = VarEnv.add_local_with_offset env ae name 1l in
70677079
let sr, code = compile_exp env ae1 e in
70687080
sr,
7069-
Tagged.obj env Tagged.MutBox [ compile_unboxed_zero ] ^^
7070-
G.i (LocalSet (nr i)) ^^
7081+
MutBox.alloc env ^^ G.i (LocalSet (nr i)) ^^
70717082
code
70727083
| DefineE (name, _, e) ->
70737084
SR.unit,
@@ -7598,11 +7609,15 @@ and main_actor as_opt mod_env ds fs up =
75987609
Func.define_built_in mod_env "init" [] [] (fun env ->
75997610
let ae0 = VarEnv.empty_ae in
76007611

7612+
let captured = Freevars.captured_vars (Freevars.actor ds fs up) in
7613+
76017614
(* Add any params to the environment *)
7615+
(* Captured ones need to go into static memory, the rest into locals *)
76027616
let args = match as_opt with None -> [] | Some as_ -> as_ in
76037617
let arg_names = List.map (fun a -> a.it) args in
76047618
let arg_tys = List.map (fun a -> a.note) args in
7605-
let (ae1, setters) = VarEnv.add_argument_locals env ae0 arg_names in
7619+
let as_local n = not (Freevars.S.mem n captured) in
7620+
let ae1 = VarEnv.add_arguments env ae0 as_local arg_names in
76067621

76077622
(* Reverse the fs, to a map from variable to exported name *)
76087623
let v2en = E.NameEnv.from_list (List.map (fun f -> (f.it.var, f.it.name)) fs) in
@@ -7623,7 +7638,7 @@ and main_actor as_opt mod_env ds fs up =
76237638
Dfinity.export_upgrade_methods env;
76247639

76257640
(* Deserialize any arguments *)
7626-
(match as_opt with
7641+
begin match as_opt with
76277642
| None
76287643
| Some [] ->
76297644
(* Liberally accept empty as well as unit argument *)
@@ -7632,7 +7647,8 @@ and main_actor as_opt mod_env ds fs up =
76327647
G.if_ [] (Serialization.deserialize env arg_tys) G.nop
76337648
| Some (_ :: _) ->
76347649
Serialization.deserialize env arg_tys ^^
7635-
G.concat (List.rev setters)) ^^
7650+
G.concat_map (Var.set_val env ae1) (List.rev arg_names)
7651+
end ^^
76367652
(* Continue with decls *)
76377653
decls_codeW G.nop
76387654
)

src/ir_def/check_ir.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ and check_args env args =
840840
check env a.at (not (T.Env.mem a.it ve))
841841
"duplicate binding for %s in argument list" a.it;
842842
check_typ env a.note;
843-
let val_info = {typ = a.note; const = false; loc_known = false} in
843+
let val_info = {typ = a.note; const = false; loc_known = env.lvl = TopLvl } in
844844
let env' = T.Env.add a.it val_info ve in
845845
go env' as_
846846
in go T.Env.empty args

src/ir_def/freevars.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,13 @@ let rec exp e : f = match e.it with
111111
| DeclareE (i, t, e) -> exp e // i
112112
| DefineE (i, m, e) -> id i ++ exp e
113113
| FuncE (x, s, c, tp, as_, t, e) -> under_lambda (exp e /// args as_)
114-
| ActorE (ds, fs, u, _) -> close (decs ds +++ fields fs +++ upgrade u)
114+
| ActorE (ds, fs, u, _) -> actor ds fs u
115115
| NewObjE (_, fs, _) -> fields fs
116116
| TryE (e, cs) -> exp e ++ cases cs
117117
| SelfCallE (_, e1, e2, e3) -> under_lambda (exp e1) ++ exp e2 ++ exp e3
118118

119+
and actor ds fs u = close (decs ds +++ fields fs +++ upgrade u)
120+
119121
and upgrade {pre; post} = under_lambda (exp pre) ++ under_lambda (exp post)
120122

121123
and exps es : f = unions exp es

src/ir_passes/const.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ type env = info M.t
120120

121121

122122
let no_info = { loc_known = false; const = surely_false }
123-
let arg env a = M.add a.it no_info env
124-
let args env as_ = List.fold_left arg env as_
123+
let arg lvl env a = M.add a.it { no_info with loc_known = lvl = TopLvl } env
124+
let args lvl env as_ = List.fold_left (arg lvl) env as_
125125

126126
let rec pat env p = match p.it with
127127
| WildP
@@ -153,7 +153,7 @@ let rec exp lvl (env : env) e : lazy_bool =
153153
match e.it with
154154
| VarE v -> (find v env).const
155155
| FuncE (x, s, c, tp, as_ , ts, body) ->
156-
exp_ NotTopLvl (args env as_) body;
156+
exp_ NotTopLvl (args NotTopLvl env as_) body;
157157
begin match s, lvl with
158158
(* shared functions are not const for now *)
159159
| Type.Shared _, _ -> surely_false
@@ -271,7 +271,7 @@ and comp_unit = function
271271
| ActorU (as_opt, ds, fs, {pre; post}, typ) ->
272272
let env = match as_opt with
273273
| None -> M.empty
274-
| Some as_ -> args M.empty as_
274+
| Some as_ -> args TopLvl M.empty as_
275275
in
276276
let (env', _) = decs TopLvl env ds in
277277
exp_ TopLvl env' pre;

src/lowering/desugar.ml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -452,11 +452,7 @@ and dec' at n d = match d with
452452
let id' = {id with note = ()} in
453453
let sort, _, _, _, _ = Type.as_func n.S.note_typ in
454454
let op = match sp.it with
455-
| T.Local ->
456-
if s.it = T.Actor then (* HACK: work around for issue #1847 (also below) *)
457-
Some { it = S.WildP; at = no_region; note = T.ctxt }
458-
else
459-
None
455+
| T.Local -> None
460456
| T.Shared (_, p) -> Some p in
461457
let inst = List.map
462458
(fun tb ->
@@ -764,9 +760,7 @@ let transform_unit_body (u : S.comp_unit_body) : Ir.comp_unit =
764760
| S.ActorClassU (sp, typ_id, p, _, self_id, fields) ->
765761
let fun_typ = u.note.S.note_typ in
766762
let op = match sp.it with
767-
| T.Local ->
768-
(* HACK: work around for issue #1847 (also above) *)
769-
Some { it = S.WildP; at = no_region; note = T.ctxt }
763+
| T.Local -> None
770764
| T.Shared (_, p) -> Some p in
771765
let args, wrap, control, _n_res = to_args fun_typ op p in
772766
let obj_typ =

test/run-drun/issue-1847.mo

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
actor class (t : Text) {
2+
3+
public func f () : async Text { t };
4+
5+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ingress Completed: Reply: 0x4449444c016c01b3c4b1f2046801000112010000000000000000000000000000000001
2+
ingress Err: IC0503: Canister 75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q trapped explicitly: IDL error: empty input
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
→ update create_canister()
2+
← completed: (record {1313628723 = service "cvccv-qqaaq-aaaaa-aaaaa-c"})
3+
→ update install_code(record {4849238 = blob ""; 265441191 = blob "\00asm\01\00\0…
4+
← rejected (RC_CANISTER_ERROR): Initialization trapped: EvalTrapError :0.1 "canister trapped explicitly: IDL error: empty input"

0 commit comments

Comments
 (0)