Module RTLinjectgen

Require Import
  Utf8 Coqlib Maps
  Integers Registers.

Require Import
  Utils Common DataStructures
  InjectedCode INJECT GCthread
  MIR RTLinject Transcode.

Section RTLINJECTGEN.

Local Notation node := positive (only parsing).

Variable bi : backend_info.
Variable om : object_model.
Variable p : MIR.program.

All functions return an int and have an extra argument (pointer to the thread descriptor).
Definition translate_sig (s : MIR.signature) : Ast.signature :=
  let args := List.map (fun _ => Ast.Tint) s.(sig_args)
    in
    Ast.mksignature
    (if s.(sig_ext) then args else Ast.Tint :: args)
    (Some Ast.Tint).

Section TRINSTR.

  Variables (gv: PTree.t Z)
            (ep: node) (nd: node) (roots: N)
            (md: reg) (tmp1 tmp2: reg).

Definition gvi_of_type (ty: field_type) : Ast.ident :=
  match ty with
    | FPointer => bi.(bi_ptr_globals)
    | FInteger => bi.(bi_int_globals)
  end.

Definition translate_instruction (i: MIR.instruction) : mon unit :=
  match i with
    | Mnop succ => update_instr nd
      (if peq ep nd
       then if roots
            then Inop succ
            else Iinject (prologue_code roots) (md::nil) nil succ
       else if plt nd succ
            then Inop succ
            else Iinject (cooperate_code bi) (md::nil) nil succ)
    | Mop dst (OpIntConst cst) args succ => update_instr nd (Iop (Op.Ointconst cst) args dst succ)
    | Mop dst OpAdd args succ => update_instr nd (Iop (Op.Olea (Op.Aindexed2 Int.zero)) args dst succ)
    | Mop dst OpNull args succ => update_instr nd (Iop (Op.Ointconst Int.zero) args dst succ)
    | Mop dst OpNeg args succ => update_instr nd (Iop Op.Oneg args dst succ)
    | Mop dst (OpShri i) args succ => update_instr nd (Iop (Op.Oshrimm i) args dst succ)
    | Mop dst OpMov (src::nil) succ => update_instr nd
       (if is_ptr_reg roots dst
       then Iinject (saveref_code dst) (src::nil) (dst::nil) succ
       else Iop Op.Omove (src::nil) dst succ)
    | Mop _ OpMov _ succ => error (Errors.msg "Ill-typed move")
    | Mif cmp args if_so if_not => update_instr nd (Icond cmp args if_so if_not)
    | Mget dst src fld succ => update_instr nd (Iinject (read_code (slot_of_reg dst) fld)
                                                        (src::nil) (dst::nil) succ)
    | Mput dst fld src succ => update_instr nd (Iinject (write_code bi fld)
                                                        (md::dst::src::nil) nil succ)
    | Mget_global dst src succ =>
        match p.(prog_vars) ! src, gv ! src with
            | Some (k, ty), Some adr => update_instr nd
               (Iinject (read_global_code (gvi_of_type ty) adr k) nil (dst::nil) succ)
            | _, _ => error (Errors.msg "Wrong global variable identifier")
        end
    | Mput_global dst src succ =>
        match p.(prog_vars) ! dst, gv ! dst with
            | Some (k, ty), Some adr => update_instr nd
                (Iinject (write_global_code (gvi_of_type ty) adr k) (src::nil) nil succ)
            | _, _ => error (Errors.msg "Wrong global variable identifier")
        end
    | Mnew dst hdr succ => update_instr nd
        (Iinject (new_code bi (slot_of_reg dst) hdr) (md::nil) (dst::nil) succ)
    | Mfence succ => update_instr nd
        (Iinject (Make_IC (Sfence false) (Satomic true (Sreturn nil)) nil) nil nil succ)
    | Mspawn sg func param succ =>
        do n <- add_instr (Ithreadcreate tmp1 tmp2 succ);
        update_instr nd (Iinject (spawn_code bi func) (param::nil) (tmp1::tmp2::nil) n)
    | Mlock mn succ => update_instr nd (Iinject lock_code (mn::nil) nil succ)
    | Munlock mn succ => update_instr nd (Iinject unlock_code (mn::nil) nil succ)

    | Mcall dst sg func args succ =>
        do n <- match sg.(sig_res) with
                  | FInteger => ret succ
                  | FPointer => add_instr (Iinject (saveref_code dst) (dst::nil) (tmp1::nil) succ)
                end;
        do n' <- add_instr (Icall (translate_sig sg) (inr _ func)
                                  (if sg.(sig_ext) then args else md::args) dst n);
        update_instr nd (Iinject (cooperate_code bi) (md::nil) nil n')

   | Mret res =>
     if roots
     then update_instr nd (Ireturn res)
     else do n <- add_instr (Ireturn res);
          update_instr nd (Iinject epilogue_code (md::nil) nil n)

  end.

End TRINSTR.

Definition translate_fun gv (f: MIR.fundef) : Errors.res RTLinject.fundef :=
  match f with
    | Ast.External e => Errors.OK (Ast.External e)
    | Ast.Internal i =>
      match i.(MIR.fn_code) ! (i.(MIR.fn_entrypoint)) with
        | Some (Mnop _) =>
          let arg0 : reg := fresh_register i.(MIR.fn_code) in
          let dummy : reg := Psucc arg0 in
          let dummy' : reg := Psucc dummy in
          let is : state := init_state (Psucc (max_key i.(MIR.fn_code))) in
          match
          PTree.fold
            (fun m k j => do _ <- m;
             translate_instruction gv i.(MIR.fn_entrypoint) k i.(MIR.fn_roots) arg0
               dummy dummy' j)
            i.(MIR.fn_code)
            (ret tt)
            is
            with
              | OK _ st _ =>
                  Errors.OK
                  (Ast.Internal (RTLinject.mkfunction
                     (translate_sig i.(MIR.fn_sig))
                     (arg0 :: i.(MIR.fn_params))
                     (match i.(fn_roots) with
                          | N0 => Int.zero
                          | Npos r => Int.repr ((Zpos r + 2) * 4)
                      end)
                     st.(st_code)
                     i.(MIR.fn_entrypoint)))
              | Error msg => Errors.Error msg
          end
        | _ => Errors.Error (Errors.msg "No nop at function entry")
      end
  end.

Definition translate_global (gv: PTree.t (field_kind * field_type))
  : (PTree.t Z * Z * Z)%type :=
  PTree.fold (A := field_kind * field_type) (B := PTree.t Z * Z * Z)
       (fun acc v kt =>
          let '(aloc, max_p, max_i) := acc in
          let '(k, t) := kt in
            match t with
              | FPointer => (aloc ! v <- max_p, max_p + 4, max_i)
              | FInteger => (aloc ! v <- max_i, max_p, max_i + 4)
            end
       )
       gv
       (PTree.empty _, 0, 0)
  .

Definition transf_program : Errors.res RTLinject.program :=
  let '(gv, max_p, max_i) := translate_global p.(prog_vars) in
  Errors.bind
    (Ast.map_partial Ast.prefix_funct_name (translate_fun gv)
        (PTree.elements p.(MIR.prog_funct)))
    (fun fl =>
       Errors.OK
      (Ast.mkprogram
          ( (bi.(bi_thread_main), Ast.Internal (thread_main_func bi))
          ::(bi.(bi_main), Ast.Internal (gc_main_func bi om p max_p))
          ::(bi.(bi_abort), Ast.External (Ast.mkextfun bi.(bi_abort) (Ast.mksignature (Ast.Tint::nil) None)))
          ::fl)
          bi.(bi_main)
          ( (bi.(bi_mutator_lock), Ast.Init_space 4::nil, tt)
          ::(bi.(bi_free_list), Ast.Init_space 4::nil, tt)
          ::(bi.(bi_mutator_data), Ast.Init_space (Int.unsigned (md_size bi))::nil, tt)
          ::(bi.(bi_heap), Ast.Init_space (Int.unsigned (heap_byte_size om))::nil, tt)
          ::(bi.(bi_collector), Ast.Init_space (Int.unsigned sizeof_cd)::nil, tt)
          ::(bi.(bi_collector_stack), Ast.Init_space (Int.unsigned bi.(bi_collector_stack_size))::nil, tt)
          ::(bi.(bi_ptr_globals), Ast.Init_space max_p::nil, tt)
          ::(bi.(bi_int_globals), Ast.Init_space max_i::nil, tt)
          ::nil)
       )).

End RTLINJECTGEN.