301 lines
10 KiB
OCaml
301 lines
10 KiB
OCaml
(**************************************************************************)
|
|
(* *)
|
|
(* OCaml *)
|
|
(* *)
|
|
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
|
|
(* *)
|
|
(* Copyright 1996 Institut National de Recherche en Informatique et *)
|
|
(* en Automatique. *)
|
|
(* *)
|
|
(* All rights reserved. This file is distributed under the terms of *)
|
|
(* the GNU Lesser General Public License version 2.1, with the *)
|
|
(* special exception on linking described in the file LICENSE. *)
|
|
(* *)
|
|
(**************************************************************************)
|
|
|
|
(* Transformation of Mach code into a list of pseudo-instructions. *)
|
|
|
|
open Reg
|
|
open Mach
|
|
|
|
type label = int
|
|
|
|
let label_counter = ref 99
|
|
|
|
let new_label() = incr label_counter; !label_counter
|
|
|
|
type instruction =
|
|
{ mutable desc: instruction_desc;
|
|
mutable next: instruction;
|
|
arg: Reg.t array;
|
|
res: Reg.t array;
|
|
dbg: Debuginfo.t;
|
|
live: Reg.Set.t }
|
|
|
|
and instruction_desc =
|
|
Lend
|
|
| Lop of operation
|
|
| Lreloadretaddr
|
|
| Lreturn
|
|
| Llabel of label
|
|
| Lbranch of label
|
|
| Lcondbranch of test * label
|
|
| Lcondbranch3 of label option * label option * label option
|
|
| Lswitch of label array
|
|
| Lsetuptrap of label
|
|
| Lpushtrap
|
|
| Lpoptrap
|
|
| Lraise of Lambda.raise_kind
|
|
|
|
let has_fallthrough = function
|
|
| Lreturn | Lbranch _ | Lswitch _ | Lraise _
|
|
| Lop Itailcall_ind | Lop (Itailcall_imm _) -> false
|
|
| _ -> true
|
|
|
|
type fundecl =
|
|
{ fun_name: string;
|
|
fun_body: instruction;
|
|
fun_fast: bool;
|
|
fun_dbg : Debuginfo.t }
|
|
|
|
(* Invert a test *)
|
|
|
|
let invert_integer_test = function
|
|
Isigned cmp -> Isigned(Cmm.negate_comparison cmp)
|
|
| Iunsigned cmp -> Iunsigned(Cmm.negate_comparison cmp)
|
|
|
|
let invert_test = function
|
|
Itruetest -> Ifalsetest
|
|
| Ifalsetest -> Itruetest
|
|
| Iinttest(cmp) -> Iinttest(invert_integer_test cmp)
|
|
| Iinttest_imm(cmp, n) -> Iinttest_imm(invert_integer_test cmp, n)
|
|
| Ifloattest(cmp, neg) -> Ifloattest(cmp, not neg)
|
|
| Ieventest -> Ioddtest
|
|
| Ioddtest -> Ieventest
|
|
|
|
(* The "end" instruction *)
|
|
|
|
let rec end_instr =
|
|
{ desc = Lend;
|
|
next = end_instr;
|
|
arg = [||];
|
|
res = [||];
|
|
dbg = Debuginfo.none;
|
|
live = Reg.Set.empty }
|
|
|
|
(* Cons an instruction (live, debug empty) *)
|
|
|
|
let instr_cons d a r n =
|
|
{ desc = d; next = n; arg = a; res = r;
|
|
dbg = Debuginfo.none; live = Reg.Set.empty }
|
|
|
|
(* Cons a simple instruction (arg, res, live empty) *)
|
|
|
|
let cons_instr d n =
|
|
{ desc = d; next = n; arg = [||]; res = [||];
|
|
dbg = Debuginfo.none; live = Reg.Set.empty }
|
|
|
|
(* Build an instruction with arg, res, dbg, live taken from
|
|
the given Mach.instruction *)
|
|
|
|
let copy_instr d i n =
|
|
{ desc = d; next = n;
|
|
arg = i.Mach.arg; res = i.Mach.res;
|
|
dbg = i.Mach.dbg; live = i.Mach.live }
|
|
|
|
(*
|
|
Label the beginning of the given instruction sequence.
|
|
- If the sequence starts with a branch, jump over it.
|
|
- If the sequence is the end, (tail call position), just do nothing
|
|
*)
|
|
|
|
let get_label n = match n.desc with
|
|
Lbranch lbl -> (lbl, n)
|
|
| Llabel lbl -> (lbl, n)
|
|
| Lend -> (-1, n)
|
|
| _ -> let lbl = new_label() in (lbl, cons_instr (Llabel lbl) n)
|
|
|
|
(* Check the fallthrough label *)
|
|
let check_label n = match n.desc with
|
|
| Lbranch lbl -> lbl
|
|
| Llabel lbl -> lbl
|
|
| _ -> -1
|
|
|
|
(* Discard all instructions up to the next label.
|
|
This function is to be called before adding a non-terminating
|
|
instruction. *)
|
|
|
|
let rec discard_dead_code n =
|
|
match n.desc with
|
|
Lend -> n
|
|
| Llabel _ -> n
|
|
(* Do not discard Lpoptrap/Lpushtrap or Istackoffset instructions,
|
|
as this may cause a stack imbalance later during assembler generation. *)
|
|
| Lpoptrap | Lpushtrap -> n
|
|
| Lop(Istackoffset _) -> n
|
|
| _ -> discard_dead_code n.next
|
|
|
|
(*
|
|
Add a branch in front of a continuation.
|
|
Discard dead code in the continuation.
|
|
Does not insert anything if we're just falling through
|
|
or if we jump to dead code after the end of function (lbl=-1)
|
|
*)
|
|
|
|
let add_branch lbl n =
|
|
if lbl >= 0 then
|
|
let n1 = discard_dead_code n in
|
|
match n1.desc with
|
|
| Llabel lbl1 when lbl1 = lbl -> n1
|
|
| _ -> cons_instr (Lbranch lbl) n1
|
|
else
|
|
discard_dead_code n
|
|
|
|
let try_depth = ref 0
|
|
|
|
(* Association list: exit handler -> (handler label, try-nesting factor) *)
|
|
|
|
let exit_label = ref []
|
|
|
|
let find_exit_label_try_depth k =
|
|
try
|
|
List.assoc k !exit_label
|
|
with
|
|
| Not_found -> Misc.fatal_error "Linearize.find_exit_label"
|
|
|
|
let find_exit_label k =
|
|
let (label, t) = find_exit_label_try_depth k in
|
|
assert(t = !try_depth);
|
|
label
|
|
|
|
let is_next_catch n = match !exit_label with
|
|
| (n0,(_,t))::_ when n0=n && t = !try_depth -> true
|
|
| _ -> false
|
|
|
|
let local_exit k =
|
|
snd (find_exit_label_try_depth k) = !try_depth
|
|
|
|
(* Linearize an instruction [i]: add it in front of the continuation [n] *)
|
|
|
|
let rec linear i n =
|
|
match i.Mach.desc with
|
|
Iend -> n
|
|
| Iop(Itailcall_ind | Itailcall_imm _ as op) ->
|
|
copy_instr (Lop op) i (discard_dead_code n)
|
|
| Iop(Imove | Ireload | Ispill)
|
|
when i.Mach.arg.(0).loc = i.Mach.res.(0).loc ->
|
|
linear i.Mach.next n
|
|
| Iop op ->
|
|
copy_instr (Lop op) i (linear i.Mach.next n)
|
|
| Ireturn ->
|
|
let n1 = copy_instr Lreturn i (discard_dead_code n) in
|
|
if !Proc.contains_calls
|
|
then cons_instr Lreloadretaddr n1
|
|
else n1
|
|
| Iifthenelse(test, ifso, ifnot) ->
|
|
let n1 = linear i.Mach.next n in
|
|
begin match (ifso.Mach.desc, ifnot.Mach.desc, n1.desc) with
|
|
Iend, _, Lbranch lbl ->
|
|
copy_instr (Lcondbranch(test, lbl)) i (linear ifnot n1)
|
|
| _, Iend, Lbranch lbl ->
|
|
copy_instr (Lcondbranch(invert_test test, lbl)) i (linear ifso n1)
|
|
| Iexit nfail1, Iexit nfail2, _
|
|
when is_next_catch nfail1 && local_exit nfail2 ->
|
|
let lbl2 = find_exit_label nfail2 in
|
|
copy_instr
|
|
(Lcondbranch (invert_test test, lbl2)) i (linear ifso n1)
|
|
| Iexit nfail, _, _ when local_exit nfail ->
|
|
let n2 = linear ifnot n1
|
|
and lbl = find_exit_label nfail in
|
|
copy_instr (Lcondbranch(test, lbl)) i n2
|
|
| _, Iexit nfail, _ when local_exit nfail ->
|
|
let n2 = linear ifso n1 in
|
|
let lbl = find_exit_label nfail in
|
|
copy_instr (Lcondbranch(invert_test test, lbl)) i n2
|
|
| Iend, _, _ ->
|
|
let (lbl_end, n2) = get_label n1 in
|
|
copy_instr (Lcondbranch(test, lbl_end)) i (linear ifnot n2)
|
|
| _, Iend, _ ->
|
|
let (lbl_end, n2) = get_label n1 in
|
|
copy_instr (Lcondbranch(invert_test test, lbl_end)) i
|
|
(linear ifso n2)
|
|
| _, _, _ ->
|
|
(* Should attempt branch prediction here *)
|
|
let (lbl_end, n2) = get_label n1 in
|
|
let (lbl_else, nelse) = get_label (linear ifnot n2) in
|
|
copy_instr (Lcondbranch(invert_test test, lbl_else)) i
|
|
(linear ifso (add_branch lbl_end nelse))
|
|
end
|
|
| Iswitch(index, cases) ->
|
|
let lbl_cases = Array.make (Array.length cases) 0 in
|
|
let (lbl_end, n1) = get_label(linear i.Mach.next n) in
|
|
let n2 = ref (discard_dead_code n1) in
|
|
for i = Array.length cases - 1 downto 0 do
|
|
let (lbl_case, ncase) =
|
|
get_label(linear cases.(i) (add_branch lbl_end !n2)) in
|
|
lbl_cases.(i) <- lbl_case;
|
|
n2 := discard_dead_code ncase
|
|
done;
|
|
(* Switches with 1 and 2 branches have been eliminated earlier.
|
|
Here, we do something for switches with 3 branches. *)
|
|
if Array.length index = 3 then begin
|
|
let fallthrough_lbl = check_label !n2 in
|
|
let find_label n =
|
|
let lbl = lbl_cases.(index.(n)) in
|
|
if lbl = fallthrough_lbl then None else Some lbl in
|
|
copy_instr (Lcondbranch3(find_label 0, find_label 1, find_label 2))
|
|
i !n2
|
|
end else
|
|
copy_instr (Lswitch(Array.map (fun n -> lbl_cases.(n)) index)) i !n2
|
|
| Iloop body ->
|
|
let lbl_head = new_label() in
|
|
let n1 = linear i.Mach.next n in
|
|
let n2 = linear body (cons_instr (Lbranch lbl_head) n1) in
|
|
cons_instr (Llabel lbl_head) n2
|
|
| Icatch(io, body, handler) ->
|
|
let (lbl_end, n1) = get_label(linear i.Mach.next n) in
|
|
let (lbl_handler, n2) = get_label(linear handler n1) in
|
|
exit_label := (io, (lbl_handler, !try_depth)) :: !exit_label ;
|
|
let n3 = linear body (add_branch lbl_end n2) in
|
|
exit_label := List.tl !exit_label;
|
|
n3
|
|
| Iexit nfail ->
|
|
let lbl, t = find_exit_label_try_depth nfail in
|
|
(* We need to re-insert dummy pushtrap (which won't be executed),
|
|
so as to preserve stack offset during assembler generation.
|
|
It would make sense to have a special pseudo-instruction
|
|
only to inform the later pass about this stack offset
|
|
(corresponding to N traps).
|
|
*)
|
|
let rec loop i tt =
|
|
if t = tt then i
|
|
else loop (cons_instr Lpushtrap i) (tt - 1)
|
|
in
|
|
let n1 = loop (linear i.Mach.next n) !try_depth in
|
|
let rec loop i tt =
|
|
if t = tt then i
|
|
else loop (cons_instr Lpoptrap i) (tt - 1)
|
|
in
|
|
loop (add_branch lbl n1) !try_depth
|
|
| Itrywith(body, handler) ->
|
|
let (lbl_join, n1) = get_label (linear i.Mach.next n) in
|
|
incr try_depth;
|
|
let (lbl_body, n2) =
|
|
get_label (cons_instr Lpushtrap
|
|
(linear body (cons_instr Lpoptrap n1))) in
|
|
decr try_depth;
|
|
cons_instr (Lsetuptrap lbl_body)
|
|
(linear handler (add_branch lbl_join n2))
|
|
| Iraise k ->
|
|
copy_instr (Lraise k) i (discard_dead_code n)
|
|
|
|
let reset () =
|
|
label_counter := 99;
|
|
exit_label := []
|
|
|
|
let fundecl f =
|
|
{ fun_name = f.Mach.fun_name;
|
|
fun_body = linear f.Mach.fun_body end_instr;
|
|
fun_fast = f.Mach.fun_fast;
|
|
fun_dbg = f.Mach.fun_dbg }
|