summaryrefslogtreecommitdiff
path: root/bytecomp/codegen.ml
diff options
context:
space:
mode:
Diffstat (limited to 'bytecomp/codegen.ml')
-rw-r--r--bytecomp/codegen.ml443
1 files changed, 443 insertions, 0 deletions
diff --git a/bytecomp/codegen.ml b/bytecomp/codegen.ml
new file mode 100644
index 0000000000..f19ca96143
--- /dev/null
+++ b/bytecomp/codegen.ml
@@ -0,0 +1,443 @@
+(* codegen.ml : translation of lambda terms to lists of instructions. *)
+
+open Misc
+open Asttypes
+open Lambda
+open Instruct
+
+
+(**** Label generation ****)
+
+let label_counter = ref 0
+
+let new_label () =
+ incr label_counter; !label_counter
+
+(**** Structure of the compilation environment. ****)
+
+type compilation_env =
+ { ce_stack: int Ident.tbl; (* Positions of variables in the stack *)
+ ce_heap: int Ident.tbl } (* Structure of the heap-allocated env *)
+
+(* The ce_stack component gives locations of variables residing
+ in the stack. The locations are offsets w.r.t. the origin of the
+ stack frame.
+ The ce_heap component gives the positions of variables residing in the
+ heap-allocated environment. *)
+
+let empty_env =
+ { ce_stack = Ident.empty; ce_heap = Ident.empty }
+
+(* Add a stack-allocated variable *)
+
+let add_var id pos env =
+ { ce_stack = Ident.add id pos env.ce_stack;
+ ce_heap = env.ce_heap }
+
+(**** Examination of the continuation ****)
+
+(* Return a label to the beginning of the given continuation.
+ If the sequence starts with a branch, use the target of that branch
+ as the label, thus avoiding a jump to a jump. *)
+
+let label_code = function
+ Kbranch lbl :: _ as cont -> (lbl, cont)
+ | Klabel lbl :: _ as cont -> (lbl, cont)
+ | cont -> let lbl = new_label() in (lbl, Klabel lbl :: cont)
+
+(* Return a branch to the continuation. That is, an instruction that,
+ when executed, branches to the continuation or performs what the
+ continuation performs. We avoid generating branches to branches and
+ branches to returns. *)
+
+let make_branch cont =
+ match cont with
+ (Kbranch _ as branch) :: _ -> (branch, cont)
+ | (Kreturn _ as return) :: _ -> (return, cont)
+ | Kraise :: _ -> (Kraise, cont)
+ | Klabel lbl :: _ -> (Kbranch lbl, cont)
+ | _ -> let lbl = new_label() in (Kbranch lbl, Klabel lbl :: cont)
+
+(* Discard all instructions up to the next label.
+ This function is to be applied to the continuation before adding a
+ non-terminating instruction (branch, raise, return) in front of it. *)
+
+let rec discard_dead_code = function
+ [] -> []
+ | (Klabel _ | Krestart) :: _ as cont -> cont
+ | _ :: cont -> discard_dead_code cont
+
+(* Check if we're in tailcall position *)
+
+let rec is_tailcall = function
+ Kreturn _ :: _ -> true
+ | Klabel _ :: c -> is_tailcall c
+ | _ -> false
+
+(* Add a Kpop N instruction in front of a continuation *)
+
+let rec add_pop n cont =
+ if n = 0 then cont else
+ match cont with
+ Kpop m :: cont -> add_pop (n + m) cont
+ | Kreturn m :: cont -> Kreturn(n + m) :: cont
+ | Kraise :: _ -> cont
+ | _ -> Kpop n :: cont
+
+(* Add the constant "unit" in front of a continuation *)
+
+let add_const_unit = function
+ (Kacc _ | Kconst _ | Kgetglobal _ | Kpush_retaddr _) :: _ as cont -> cont
+ | cont -> Kconst const_unit :: cont
+
+(**** Compilation of a lambda expression ****)
+
+(* The label to which Lstaticfail branches, and the stack size at that point.*)
+
+let lbl_staticfail = ref 0
+and sz_staticfail = ref 0
+
+(* Function bodies that remain to be compiled *)
+
+let functions_to_compile =
+ (Stack.new () : (Ident.t * lambda * label * Ident.t list) Stack.t)
+
+(* Compile an expression.
+ The val of the expression is left in the accumulator.
+ env = compilation environment
+ exp = the lambda expression to compile
+ sz = current size of the stack frame
+ cont = list of instructions to execute afterwards
+ Result = list of instructions that evaluate exp, then perform cont. *)
+
+open Format
+
+let rec comp_expr env exp sz cont =
+ match exp with
+ Lvar id ->
+ begin try
+ let pos = Ident.find_same id env.ce_stack in
+ Kacc(sz - pos) :: cont
+ with Not_found ->
+ try
+ let pos = Ident.find_same id env.ce_heap in
+ Kenvacc(pos) :: cont
+ with Not_found ->
+ Ident.print id; print_newline();
+ fatal_error "Codegen.comp_expr: var"
+ end
+ | Lconst cst ->
+ Kconst cst :: cont
+ | Lapply(func, args) ->
+ let nargs = List.length args in
+ if is_tailcall cont then
+ comp_args env args sz
+ (Kpush :: comp_expr env func (sz + nargs)
+ (Kappterm(nargs, sz + nargs) :: discard_dead_code cont))
+ else
+ if nargs < 4 then
+ comp_args env args sz
+ (Kpush :: comp_expr env func (sz + nargs) (Kapply nargs :: cont))
+ else begin
+ let (lbl, cont1) = label_code cont in
+ Kpush_retaddr lbl ::
+ comp_args env args (sz + 3)
+ (Kpush :: comp_expr env func (sz + 3 + nargs)
+ (Kapply nargs :: cont1))
+ end
+ | Lfunction(param, body) ->
+ let lbl = new_label() in
+ let fv = free_variables exp in
+ Stack.push (param, body, lbl, fv) functions_to_compile;
+ comp_args env (List.map (fun n -> Lvar n) fv) sz
+ (Kclosure(lbl, List.length fv) :: cont)
+ | Llet(id, arg, body) ->
+ comp_expr env arg sz
+ (Kpush :: comp_expr (add_var id (sz+1) env) body (sz+1)
+ (add_pop 1 cont))
+ | Lletrec(([id, Lfunction(param, funct_body), _] as decl), let_body) ->
+ let lbl = new_label() in
+ let fv = free_variables (Lletrec(decl, lambda_unit)) in
+ Stack.push (param, funct_body, lbl, id :: fv) functions_to_compile;
+ comp_args env (List.map (fun n -> Lvar n) fv) sz
+ (Kclosurerec(lbl, List.length fv) :: Kpush ::
+ (comp_expr (add_var id (sz+1) env) let_body (sz+1)
+ (add_pop 1 cont)))
+ | Lletrec(decl, body) ->
+ let ndecl = List.length decl in
+ let rec comp_decl new_env sz i = function
+ [] ->
+ comp_expr new_env body sz (add_pop ndecl cont)
+ | (id, exp, blocksize) :: rem ->
+ comp_expr new_env exp sz
+ (Kpush :: Kacc i :: Kupdate :: comp_decl new_env sz (i-1) rem) in
+ let rec comp_init new_env sz = function
+ [] ->
+ comp_decl new_env sz ndecl decl
+ | (id, exp, blocksize) :: rem ->
+ Kdummy blocksize :: Kpush ::
+ comp_init (add_var id (sz+1) new_env) (sz+1) rem in
+ comp_init env sz decl
+ | Lprim(Pidentity, [arg]) ->
+ comp_expr env arg sz cont
+ | Lprim(Pnot, [arg]) ->
+ let newcont =
+ match cont with
+ Kbranchif lbl :: cont1 -> Kbranchifnot lbl :: cont1
+ | Kbranchifnot lbl :: cont1 -> Kbranchif lbl :: cont1
+ | _ -> Kboolnot :: cont in
+ comp_expr env arg sz newcont
+ | Lprim(Psequand, [exp1; exp2]) ->
+ begin match cont with
+ Kbranchifnot lbl :: _ ->
+ comp_expr env exp1 sz (Kbranchifnot lbl ::
+ comp_expr env exp2 sz cont)
+ | Kbranchif lbl :: cont1 ->
+ let (lbl2, cont2) = label_code cont1 in
+ comp_expr env exp1 sz (Kbranchifnot lbl2 ::
+ comp_expr env exp2 sz (Kbranchif lbl :: cont2))
+ | _ ->
+ let (lbl, cont1) = label_code cont in
+ comp_expr env exp1 sz (Kstrictbranchifnot lbl ::
+ comp_expr env exp2 sz cont1)
+ end
+ | Lprim(Psequor, [exp1; exp2]) ->
+ begin match cont with
+ Kbranchif lbl :: _ ->
+ comp_expr env exp1 sz (Kbranchif lbl ::
+ comp_expr env exp2 sz cont)
+ | Kbranchifnot lbl :: cont1 ->
+ let (lbl2, cont2) = label_code cont1 in
+ comp_expr env exp1 sz (Kbranchif lbl2 ::
+ comp_expr env exp2 sz (Kbranchifnot lbl :: cont2))
+ | _ ->
+ let (lbl, cont1) = label_code cont in
+ comp_expr env exp1 sz (Kstrictbranchif lbl ::
+ comp_expr env exp2 sz cont1)
+ end
+ | Lprim(Praise, [arg]) ->
+ comp_expr env arg sz (Kraise :: discard_dead_code cont)
+ | Lprim((Paddint | Psubint as prim), [arg; Lconst(Const_base(Const_int n))])
+ when n >= immed_min & n <= immed_max ->
+ let ofs = if prim == Paddint then n else -n in
+ comp_expr env arg sz (Koffsetint ofs :: cont)
+ | Lprim(p, args) ->
+ let instr =
+ match p with
+ Pgetglobal id -> Kgetglobal id
+ | Psetglobal id -> Ksetglobal id
+ | Pupdate -> Kupdate
+ | Pcomp cmp -> Kintcomp cmp
+ | Pmakeblock tag -> Kmakeblock(List.length args, tag)
+ | Ptagof -> Ktagof
+ | Pfield n -> Kgetfield n
+ | Psetfield n -> Ksetfield n
+ | Pccall(name, n) -> Kccall(name, n)
+ | Pnegint -> Knegint
+ | Paddint -> Kaddint
+ | Psubint -> Ksubint
+ | Pmulint -> Kmulint
+ | Pdivint -> Kdivint
+ | Pmodint -> Kmodint
+ | Pandint -> Kandint
+ | Porint -> Korint
+ | Pxorint -> Kxorint
+ | Plslint -> Klslint
+ | Plsrint -> Klsrint
+ | Pasrint -> Kasrint
+ | Poffsetint n -> Koffsetint n
+ | Poffsetref n -> Koffsetref n
+ | Pgetstringchar -> Kgetstringchar
+ | Psetstringchar -> Ksetstringchar
+ | Pvectlength -> Kvectlength
+ | Pgetvectitem -> Kgetvectitem
+ | Psetvectitem -> Ksetvectitem
+ | _ -> fatal_error "Codegen.comp_expr: prim" in
+ comp_args env args sz (instr :: cont)
+ | Lcatch(body, Lstaticfail) ->
+ comp_expr env body sz cont
+ | Lcatch(body, handler) ->
+ let (branch1, cont1) = make_branch cont in
+ let (lbl_handler, cont2) = label_code (comp_expr env handler sz cont1) in
+ let saved_lbl_staticfail = !lbl_staticfail
+ and saved_sz_staticfail = !sz_staticfail in
+ lbl_staticfail := lbl_handler;
+ sz_staticfail := sz;
+ let cont3 = comp_expr env body sz (branch1 :: cont2) in
+ lbl_staticfail := saved_lbl_staticfail;
+ sz_staticfail := saved_sz_staticfail;
+ cont3
+ | Lstaticfail ->
+ add_pop (sz - !sz_staticfail)
+ (Kbranch !lbl_staticfail :: discard_dead_code cont)
+ | Ltrywith(body, id, handler) ->
+ let (branch1, cont1) = make_branch cont in
+ let lbl_handler = new_label() in
+ Kpushtrap lbl_handler ::
+ comp_expr env body (sz+4) (Kpoptrap :: branch1 ::
+ Klabel lbl_handler :: Kpush ::
+ comp_expr (add_var id (sz+1) env) handler (sz+1) (add_pop 1 cont1))
+ | Lifthenelse(cond, ifso, ifnot) ->
+ comp_binary_test env cond ifso ifnot sz cont
+ | Lsequence(exp1, exp2) ->
+ comp_expr env exp1 sz (comp_expr env exp2 sz cont)
+ | Lwhile(cond, body) ->
+ let lbl_loop = new_label() in
+ let lbl_test = new_label() in
+ Kbranch lbl_test :: Klabel lbl_loop :: Kcheck_signals ::
+ comp_expr env body sz
+ (Klabel lbl_test ::
+ comp_expr env cond sz (Kbranchif lbl_loop :: cont))
+ | Lfor(param, start, stop, dir, body) ->
+ let lbl_loop = new_label() in
+ let lbl_test = new_label() in
+ let offset = match dir with Upto -> 1 | Downto -> -1 in
+ let comp = match dir with Upto -> Cle | Downto -> Cge in
+ comp_expr env start sz
+ (Kpush :: comp_expr env stop (sz+1)
+ (Kpush :: Kbranch lbl_test ::
+ Klabel lbl_loop :: Kcheck_signals ::
+ comp_expr (add_var param (sz+1) env) body (sz+2)
+ (Kacc 1 :: Koffsetint offset :: Kassign 1 ::
+ Klabel lbl_test ::
+ Kacc 0 :: Kpush :: Kacc 2 :: Kintcomp comp ::
+ Kbranchif lbl_loop ::
+ add_const_unit (add_pop 2 cont))))
+ | Lswitch(arg, lo, hi, casel) ->
+ let numcases = List.length casel in
+ let cont1 =
+ if lo = 0 & numcases >= hi - 8 then (* Always true if hi <= 8... *)
+ comp_direct_switch env hi casel sz cont
+ else begin
+ let (transl_table, actions) = Dectree.make_decision_tree casel in
+ Ktranslate transl_table :: comp_switch env actions sz cont
+ end in
+ comp_expr env arg sz cont1
+ | Lshared(expr, lblref) ->
+ begin match !lblref with
+ None ->
+ let (lbl, cont1) = label_code(comp_expr env expr sz cont) in
+ lblref := Some lbl;
+ cont1
+ | Some lbl ->
+ Kbranch lbl :: discard_dead_code cont
+ end
+
+(* Compile a list of arguments [e1; ...; eN] to a primitive operation.
+ The values of eN ... e2 are pushed on the stack, e2 at top of stack,
+ then e3, then ... The val of e1 is left in the accumulator. *)
+
+and comp_args env argl sz cont =
+ comp_expr_list env (List.rev argl) sz cont
+
+and comp_expr_list env exprl sz cont =
+ match exprl with
+ [] -> cont
+ | [exp] -> comp_expr env exp sz cont
+ | exp :: rem ->
+ comp_expr env exp sz (Kpush :: comp_expr_list env rem (sz+1) cont)
+
+(* Compile an if-then-else test. *)
+
+and comp_binary_test env cond ifso ifnot sz cont =
+ let cont_cond =
+ if ifnot = Lconst const_unit then begin
+ let (lbl_end, cont1) = label_code cont in
+ Kbranchifnot lbl_end :: comp_expr env ifso sz cont1
+ end else
+ if ifso = Lstaticfail & sz = !sz_staticfail then
+ Kbranchif !lbl_staticfail :: comp_expr env ifnot sz cont
+ else
+ if ifnot = Lstaticfail & sz = !sz_staticfail then
+ Kbranchifnot !lbl_staticfail :: comp_expr env ifso sz cont
+ else begin
+ let (branch_end, cont1) = make_branch cont in
+ let (lbl_not, cont2) = label_code(comp_expr env ifnot sz cont1) in
+ Kbranchifnot lbl_not :: comp_expr env ifso sz (branch_end :: cont2)
+ end in
+ comp_expr env cond sz cont_cond
+
+(* Compile a Lswitch directly, without breaking the array of cases into
+ dense enough components *)
+
+and comp_direct_switch env range casel sz cont =
+ let actv = Array.new range Lstaticfail in
+ List.iter (fun (n, act) -> actv.(n) <- act) casel;
+ comp_switch env actv sz cont
+
+(* Compile a switch instruction *)
+
+and comp_switch env actv sz cont =
+ (* To ensure stack balancing, we must have either sz = !sz_staticfail
+ or none of the actv.(i) contains an unguarded Lstaticfail. *)
+ let lblv = Array.new (Array.length actv) !lbl_staticfail in
+ let (branch, cont1) = make_branch cont in
+ let c = ref (discard_dead_code cont1) in
+ for i = Array.length actv - 1 downto 0 do
+ let (lbl, c1) = label_code(comp_expr env actv.(i) sz (branch :: !c)) in
+ lblv.(i) <- lbl;
+ c := discard_dead_code c1
+ done;
+ Kswitch lblv :: !c
+
+(**** Compilation of functions ****)
+
+let comp_function (param, body, entry_lbl, free_vars) cont =
+ (* Uncurry the function body *)
+ let rec uncurry = function
+ Lfunction(param, body) ->
+ let (params, final) = uncurry body in (param :: params, final)
+ | Lshared(exp, lblref) ->
+ uncurry exp
+ | exp ->
+ ([], exp) in
+ let (params, fun_body) =
+ uncurry (Lfunction(param, body)) in
+ let arity = List.length params in
+ let rec pos_args pos delta = function
+ [] -> Ident.empty
+ | id :: rem -> Ident.add id pos (pos_args (pos+delta) delta rem) in
+ let env =
+ { ce_stack = pos_args arity (-1) params;
+ ce_heap = pos_args 0 1 free_vars } in
+ let cont1 =
+ comp_expr env fun_body arity (Kreturn arity :: cont) in
+ if arity > 1 then
+ Krestart :: Klabel entry_lbl :: Kgrab(arity - 1) :: cont1
+ else
+ Klabel entry_lbl :: cont1
+
+let comp_remainder cont =
+ let c = ref cont in
+ begin try
+ while true do
+ c := comp_function (Stack.pop functions_to_compile) !c
+ done
+ with Stack.Empty ->
+ ()
+ end;
+ !c
+
+(**** Compilation of a lambda phrase ****)
+
+let compile_implementation expr =
+ Stack.clear functions_to_compile;
+ label_counter := 0;
+ lbl_staticfail := 0;
+ sz_staticfail := 0;
+ let init_code = comp_expr empty_env expr 0 [] in
+ if Stack.length functions_to_compile > 0 then begin
+ let lbl_init = new_label() in
+ Kbranch lbl_init :: comp_remainder (Klabel lbl_init :: init_code)
+ end else
+ init_code
+
+let compile_phrase expr =
+ Stack.clear functions_to_compile;
+ label_counter := 0;
+ lbl_staticfail := 0;
+ sz_staticfail := 0;
+ let init_code = comp_expr empty_env expr 0 [Kstop] in
+ let fun_code = comp_remainder [] in
+ (init_code, fun_code)
+