diff --git a/README.md b/README.md index f681309301f..97dc9fc8043 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ A simple language for writing Dfinity actors. +## Git submodules + +Note: The `vendor/wasm-spec/` directory is empty until you issue this command: + +``` +git submodule update --init --recursive +``` + ## Installation using Nix To install the `asc` binary into your nix environment, use diff --git a/src/pipeline.ml b/src/pipeline.ml index fc36ed21435..dcc0bfeb706 100644 --- a/src/pipeline.ml +++ b/src/pipeline.ml @@ -9,6 +9,12 @@ type stat_env = Typing.scope type dyn_env = Interpret.env type env = stat_env * dyn_env +(* TEMP --- include these modules in the build, even they they are unused: *) +module Syntaxops_ir = Syntaxops_ir +let _ = Syntaxops_ir.fresh (* a "use" of the module, to suppress build errors *) +module Tailcall_ir = Tailcall_ir +let _ = Tailcall_ir.prog (* a "use" of the module, to suppress build errors *) + (* Diagnostics *) let phase heading name = diff --git a/src/syntaxops_ir.ml b/src/syntaxops_ir.ml new file mode 100644 index 00000000000..9b02641cbee --- /dev/null +++ b/src/syntaxops_ir.ml @@ -0,0 +1,407 @@ +(* WIP translation of syntaxops to use IR in place of Source *) + +open Source +open Ir +open Effect + +module S = Syntax +module T = Type + +type var = exp + +(* Mutabilities *) + +let varM = S.Var@@no_region +let constM = S.Const@@no_region + +(* Field names *) + +let nameN s = (S.Name s)@@no_region + +let nextN = nameN "next" + +(* Identifiers *) + +let idE id typ = + {it = VarE id; + at = no_region; + note = {S.note_typ = typ; + S.note_eff = T.Triv} + } + +let id_of_exp x = + match x.it with + | VarE x -> x + | _ -> failwith "Impossible: id_of_exp" + +(* Fresh id generation *) + +let id_stamp = ref 0 + +let fresh () = + let name = Printf.sprintf "$%i" (!id_stamp) in + id_stamp := !id_stamp + 1; + name + +let fresh_lab () = + let name = fresh () in + name@@no_region + +let fresh_id typ = + let name = fresh () in + idE (name@@no_region) typ + + +(* Patterns *) + +let varP x = + { it=VarP (id_of_exp x); + at = x.at; + note = x.note.S.note_typ + } + +let tupP pats = + {it = TupP pats; + note = T.Tup (List.map (fun p -> p.note) pats); + at = no_region} + +let seqP ps = + match ps with + | [p] -> p + | ps -> tupP ps + +let as_seqP p = + match p.it with + | TupP ps -> ps + | _ -> [p] + +(* Primitives *) + +let primE name typ = + {it = PrimE name; + at = no_region; + note = {S.note_typ = typ; + S.note_eff = T.Triv} + } + +(* tuples *) + +let projE e n = + match typ e with + | T.Tup ts -> + {it = ProjE(e,n); + note = {S.note_typ = List.nth ts n; + S.note_eff = eff e}; + at = no_region; + } + | _ -> failwith "projE" + +let blockE decs = + let rec typ_decs decs = + match decs with + | [] -> T.unit + | [dec] -> typ dec + | _::decs -> typ_decs decs + in + let es = List.map eff decs in + let typ = typ_decs decs in + let e = List.fold_left max_eff Type.Triv es in + { it = BlockE (decs, typ); + at = no_region; + note = {S.note_typ = typ; + S.note_eff = e} + } + +let textE s = + { it = LitE (S.TextLit s); + at = no_region; + note = {S.note_typ = T.Prim T.Text; + S.note_eff = T.Triv;} + } + + +let unitE = + { it = TupE []; + at = no_region; + note = {S.note_typ = T.Tup []; + S.note_eff = T.Triv} + } + +let boolE b = + { it = LitE (S.BoolLit b); + at = no_region; + note = {S.note_typ = T.bool; + S.note_eff = T.Triv} + } + +(* Take `cc` as a param. *) +let callE cc e1 ts e2 t = + { it = CallE(cc,e1,ts,e2); + at = no_region; + note = {S.note_typ = t; + S.note_eff = T.Triv} + } + + + +let ifE exp1 exp2 exp3 typ = + { it = IfE (exp1, exp2, exp3); + at = no_region; + note = {S.note_typ = typ; + S.note_eff = max_eff (eff exp1) (max_eff (eff exp2) (eff exp3)) + } + } + +let dotE exp name typ = + { it = DotE (exp, name); + at = no_region; + note = {S.note_typ = typ; + S.note_eff = eff exp} + } + +let switch_optE exp1 exp2 pat exp3 typ = + { it = + SwitchE + (exp1, + [{it = {pat = {it = LitP S.NullLit; + at = no_region; + note = exp1.note.S.note_typ}; + exp = exp2}; + at = no_region; + note = ()}; + {it = {pat = {it = OptP pat; + at = no_region; + note = exp1.note.S.note_typ}; + exp = exp3}; + at = no_region; + note = ()}] + ); + at = no_region; + note = {S.note_typ = typ; + S.note_eff = max_eff (eff exp1) (max_eff (eff exp2) (eff exp3)) + } + } + +let tupE exps = + let effs = List.map eff exps in + let eff = List.fold_left max_eff Type.Triv effs in + {it = TupE exps; + at = no_region; + note = {S.note_typ = T.Tup (List.map typ exps); + S.note_eff = eff} + } + +let breakE l exp typ = + { it = BreakE (l, exp); + at = no_region; + note = {S.note_eff = eff exp; + S.note_typ = typ} + } + +let retE exp typ = + { it = RetE exp; + at = no_region; + note = {S.note_eff = eff exp; + S.note_typ = typ} + } + +let assignE exp1 exp2 = + { it = AssignE (exp1,exp2); + at = no_region; + note = {S.note_eff = Effect.max_eff (eff exp1) (eff exp2); + S.note_typ = Type.unit} + } + +let labelE l typT exp = + { exp with it = LabelE(l,typT,exp) } + +let loopE exp1 exp2Opt = + { it = LoopE(exp1,exp2Opt); + at = no_region; + note = {S.note_eff = Effect.max_eff (eff exp1) + (match exp2Opt with + | Some exp2 -> eff exp2 + | None -> Type.Triv); + S.note_typ = Type.Non} + } + + +let declare_idE x typ exp1 = + { it = DeclareE (x, typ, exp1); + at = no_region; + note = exp1.note; + } + +let define_idE x mut exp1 = + { it = DefineE (x, mut, exp1); + at = no_region; + note = { S.note_typ = T.unit; + S.note_eff =T.Triv} + } + +let newObjE typ sort ids = + { it = NewObjE (sort, ids, typ); + at = no_region; + note = { S.note_typ = typ; + S.note_eff = T.Triv} + } + +(* Declarations *) + + +let letP p e = + {it = LetD(p,e); + at = no_region; + note = { S.note_typ = T.unit; (* ! *) + S.note_eff = e.note.S.note_eff; } + } + +let letD x exp = { it = LetD (varP x,exp); + at = no_region; + note = { S.note_eff = eff exp; + S.note_typ = T.unit;} (* ! *) + } + +let varD x exp = { it = VarD (x,exp); + at = no_region; + note = { S.note_eff = eff exp; + S.note_typ = T.unit;} (* ! *) + } +let expD exp = { exp with it = ExpD exp} + + +(* let expressions (derived) *) + +let letE x exp1 exp2 = blockE [letD x exp1; expD exp2] + +(* Mono-morphic function declaration, sharing inferred from f's type *) +let funcD f x e = + match f.it,x.it with + | VarE _, VarE _ -> + let sharing,t1,t2 = match typ f with + | T.Func(T.Call sharing, _, _, ts1, ts2) -> sharing,T.seq ts1, T.seq ts2 + | _ -> assert false in + let cc = Value.call_conv_of_typ (typ f) in + {it=FuncD( cc, (id_of_exp f), + [], + {it = VarP (id_of_exp x); at = no_region; note = t1}, + (* TODO: Assert invariant: t2 has no free (unbound) DeBruijn indices -- Claudio *) + t2, + e); + at = no_region; + note = f.note} + | _ -> failwith "Impossible: funcD" + + +(* Mono-morphic, n-ary function declaration *) +let nary_funcD f xs e = + match f.it,f.note.S.note_typ with + | VarE _, + T.Func(T.Call sharing,_,_,_,ts2) -> + let cc = Value.call_conv_of_typ (typ f) in + let t2 = T.seq ts2 in + {it=FuncD( + cc, + id_of_exp f, + [], + seqP (List.map varP xs), + t2, + e); + at = no_region; + note = f.note} + | _,_ -> failwith "Impossible: funcD" + + +(* Continuation types *) + +let answerT = T.unit + +let contT typ = T.Func(T.Call T.Local, T.Returns, [], T.as_seq typ, []) +let cpsT typ = T.Func(T.Call T.Local, T.Returns, [], [contT typ], []) + +let fresh_cont typ = fresh_id (contT typ) + +(* Sequence expressions *) + +let seqE es = + match es with + | [e] -> e + | es -> tupE es + +let as_seqE e = + match e.it with + | TupE es -> es + | _ -> [e] + +(* Lambdas & continuations *) + +(* Lambda abstraction *) + +(* local lambda *) +let (-->) x e = + match x.it with + | VarE _ -> + let f = idE ("$lambda"@@no_region) (T.Func(T.Call T.Local, T.Returns, [], T.as_seq (typ x), T.as_seq (typ e))) in + (* decE *) (funcD f x e) + | _ -> failwith "Impossible: -->" + +(* n-ary local lambda *) +let (-->*) xs e = + let f = idE ("$lambda"@@no_region) + (T.Func(T.Call T.Local, T.Returns, [], + List.map typ xs, T.as_seq (typ e))) in + (* decE *) (nary_funcD f xs e) + + +(* n-ary shared lambda *) +let (-@>*) xs e = + let f = idE ("$lambda"@@no_region) + (T.Func(T.Call T.Sharable, T.Returns, [], + List.map typ xs, T.as_seq (typ e))) in + (* decE *) (nary_funcD f xs e) + + +(* Lambda application (monomorphic) *) + +let ( -*- ) exp1 exp2 = + match exp1.note.S.note_typ with + | T.Func(_, _, [], ts1, ts2) -> +(* for debugging bad applications, imprecisely + (if not ((T.seq ts1) = (typ exp2)) + then + begin + (Printf.printf "\nBad -*- application: func:\n %s \n arg: %s\n, expected type: \n %s: received type: \n %s" + (Wasm.Sexpr.to_string 80 (Arrange.exp exp1)) + (Wasm.Sexpr.to_string 80 (Arrange.exp exp2)) + (T.string_of_typ (T.seq ts1)) + (T.string_of_typ (typ exp2))); + + end + else ()); + *) + let cc = + let t = exp1.note.S.note_typ in + Value.call_conv_of_typ t in + + {it = CallE(cc, exp1, [], exp2); + at = no_region; + note = {S.note_typ = T.seq ts2; + S.note_eff = max_eff (eff exp1) (eff exp2)} + } + | typ1 -> failwith + (Printf.sprintf "Impossible: \n func: %s \n : %s arg: \n %s" + (Wasm.Sexpr.to_string 80 (Arrange_ir.exp exp1)) + (Type.string_of_typ typ1) + (Wasm.Sexpr.to_string 80 (Arrange_ir.exp exp2))) + + +(* Intermediate, cps-based @async and @await primitives, + introduced by await(opt).ml, removed by async.ml +*) + +let prim_async typ = + primE "@async" (T.Func(T.Call T.Local, T.Returns, [], [cpsT typ], [T.Async typ])) + +let prim_await typ = + primE "@await" (T.Func(T.Call T.Local, T.Returns, [], [T.Async typ; contT typ], [])) + diff --git a/src/tailcall_ir.ml b/src/tailcall_ir.ml new file mode 100644 index 00000000000..940a0f86b74 --- /dev/null +++ b/src/tailcall_ir.ml @@ -0,0 +1,278 @@ +open Ir +open Effect +open Type +open Syntaxops_ir + +(* Optimize (self) tail calls to jumps, avoiding stack overflow + in a single linear pass *) + +(* +This is simple tail call optimizer that replaces tail calls to the current function by jumps. +It can easily be extended to non-self tail calls, once supported by wasm. + +For each function `f` whose `body[...]` has at least one self tailcall to `f(es)`, apply the transformation: +``` + func f(pat) = body[f(es)+] + ~~> + func f(args) = { + var temp = args; + loop { + label l { + let pat = temp; + return body[{temp := es;break l;}+] + } + } + } +``` + + +It's implemented by a recursive traversal that maintains an environment recording whether the current term is in tail position, +and what its enclosing function (if any) is. + +The enclosing function is forgotten when shadowed by a local binding (we don't assume all variables are distinct) and when +entering a function, class or actor constructor. + +On little gotcha for functional programmers: the argument `e` to an early `return e` is *always* in tail position, +regardless of `return e`s own tail position. + + *) + + +type func_info = {func:S.id; + typ_binds: typ_bind list; + temp: var; + label: S.id; + tail_called: bool ref; + } + +type env = {tail_pos:bool; (* is the expression in tail position *) + info: func_info option; (* the innermost enclosing func, if any *) + } + + +let bind env i (info:func_info option) : env = + match info with + | Some _ -> + {env with info = info; } + | None -> + match env.info with + | Some {func;_} when i.Source.it = func.Source.it -> + {env with info = None} (* remove shadowed func info *) + | _ -> env (* preserve existing, non-shadowed info *) + + +let are_generic_insts tbs insts = + List.for_all2 (fun tb inst -> + match tb.Source.note, inst with + | Con(c1,[]), Con(c2,[]) -> c1 = c2 (* conservative, but safe *) + | Con(c1,[]), _ -> false + | _,_ -> assert false) tbs insts + +let rec tailexp env e = + {e with Source.it = exp' env e} + +and exp env e : exp = + {e with Source.it = exp' {env with tail_pos = false} e} + +and exp' env e : exp' = match e.Source.it with + | VarE _ + | LitE _ + | PrimE _ -> e.Source.it + | UnE (ot, uo, e) -> UnE (ot, uo, exp env e) + | BinE (ot, e1, bo, e2)-> BinE (ot, exp env e1, bo, exp env e2) + | RelE (ot, e1, ro, e2)-> RelE (ot, exp env e1, ro, exp env e2) + | TupE es -> TupE (List.map (exp env) es) + | ProjE (e, i) -> ProjE (exp env e, i) + | ActorE (i, es, t) -> ActorE (i, exp_fields env es, t) + | DotE (e, sn) -> DotE (exp env e, sn) + | ActorDotE (e, sn) -> DotE (exp env e, sn) + | AssignE (e1, e2) -> AssignE (exp env e1, exp env e2) + | ArrayE (m,t,es) -> ArrayE (m,t,(exps env es)) + | IdxE (e1, e2) -> IdxE (exp env e1, exp env e2) + | CallE (cc, e1, insts, e2) -> + begin + match e1.Source.it, env with + | VarE f1, {tail_pos = true; + info = Some {func; typ_binds; temp; label; tail_called}} + when f1.Source.it = func.Source.it && are_generic_insts typ_binds insts -> + tail_called := true; + (blockE [expD (assignE temp (exp env e2)); + expD (breakE label (tupE []) (typ e))]).Source.it + | _,_-> CallE(cc, exp env e1, insts, exp env e2) + end + | BlockE (ds,ot) -> BlockE (decs env ds, ot) + | IfE (e1, e2, e3) -> IfE (exp env e1, tailexp env e2, tailexp env e3) + | SwitchE (e, cs) -> SwitchE (exp env e, cases env cs) + | WhileE (e1, e2) -> WhileE (exp env e1, exp env e2) + | LoopE (e1, None) -> LoopE (exp env e1, None) + | LoopE (e1, Some e2) -> LoopE (exp env e1, Some (exp env e2)) + | ForE (p, e1, e2) -> let env1 = pat env p in + ForE (p, exp env e1, exp env1 e2) + | LabelE (i, t, e) -> let env1 = bind env i None in + LabelE(i, t, exp env1 e) + | BreakE (i, e) -> BreakE(i,exp env e) + | RetE e -> RetE (tailexp {env with tail_pos = true} e) + (* NB:^ e is always in tailposition, regardless of fst env *) + | AsyncE e -> AsyncE (exp {tail_pos = true; info = None} e) + | AwaitE e -> AwaitE (exp env e) + | AssertE e -> AssertE (exp env e) + | IsE (e, t) -> IsE (exp env e, t) + | OptE e -> OptE (exp env e) + | DeclareE (i, t, e) -> let env1 = bind env i None in + DeclareE (i, t, tailexp env1 e) + | DefineE (i, m, e) -> DefineE (i, m, exp env e) + | NewObjE (s,is,t) -> NewObjE (s, is, t) + +and exps env es = List.map (exp env) es + +and pat env p = + let env = pat' env p.Source.it in + env + +and pat' env p = match p with + | WildP -> env + | VarP i -> + let env1 = bind env i None in + env1 + | TupP ps -> pats env ps + | LitP l -> env + | OptP p -> pat env p + | AltP (p1, p2) -> assert(Freevars_ir.S.is_empty (snd (Freevars_ir.pat p1))); + assert(Freevars_ir.S.is_empty (snd (Freevars_ir.pat p2))); + env + +and pats env ps = + match ps with + | [] -> env + | p::ps -> + let env1 = pat env p in + pats env1 ps + +and case env (c : case) = + {c with Source.it = case' env c.Source.it} +and case' env {pat=p;exp=e} = + let env1 = pat env p in + let e' = tailexp env1 e in + {pat=p; exp=e'} + + +and cases env cs = List.map (case env) cs + +and exp_field env (ef : exp_field) = + let (mk_ef,env) = exp_field' env ef.Source.it in + ({ef with Source.it = mk_ef}, env) + +and exp_field' env {name = n; id = i; exp = e; mut; priv} = + let env = bind env i None in + ((fun env1-> {name = n; id = i; exp = exp env1 e; mut; priv}), + env) + +and exp_fields env efs = + let rec exp_fields_aux env efs = + match efs with + | [] -> ([],env) + | ef::efs -> + let (mk_ef,env) = exp_field env ef in + let (mk_efs,env) = exp_fields_aux env efs in + (mk_ef::mk_efs,env) in + let mk_efs,env = exp_fields_aux env efs in + List.map (fun mk_ef -> {mk_ef with Source.it = mk_ef.Source.it env}) mk_efs + +and dec env d = + let (mk_d,env1) = dec' env d in + ({d with Source.it = mk_d}, env1) + +and dec' env d = + match d.Source.it with + | ExpD e -> + (fun env1 -> ExpD (tailexp env1 e)), + env + | LetD (p, e) -> + let env = pat env p in + (fun env1 -> LetD(p,exp env1 e)), + env + | VarD (i, e) -> + let env = bind env i None in + (fun env1 -> VarD(i,exp env1 e)), + env + | FuncD ({Value.sort=Call Local;_} as cc, id, tbs, p, typT, exp0) -> + let env = bind env id None in + (fun env1 -> + let temp = fresh_id (Mut p.Source.note) in + let l = fresh_lab () in + let tail_called = ref false in + let env2 = {tail_pos = true; + info = Some {func = id; + typ_binds = tbs; + temp = temp; + label = l; + tail_called = tail_called}} + in + let env3 = pat env2 p in (* shadow id if necessary *) + let exp0' = tailexp env3 exp0 in + let cs = List.map (fun tb -> tb.Source.note) tbs in + if !tail_called then + let ids = match typ d with + | Func(_,_,_,dom,_) -> List.map (fun t -> fresh_id (open_ cs t)) dom + | _ -> assert false + in + let args = seqP (List.map varP ids) in + let l_typ = + {Source.it = Syntax.TupT []; at = Source.no_region; note = Type.unit} + in + let body = + blockE [ varD (id_of_exp temp) (seqE ids); + expD (loopE + (labelE l l_typ.Source.note + (blockE [letP p temp; + expD (retE exp0' Type.unit)])) None) + ] in + FuncD (cc, id, tbs, args, typT, body) + else + FuncD (cc, id, tbs, p, typT, exp0')) + , + env + + | FuncD (cc, i, tbs, p, t, e) -> + (* don't optimize self-tail calls for shared functions otherwise + we won't go through the scheduler *) + let env = bind env i None in + (fun env1 -> + let env2 = pat {tail_pos = true; info = None} p in + let e' = tailexp env2 e in + FuncD(cc, i, tbs, p, t, e')), + env + + | TypD (_, _) -> + (fun env -> d.Source.it), + env + +and decs env ds = + let rec tail_posns ds = + match ds with + | [] -> (true,[]) + | {Source.it=TypD _;_}::ds -> + let (b,bs) = tail_posns ds in + (b,b::bs) + | d::ds -> + let (b,bs) = tail_posns ds in + (false,b::bs) + in + let _,tail_posns = tail_posns ds in + let rec decs_aux env ds = + match ds with + | [] -> ([],env) + | d::ds -> + let (mk_d,env1) = dec env d in + let (mk_ds,env2) = decs_aux env1 ds in + (mk_d::mk_ds,env2) + in + let mk_ds,env1 = decs_aux env ds in + List.map2 (fun mk_d in_tail_pos -> + let env2 = if in_tail_pos + then env1 + else {env1 with tail_pos = false} in + {mk_d with Source.it = mk_d.Source.it env2}) mk_ds tail_posns + + +and prog p:prog = {p with Source.it = decs {tail_pos = false; info = None;} p.Source.it}