947d2274bb
and modified the emit module to emit a Pop instruction after every top-level expression. This change was required because the semantics of the language are pretty clear. Every expression evaluates to something - meaning that, in the corresponding bytecode, every expression must have exactly a +1 effect on the data stack. I.e. every expression, when its corresponding bytecode is evaluated, has the effect of pushing something to the stack. For values that are not used by another expression, this value must be immediately popped. Some optimizations could target this area. For example, for top-level expressions, it is obvious to the compiler that their values will not be used - hence the compiler can use optimized versions of some instructions (like StoreLocal and StoreGlobal) to simply never leave the value on the stack, thus saving an extra Pop instruction (good for performance and code size). Same thing applies in function bodies, letrec/let/begin bodies, where expressions whose values are never used may appear. It may also make sense to introduce registers to the VM, for the purposes of parameter passing (such that up to a predetermined number of parameters are progressively passed through registers instead of pushed to the stack). This would pair well with eliminating unnecessary currying in the byte code.
149 lines
4.8 KiB
OCaml
149 lines
4.8 KiB
OCaml
|
|
type literal = Core_ast.literal
|
|
type expression = Scope_analysis.expression
|
|
module SymbolTable = Scope_analysis.SymbolTable
|
|
|
|
type instr = Vm.Types.instr
|
|
|
|
type pre_instr =
|
|
| Instr of instr
|
|
| BackPatchMkClosure
|
|
| BackPatchJumpF
|
|
|
|
type program = {
|
|
instrs : pre_instr Dynarray.t;
|
|
constants : Vm.Types.value Dynarray.t;
|
|
sym_table : int SymbolTable.t;
|
|
(* This array holds the lambda bodies that we have to compiler later, and
|
|
the index we have to patch the address back into.
|
|
*)
|
|
backpatch : (int * expression) Queue.t;
|
|
}
|
|
|
|
let ( let* ) = Result.bind
|
|
|
|
let current_index p =
|
|
Dynarray.length p.instrs
|
|
let set_instr p i ins =
|
|
Dynarray.set p.instrs i (Instr ins)
|
|
|
|
let emit_mkclosure p =
|
|
Ok (Dynarray.add_last p.instrs BackPatchMkClosure)
|
|
let emit_jumpf p =
|
|
Ok (Dynarray.add_last p.instrs BackPatchJumpF)
|
|
|
|
let emit_instr p i =
|
|
Ok (Dynarray.add_last p.instrs (Instr i))
|
|
|
|
let emit_constant p c =
|
|
Dynarray.add_last p.constants c;
|
|
emit_instr p (Constant ((Dynarray.length p.constants) - 1))
|
|
(* evaluating an expression ALWAYS has the effect of pushing exactly
|
|
one element to the stack. For top-level items, this element is
|
|
silently popped.
|
|
*)
|
|
let rec compile_one p = function
|
|
| Scope_analysis.Literal (Int x) -> emit_constant p (Vm.Types.Int x)
|
|
| Literal Nil -> emit_constant p (Vm.Types.Nil)
|
|
| Literal (Double x) -> emit_constant p (Vm.Types.Double x)
|
|
| Literal (String s) -> emit_constant p (Vm.Types.String s)
|
|
| Literal (Symbol s) -> emit_constant p (Vm.Types.Symbol s)
|
|
| Literal (Cons (a, b)) ->
|
|
let* _ = compile_one p (Literal a) in
|
|
let* _ = compile_one p (Literal b) in
|
|
emit_instr p (Vm.Types.MakeCons)
|
|
| Var (Scope_analysis.Local i) ->
|
|
emit_instr p (Vm.Types.LoadLocal i)
|
|
| Var (Global i) ->
|
|
emit_instr p (Vm.Types.LoadGlobal i)
|
|
| Set (Local i, expr) ->
|
|
let* _ = compile_one p expr in
|
|
emit_instr p (Vm.Types.StoreLocal i)
|
|
| Set (Global i, expr) ->
|
|
let* _ = compile_one p expr in
|
|
emit_instr p (Vm.Types.StoreGlobal i)
|
|
| Apply (f, arg) ->
|
|
let* _ = compile_one p f in
|
|
let* _ = compile_one p arg in
|
|
emit_instr p Vm.Types.Apply
|
|
| Lambda body ->
|
|
let* _ = emit_mkclosure p in
|
|
Ok (Queue.push ((Dynarray.length p.instrs) - 1, body) p.backpatch)
|
|
| If (test, t, f) ->
|
|
(* *)
|
|
let* _ = compile_one p test in (* compile the expression to be tested *)
|
|
let jumpf_index = current_index p in
|
|
let* _ = emit_jumpf p in (* jump if false, to the false branch*)
|
|
let* _ = compile_one p t in (* true branch *)
|
|
let jump_index = current_index p in
|
|
let* _ = emit_jumpf p in (* jump unconditionally to the common point*)
|
|
let false_index = current_index p in
|
|
let* _ = compile_one p f in (* false branch *)
|
|
let reunite_index = current_index p in
|
|
let* _ = emit_instr p NOOP in
|
|
(* Now we can immediately backpatch the dummy instructions we put in place *)
|
|
set_instr p jumpf_index (JumpF false_index);
|
|
set_instr p jump_index (Jump reunite_index);
|
|
Ok ()
|
|
| Begin [] ->
|
|
Error "Cannot compile empty begin "
|
|
| Begin (e1 :: []) ->
|
|
compile_one p e1
|
|
| Begin (e1 :: e2 :: rest) ->
|
|
let* _ = compile_one p e1 in
|
|
let* _ = emit_instr p Vm.Types.Pop in
|
|
compile_one p (Begin (e2 :: rest))
|
|
|
|
and compile_all p exprs =
|
|
Util.traverse
|
|
(fun e ->
|
|
let* _ = compile_one p e in
|
|
emit_instr p Pop) exprs
|
|
|
|
(* Once we have compiled the top-level expressions, we must now compile
|
|
all of the lambdas we held off on. Some of these will hold more
|
|
lambdas - that should be fine, they'll just get added to the end
|
|
of the backpatch queue.
|
|
*)
|
|
let backpatch_one p (i, b) =
|
|
Dynarray.set p.instrs i (Instr (MakeClosure (current_index p)));
|
|
let* _ = compile_one p b in
|
|
emit_instr p End
|
|
let rec backpatch p =
|
|
if Queue.is_empty p.backpatch then
|
|
Ok ()
|
|
else
|
|
(let* _ = backpatch_one p (Queue.pop p.backpatch) in
|
|
backpatch p)
|
|
|
|
|
|
let print_instr = function
|
|
| Instr i -> Vm.Types.print_one i
|
|
| BackPatchJumpF -> "BACKPATCH JUMPF\n"
|
|
| BackPatchMkClosure -> "BACKPATCH CLOSURE\n"
|
|
let print_instrs =
|
|
Array.mapi_inplace (fun i ins ->
|
|
print_endline (Printf.sprintf "%d: %s" i (print_instr ins)); ins)
|
|
let smooth_one = function
|
|
| Instr i -> i
|
|
| _ -> failwith "backpatching process was not complete!"
|
|
let smooth_instrs p =
|
|
Dynarray.to_array (Dynarray.map smooth_one p.instrs)
|
|
|
|
let compile (exprs : expression list) (tbl : int SymbolTable.t) =
|
|
let program = {
|
|
instrs=Dynarray.create ();
|
|
constants=Dynarray.create ();
|
|
sym_table=tbl;
|
|
backpatch=Queue.create ();
|
|
} in
|
|
let* _ = compile_all program exprs in
|
|
let* _ = emit_instr program End in
|
|
let* _ = backpatch program in
|
|
let final_instrs = smooth_instrs program in
|
|
Ok (Vm.make_vm final_instrs (Dynarray.to_array program.constants) ((SymbolTable.cardinal tbl) + 1))
|
|
|
|
let compile_src src =
|
|
let* (exprs, tbl) = Scope_analysis.of_src src in
|
|
compile exprs tbl
|