(***********************************************************************)
(*                                                                     *)
(*                           Objective Caml                            *)
(*                                                                     *)
(*            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 Q Public License version 1.0.               *)
(*                                                                     *)
(***********************************************************************)

(* $Id: emit.mlp 9540 2010-01-20 16:26:46Z doligez $ *)

(* Emission of Sparc assembly code *)

open Location
open Misc
open Cmm
open Arch
open Proc
open Reg
open Mach
open Linearize
open Emitaux

(* Solaris vs. the other ports *)

let solaris = Config.system = "solaris"

(* Tradeoff between code size and code speed *)

let fastcode_flag = ref true

(* Layout of the stack *)
(* Always keep the stack 8-aligned.
   Always leave 96 bytes at the bottom of the stack *)

let stack_offset = ref 0

let frame_size () =
  let size =
    !stack_offset +
    4 * num_stack_slots.(0) + 8 * num_stack_slots.(1) +
    (if !contains_calls then 4 else 0) in
  Misc.align size 8

let slot_offset loc cl =
  match loc with
    Incoming n -> frame_size() + n + 96
  | Local n ->
      if cl = 0
      then !stack_offset + num_stack_slots.(1) * 8 + n * 4 + 96
      else !stack_offset + n * 8 + 96
  | Outgoing n -> n + 96

(* Return the other register in a register pair *)

let next_in_pair = function
    {loc = Reg r; typ = (Int | Addr)} -> phys_reg (r + 1)
  | {loc = Reg r; typ = Float} -> phys_reg (r + 16)
  | _ -> fatal_error "Emit.next_in_pair"

(* Symbols are prefixed with _ under SunOS *)

let symbol_prefix =
  if Config.system = "sunos" then "_" else ""

let emit_symbol s =
  if String.length s >= 1 & s.[0] = '.'
  then emit_string s
  else begin emit_string symbol_prefix; Emitaux.emit_symbol '$' s end

let emit_size lbl =
  if Config.system = "solaris" then
    `	.size	{emit_symbol lbl},.-{emit_symbol lbl}\n`

let rodata () =
  if Config.system = "solaris" (* || Config.system = "linux" *)
  (* || Config.system = "gnu" *) then
    `	.section \".rodata\"\n`
  else
    `	.data\n`

(* Check if an integer or native integer is an immediate operand *)

let is_immediate n =
  n <= 4095 && n >= -4096

let is_native_immediate n =
  n <= Nativeint.of_int 4095 && n >= Nativeint.of_int (-4096)

(* Output a label *)

let label_prefix =
  if Config.system = "sunos" then "L" else ".L"

let emit_label lbl =
  emit_string label_prefix; emit_int lbl

(* Output a pseudo-register *)

let emit_reg r =
  match r.loc with
    Reg r -> emit_string (register_name r)
  | _ -> fatal_error "Emit.emit_reg"

(* Output a stack reference *)

let emit_stack r =
  match r.loc with
    Stack s ->
      let ofs = slot_offset s (register_class r) in `[%sp + {emit_int ofs}]`
  | _ -> fatal_error "Emit.emit_stack"

(* Output a load *)

let emit_load instr addr arg dst =
  match addr with
    Ibased(s, 0) ->
        `	sethi	%hi({emit_symbol s}), %g1\n`;
        `	{emit_string instr}	[%g1 + %lo({emit_symbol s})], {emit_reg dst}\n`
  | Ibased(s, ofs) ->
        `	sethi	%hi({emit_symbol s} + {emit_int ofs}), %g1\n`;
        `	{emit_string instr}	[%g1 + %lo({emit_symbol s} + {emit_int ofs})], {emit_reg dst}\n`
  | Iindexed ofs ->
      if is_immediate ofs then
        `	{emit_string instr}	[{emit_reg arg.(0)} + {emit_int ofs}], {emit_reg dst}\n`
      else begin
        `	sethi	%hi({emit_int ofs}), %g1\n`;
        `	or	%g1, %lo({emit_int ofs}), %g1\n`;
        `	{emit_string instr}	[{emit_reg arg.(0)} + %g1], {emit_reg dst}\n`
      end

(* Output a store *)

let emit_store instr addr arg src =
  match addr with
    Ibased(s, 0) ->
        `	sethi	%hi({emit_symbol s}), %g1\n`;
        `	{emit_string instr}	{emit_reg src}, [%g1 + %lo({emit_symbol s})]\n`
  | Ibased(s, ofs) ->
        `	sethi	%hi({emit_symbol s} + {emit_int ofs}), %g1\n`;
        `	{emit_string instr}	{emit_reg src}, [%g1 + %lo({emit_symbol s} + {emit_int ofs})]\n`
  | Iindexed ofs ->
      if is_immediate ofs then
        `	{emit_string instr}	{emit_reg src}, [{emit_reg arg.(1)} + {emit_int ofs}]\n`
      else begin
        `	sethi	%hi({emit_int ofs}), %g1\n`;
        `	or	%g1, %lo({emit_int ofs}), %g1\n`;
        `	{emit_string instr}	{emit_reg src}, [{emit_reg arg.(1)} + %g1]\n`
      end

(* Record live pointers at call points *)

type frame_descr =
  { fd_lbl: int;                        (* Return address *)
    fd_frame_size: int;                 (* Size of stack frame *)
    fd_live_offset: int list }          (* Offsets/regs of live addresses *)

let frame_descriptors = ref([] : frame_descr list)

let record_frame live =
  let lbl = new_label() in
  let live_offset = ref [] in
  Reg.Set.iter
    (function
        {typ = Addr; loc = Reg r} ->
          live_offset := ((r lsl 1) + 1) :: !live_offset
      | {typ = Addr; loc = Stack s} as reg ->
          live_offset :=
            slot_offset s (register_class reg) :: !live_offset
      | _ -> ())
    live;
  frame_descriptors :=
    { fd_lbl = lbl;
      fd_frame_size = frame_size();
      fd_live_offset = !live_offset } :: !frame_descriptors;
  `{emit_label lbl}:`

let emit_frame fd =
  `	.word	{emit_label fd.fd_lbl}\n`;
  `	.half	{emit_int fd.fd_frame_size}\n`;
  `	.half	{emit_int (List.length fd.fd_live_offset)}\n`;
  List.iter
    (fun n ->
      `	.half	{emit_int n}\n`)
    fd.fd_live_offset;
  `	.align	4\n`

(* Record floating-point constants *)

let float_constants = ref ([] : (int * string) list)

let emit_float_constant (lbl, cst) =
  rodata ();
  `	.align	8\n`;
  `{emit_label lbl}:`;
  emit_float64_split_directive ".word" cst

(* Emission of the profiling prelude *)
let emit_profile () =
  begin match Config.system with
    "solaris" ->
      let lbl = new_label() in
      `	.section \".bss\"\n`;
      `{emit_label lbl}:	.skip 4\n`;
      `	.text\n`;
      `	save	%sp,-96,%sp\n`;
      `	sethi	%hi({emit_label lbl}),%o0\n`;
      `	call	_mcount\n`;
      `	or	%o0,%lo({emit_label lbl}),%o0\n`;
      `	restore\n`
  | _ -> ()
  end

(* Names of various instructions *)

let name_for_int_operation = function
    Iadd -> "add"
  | Isub -> "sub"
  | Iand -> "and"
  | Ior -> "or"
  | Ixor -> "xor"
  | Ilsl -> "sll"
  | Ilsr -> "srl"
  | Iasr -> "sra"
  | Imul -> "smul"
  | _ -> Misc.fatal_error "Emit.name_for_int_operation"

let name_for_float_operation = function
    Inegf -> if !arch_version = SPARC_V9 then "fnegd" else "fnegs"
  | Iabsf -> if !arch_version = SPARC_V9 then "fabsd" else "fabss"
  | Iaddf -> "faddd"
  | Isubf -> "fsubd"
  | Imulf -> "fmuld"
  | Idivf -> "fdivd"
  | _ -> Misc.fatal_error "Emit.name_for_float_operation"

let name_for_int_movcc = function
    Isigned Ceq -> "e"	    | Isigned Cne -> "ne"
  | Isigned Cle -> "le"	    | Isigned Cgt -> "g"
  | Isigned Clt -> "l"	    | Isigned Cge -> "ge"
  | Iunsigned Ceq -> "e"    | Iunsigned Cne -> "ne"
  | Iunsigned Cle -> "leu"  | Iunsigned Cgt -> "gu"
  | Iunsigned Clt -> "lu"   | Iunsigned Cge -> "geu"

let name_for_int_comparison = function
    Isigned Ceq -> "be"      | Isigned Cne -> "bne"
  | Isigned Cle -> "ble"     | Isigned Cgt -> "bg"
  | Isigned Clt -> "bl"      | Isigned Cge -> "bge"
  | Iunsigned Ceq -> "be"    | Iunsigned Cne -> "bne"
  | Iunsigned Cle -> "bleu"  | Iunsigned Cgt -> "bgu"
  | Iunsigned Clt -> "blu"   | Iunsigned Cge -> "bgeu"

let name_for_float_comparison cmp neg =
  match cmp with
    Ceq -> if neg then "fbne" else "fbe"
  | Cne -> if neg then "fbe" else "fbne"
  | Cle -> if neg then "fbug" else "fble"
  | Cgt -> if neg then "fbule" else "fbg"
  | Clt -> if neg then "fbuge" else "fbl"
  | Cge -> if neg then "fbul" else "fbge"

(* Output the assembly code for an instruction *)

let function_name = ref ""
let tailrec_entry_point = ref 0
let range_check_trap = ref 0

let rec emit_instr i dslot =
    match i.desc with
      Lend -> ()
    | Lop(Imove | Ispill | Ireload) ->
        let src = i.arg.(0) and dst = i.res.(0) in
        begin match (src, dst) with
            {loc = Reg rs; typ = (Int | Addr)}, {loc = Reg rd} ->
              `	mov	{emit_reg src}, {emit_reg dst}\n`
          | {loc = Reg rs; typ = Float}, {loc = Reg rd; typ = Float} ->
              if !arch_version = SPARC_V9 then
                `	fmovd	{emit_reg src}, {emit_reg dst}\n`
              else begin
                `	fmovs	{emit_reg src}, {emit_reg dst}\n`;
                `	fmovs	{emit_reg(next_in_pair src)}, {emit_reg(next_in_pair dst)}\n`
              end
          | {loc = Reg rs; typ = Float}, {loc = Reg rd; typ = (Int | Addr)} ->
              (* This happens when calling C functions and passing a float arg
                 in %o0...%o5 *)
              `	sub	%sp, 8, %sp\n`;
              `	std	{emit_reg src}, [%sp + 96]\n`;
              `	ld	[%sp + 96], {emit_reg dst}\n`;
              `	ld	[%sp + 100], {emit_reg(next_in_pair dst)}\n`;
              `	add	%sp, 8, %sp\n`
          | {loc = Reg rs; typ = (Int | Addr)}, {loc = Stack sd} ->
              `	st	{emit_reg src}, {emit_stack dst}\n`
          | {loc = Reg rs; typ = Float}, {loc = Stack sd} ->
              `	std	{emit_reg src}, {emit_stack dst}\n`
          | {loc = Stack ss; typ = (Int | Addr)}, {loc = Reg rd} ->
              `	ld	{emit_stack src}, {emit_reg dst}\n`
          | {loc = Stack ss; typ = Float}, {loc = Reg rd} ->
              `	ldd	{emit_stack src}, {emit_reg dst}\n`
          | (_, _) ->
              fatal_error "Emit: Imove"
        end
    | Lop(Iconst_int n) ->
        if is_native_immediate n then
          `	mov	{emit_nativeint n}, {emit_reg i.res.(0)}\n`
        else begin
          `	sethi	%hi({emit_nativeint n}), %g1\n`;
          `	or	%g1, %lo({emit_nativeint n}), {emit_reg i.res.(0)}\n`
        end
    | Lop(Iconst_float s) ->
        (* On UltraSPARC, the fzero instruction could be used to set a
           floating point register pair to zero. *)
        let lbl = new_label() in
        float_constants := (lbl, s) :: !float_constants;
        `	sethi	%hi({emit_label lbl}), %g1\n`;
        `	ldd	[%g1 + %lo({emit_label lbl})], {emit_reg i.res.(0)}\n`
    | Lop(Iconst_symbol s) ->
        `	sethi	%hi({emit_symbol s}), %g1\n`;
        `	or	%g1, %lo({emit_symbol s}), {emit_reg i.res.(0)}\n`
    | Lop(Icall_ind) ->
        `{record_frame i.live}	call	{emit_reg i.arg.(0)}\n`;
        fill_delay_slot dslot
    | Lop(Icall_imm s) ->
        `{record_frame i.live}	call	{emit_symbol s}\n`;
        fill_delay_slot dslot
    | Lop(Itailcall_ind) ->
        let n = frame_size() in
        if !contains_calls then
          `	ld	[%sp + {emit_int(n - 4 + 96)}], %o7\n`;
        `	jmp	{emit_reg i.arg.(0)}\n`;
        `	add	%sp, {emit_int n}, %sp\n` (* in delay slot *)
    | Lop(Itailcall_imm s) ->
        let n = frame_size() in
        if s = !function_name then begin
            `	b	{emit_label !tailrec_entry_point}\n`;
            fill_delay_slot dslot
        end else begin
          if !contains_calls then
            `	ld	[%sp + {emit_int(n - 4 + 96)}], %o7\n`;
          `	sethi	%hi({emit_symbol s}), %g1\n`;
          `	jmp	%g1 + %lo({emit_symbol s})\n`;
          `	add	%sp, {emit_int n}, %sp\n` (* in delay slot *)
        end
    | Lop(Iextcall(s, alloc)) ->
        if alloc then begin
          `	sethi	%hi({emit_symbol s}), %g2\n`;
          `{record_frame i.live}	call	{emit_symbol "caml_c_call"}\n`;
          `	or	%g2, %lo({emit_symbol s}), %g2\n` (* in delay slot *)
        end else begin
          `	call	{emit_symbol s}\n`;
          fill_delay_slot dslot
        end
    | Lop(Istackoffset n) ->
        `	add	%sp, {emit_int (-n)}, %sp\n`;
        stack_offset := !stack_offset + n
    | Lop(Iload(chunk, addr)) ->
        let dest = i.res.(0) in
        begin match chunk with
          Double_u ->
            emit_load "ld" addr i.arg dest;
            emit_load "ld" (offset_addressing addr 4) i.arg (next_in_pair dest)
        | Single ->
            emit_load "ld" addr i.arg dest;
            `	fstod	{emit_reg dest}, {emit_reg dest}\n`
        | _ ->
            let loadinstr =
              match chunk with
                Byte_unsigned -> "ldub"
              | Byte_signed -> "ldsb"
              | Sixteen_unsigned -> "lduh"
              | Sixteen_signed -> "ldsh"
              | Double -> "ldd"
              | _ -> "ld" in
            emit_load loadinstr addr i.arg dest
        end
    | Lop(Istore(chunk, addr)) ->
        let src = i.arg.(0) in
        begin match chunk with
          Double_u ->
            emit_store "st" addr i.arg src;
            emit_store "st" (offset_addressing addr 4) i.arg (next_in_pair src)
        | Single ->
            `	fdtos	{emit_reg src}, %f30\n`;
            emit_store "st" addr i.arg (phys_reg 115) (* %f30 *)
        | _ ->
            let storeinstr =
              match chunk with
              | Byte_unsigned | Byte_signed -> "stb"
              | Sixteen_unsigned | Sixteen_signed -> "sth"
              | Double -> "std"
              | _ -> "st" in
            emit_store storeinstr addr i.arg src
        end
    | Lop(Ialloc n) ->
        if !fastcode_flag then begin
          let lbl_cont = new_label() in
          if solaris then begin
            `	sub	%l6, {emit_int n}, %l6\n`;
            `	cmp	%l6, %l7\n`
          end else begin
            `	ld	[%l7], %g1\n`;
            `	sub	%l6, {emit_int n}, %l6\n`;
            `	cmp	%l6, %g1\n`
          end;
          `	bgeu	{emit_label lbl_cont}\n`;
          `	add	%l6, 4, {emit_reg i.res.(0)}\n`; (* in delay slot *)
          `{record_frame i.live}	call	{emit_symbol "caml_call_gc"}\n`;
          `	mov	{emit_int n}, %g2\n`; (* in delay slot *)
          `	add	%l6, 4, {emit_reg i.res.(0)}\n`;
          `{emit_label lbl_cont}:\n`
        end else begin
          `{record_frame i.live}	call	{emit_symbol "caml_allocN"}\n`;
          `	mov	{emit_int n}, %g2\n`; (* in delay slot *)
          `	add	%l6, 4, {emit_reg i.res.(0)}\n`
        end
    | Lop(Iintop(Icomp cmp)) ->
        `	cmp	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
        if !arch_version = SPARC_V9 then begin
          let comp = name_for_int_movcc cmp in
          `	mov	0, {emit_reg i.res.(0)}\n`;
          `	mov{emit_string comp}	%icc, 1, {emit_reg i.res.(0)}\n`
        end
        else begin
          let comp = name_for_int_comparison cmp
          and lbl = new_label() in
          `	{emit_string comp},a	{emit_label lbl}\n`;
          `	mov	1, {emit_reg i.res.(0)}\n`;
          `	mov	0, {emit_reg i.res.(0)}\n`;
          `{emit_label lbl}:\n`
        end
    | Lop(Iintop Icheckbound) ->
        `	cmp	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
        if solaris then
          `	tleu	5\n`            (* 5 = ST_RANGE_CHECK *)
        else begin
          if !range_check_trap = 0 then range_check_trap := new_label();
          `	bleu	{emit_label !range_check_trap}\n`;
          `	nop\n`                  (* delay slot *)
        end
    | Lop(Iintop Idiv) ->
        `	sra	{emit_reg i.arg.(0)}, 31, %g1\n`;
        `	wr	%g1, %y\n`;
        `	sdiv	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}, {emit_reg i.res.(0)}\n`
    | Lop(Iintop op) ->
        let instr = name_for_int_operation op in
        `	{emit_string instr}	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}, {emit_reg i.res.(0)}\n`
    | Lop(Iintop_imm(Ilsl, 1)) ->
        (* UltraSPARC has two add units but only one shifter. *)
        `	add	{emit_reg i.arg.(0)}, {emit_reg i.arg.(0)}, {emit_reg i.res.(0)}\n`
    | Lop(Iintop_imm(Idiv, n)) ->
        let l = Misc.log2 n in
        if n = 1 lsl l then begin
          let lbl = new_label() in
          `	cmp	{emit_reg i.arg.(0)}, 0\n`;
          `	bge	{emit_label lbl}\n`;
          `	mov	{emit_reg i.arg.(0)}, %g1\n`; (* in delay slot *)
          `	add	%g1, {emit_int (n-1)}, %g1\n`;
          `{emit_label lbl}:\n`;
          `	sra	%g1, {emit_int l}, {emit_reg i.res.(0)}\n`
        end else begin
          `	sra	{emit_reg i.arg.(0)}, 31, %g1\n`;
          `	wr	%g1, %y\n`;
          `	sdiv	{emit_reg i.arg.(0)}, {emit_int n}, {emit_reg i.res.(0)}\n`
        end
    | Lop(Iintop_imm(Imod, n)) ->       (* n is a power of 2 *)
        let lbl = new_label() in
        `	tst	{emit_reg i.arg.(0)}\n`;
        `	bge	{emit_label lbl}\n`;
        `	andcc	{emit_reg i.arg.(0)}, {emit_int (n-1)}, {emit_reg i.res.(0)}\n`; (* in delay slot *)
        `	be	{emit_label lbl}\n`;
        `	nop\n`;
        `	sub	{emit_reg i.res.(0)}, {emit_int n}, {emit_reg i.res.(0)}\n`;
        `{emit_label lbl}:\n`
    | Lop(Iintop_imm(Icomp cmp, n)) ->
        `	cmp	{emit_reg i.arg.(0)}, {emit_int n}\n`;
        if !arch_version = SPARC_V9 then begin
          let comp = name_for_int_movcc cmp in
          `	mov	0, {emit_reg i.res.(0)}\n`;
          `	mov{emit_string comp}	%icc, 1, {emit_reg i.res.(0)}\n`
        end else begin
          let comp = name_for_int_comparison cmp
          and lbl = new_label() in
          `	{emit_string comp},a	{emit_label lbl}\n`;
          `	mov	1, {emit_reg i.res.(0)}\n`;
          `	mov	0, {emit_reg i.res.(0)}\n`;
          `{emit_label lbl}:\n`
        end
    | Lop(Iintop_imm(Icheckbound, n)) ->
        `	cmp	{emit_reg i.arg.(0)}, {emit_int n}\n`;
        if solaris then
          `	tleu	5\n`            (* 5 = ST_RANGE_CHECK *)
        else begin
          if !range_check_trap = 0 then range_check_trap := new_label();
          `	bleu	{emit_label !range_check_trap}\n`;
          `	nop\n`                  (* delay slot *)
        end
    | Lop(Iintop_imm(op, n)) ->
        let instr = name_for_int_operation op in
        `	{emit_string instr}	{emit_reg i.arg.(0)}, {emit_int n}, {emit_reg i.res.(0)}\n`
    | Lop(Inegf | Iabsf as op) ->
        let instr = name_for_float_operation op in
        `	{emit_string instr}	{emit_reg i.arg.(0)}, {emit_reg i.res.(0)}\n`;
        if !arch_version <> SPARC_V9 then
          `	fmovs	{emit_reg(next_in_pair i.arg.(0))}, {emit_reg(next_in_pair i.res.(0))}\n`
    | Lop(Iaddf | Isubf | Imulf | Idivf as op) ->
        let instr = name_for_float_operation op in
        `	{emit_string instr}	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}, {emit_reg i.res.(0)}\n`
    | Lop(Ifloatofint) ->
        `	sub	%sp, 8, %sp\n`;
        `	st	{emit_reg i.arg.(0)}, [%sp + 96]\n`;
        `	ld	[%sp + 96], %f30\n`;
        `	add	%sp, 8, %sp\n`;
        `	fitod	%f30, {emit_reg i.res.(0)}\n`
    | Lop(Iintoffloat) ->
        `	fdtoi	{emit_reg i.arg.(0)}, %f30\n`;
        `	sub	%sp, 8, %sp\n`;
        `	st	%f30, [%sp + 96]\n`;
        `	ld	[%sp + 96], {emit_reg i.res.(0)}\n`;
        `	add	%sp, 8, %sp\n`
    | Lop(Ispecific sop) ->
	assert false
    | Lreloadretaddr ->
        let n = frame_size() in
        `	ld	[%sp + {emit_int(n - 4 + 96)}], %o7\n`
    | Lreturn ->
        let n = frame_size() in
        `	retl\n`;
        if n = 0 then
          `	nop\n`
        else
          `	add	%sp, {emit_int n}, %sp\n`
    | Llabel lbl ->
        `{emit_label lbl}:\n`
    | Lbranch lbl ->
        `	b	{emit_label lbl}\n`;
        fill_delay_slot dslot
    | Lcondbranch(tst, lbl) ->
        begin match tst with
          Itruetest ->
            `	tst	{emit_reg i.arg.(0)}\n`;
            `	bne	{emit_label lbl}\n`
        | Ifalsetest ->
            `	tst	{emit_reg i.arg.(0)}\n`;
            `	be	{emit_label lbl}\n`
        | Iinttest cmp ->
            let comp = name_for_int_comparison cmp in
            `	cmp	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
            `	{emit_string comp}	{emit_label lbl}\n`
        | Iinttest_imm(cmp, n) ->
            let comp = name_for_int_comparison cmp in
            `	cmp	{emit_reg i.arg.(0)}, {emit_int n}\n`;
            `	{emit_string comp}	{emit_label lbl}\n`
        | Ifloattest(cmp, neg) ->
            let comp = name_for_float_comparison cmp neg in
            `	fcmpd	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
            `	nop\n`;
            `	{emit_string comp}	{emit_label lbl}\n`
        | Ioddtest ->
            `	andcc	{emit_reg i.arg.(0)}, 1, %g0\n`;
            `	bne	{emit_label lbl}\n`
        | Ieventest ->
            `	andcc	{emit_reg i.arg.(0)}, 1, %g0\n`;
            `	be	{emit_label lbl}\n`
        end;
        fill_delay_slot dslot
  | Lcondbranch3(lbl0, lbl1, lbl2) ->
        `	cmp	{emit_reg i.arg.(0)}, 1\n`;
        begin match lbl0 with
          None -> ()
        | Some lbl -> `	bl	{emit_label lbl}\n	nop\n`
        end;
        begin match lbl1 with
          None -> ()
        | Some lbl -> `	be	{emit_label lbl}\n	nop\n`
        end;
        begin match lbl2 with
          None -> ()
        | Some lbl -> `	bg	{emit_label lbl}\n	nop\n`
        end
    | Lswitch jumptbl ->
        let lbl_jumptbl = new_label() in
        `	sethi	%hi({emit_label lbl_jumptbl}), %g1\n`;
        `	or	%g1, %lo({emit_label lbl_jumptbl}), %g1\n`;
        `	sll	{emit_reg i.arg.(0)}, 2, %g2\n`;
        `	ld	[%g1 + %g2], %g1\n`;
        `	jmp	%g1\n`;         (* poor scheduling *)
        `	nop\n`;
        `{emit_label lbl_jumptbl}:`;
        for i = 0 to Array.length jumptbl - 1 do
          `	.word	{emit_label jumptbl.(i)}\n`
        done
    | Lsetuptrap lbl ->
        `	call	{emit_label lbl}\n`;
        `	sub	%sp, 8, %sp\n`  (* in delay slot *)
    | Lpushtrap ->
        stack_offset := !stack_offset + 8;
        `	st	%o7, [%sp + 96]\n`;
        `	st	%l5, [%sp + 100]\n`;
        `	mov	%sp, %l5\n`
    | Lpoptrap ->
        `	ld	[%sp + 100], %l5\n`;
        `	add	%sp, 8, %sp\n`;
        stack_offset := !stack_offset - 8
    | Lraise ->
        `	ld	[%l5 + 96], %g1\n`;
        `	mov	%l5, %sp\n`;
        `	ld	[%sp + 100], %l5\n`;
        `	jmp	%g1 + 8\n`;
        `	add	%sp, 8, %sp\n`

and fill_delay_slot = function
    None -> `	nop\n`
  | Some i -> emit_instr i None

(* Checks if a pseudo-instruction expands to exactly one machine instruction
   that does not branch. *)

let is_one_instr_op = function
    Idiv | Imod | Icomp _ | Icheckbound -> false
  | _ -> true

let is_one_instr i =
  match i.desc with
    Lop op ->
      begin match op with
        Imove | Ispill | Ireload ->
          i.arg.(0).typ <> Float && i.res.(0).typ <> Float
      | Iconst_int n -> is_native_immediate n
      | Istackoffset _ -> true
      | Iload(_, Iindexed n) -> i.res.(0).typ <> Float & is_immediate n
      | Istore(_, Iindexed n) -> i.arg.(0).typ <> Float & is_immediate n
      | Iintop(op) -> is_one_instr_op op
      | Iintop_imm(op, _) -> is_one_instr_op op
      | Iaddf | Isubf | Imulf | Idivf -> true
      | Iabsf | Inegf -> !arch_version = SPARC_V9
      | _ -> false
      end
  | _ -> false

let no_interference res arg =
  try
    for i = 0 to Array.length arg - 1 do
      for j = 0 to Array.length res - 1 do
        if arg.(i).loc = res.(j).loc then raise Exit
      done
    done;
    true
  with Exit ->
    false

(* Emit a sequence of instructions, trying to fill delay slots for branches *)

let rec emit_all i =
  match i with
    {desc = Lend} -> ()
  | {next = {desc = Lop(Icall_imm _) | Lop(Iextcall(_, false)) | Lbranch _}}
    when is_one_instr i ->
      emit_instr i.next (Some i);
      emit_all i.next.next
  | {next = {desc = Lop(Itailcall_imm s)}}
    when s = !function_name & is_one_instr i ->
      emit_instr i.next (Some i);
      emit_all i.next.next
  | {next = {desc = Lop(Icall_ind)}}
    when is_one_instr i & no_interference i.res i.next.arg ->
      emit_instr i.next (Some i);
      emit_all i.next.next
  | {next = {desc = Lcondbranch(_, _)}}
    when is_one_instr i & no_interference i.res i.next.arg ->
      emit_instr i.next (Some i);
      emit_all i.next.next
  | _ ->
      emit_instr i None;
      emit_all i.next

(* Emission of a function declaration *)

let fundecl fundecl =
  function_name := fundecl.fun_name;
  fastcode_flag := fundecl.fun_fast;
  tailrec_entry_point := new_label();
  range_check_trap := 0;
  stack_offset := 0;
  float_constants := [];
  `	.text\n`;
  `	.align	4\n`;
  `	.global	{emit_symbol fundecl.fun_name}\n`;
  if Config.system = "solaris" then
    `	.type	{emit_symbol fundecl.fun_name},#function\n`;
  `{emit_symbol fundecl.fun_name}:\n`;
  if !Clflags.gprofile then emit_profile();
  let n = frame_size() in
  if n > 0 then
    `	sub	%sp, {emit_int n}, %sp\n`;
  if !contains_calls then
    `	st	%o7, [%sp + {emit_int(n - 4 + 96)}]\n`;
  `{emit_label !tailrec_entry_point}:\n`;
  emit_all fundecl.fun_body;
  if !range_check_trap > 0 then begin
    `{emit_label !range_check_trap}:\n`;
    `	call	{emit_symbol "caml_ml_array_bound_error"}\n`;
    `	nop\n`
  end;
  emit_size fundecl.fun_name;
  List.iter emit_float_constant !float_constants

(* Emission of data *)

let emit_item = function
    Cglobal_symbol s ->
      `	.global	{emit_symbol s}\n`;
  | Cdefine_symbol s ->
      `{emit_symbol s}:\n`
  | Cdefine_label lbl ->
      `{emit_label (lbl + 100000)}:\n`
  | Cint8 n ->
      `	.byte	{emit_int n}\n`
  | Cint16 n ->
      `	.half	{emit_int n}\n`
  | Cint32 n ->
      `	.word	{emit_nativeint n}\n`
  | Cint n ->
      `	.word	{emit_nativeint n}\n`
  | Csingle f ->
      emit_float32_directive ".word" f
  | Cdouble f ->
      emit_float64_split_directive ".word" f
  | Csymbol_address s ->
      `	.word	{emit_symbol s}\n`
  | Clabel_address lbl ->
      `	.word	{emit_label (lbl + 100000)}\n`
  | Cstring s ->
      emit_string_directive "	.ascii	" s
  | Cskip n ->
      if n > 0 then `	.skip	{emit_int n}\n`
  | Calign n ->
      `	.align	{emit_int n}\n`

let data l =
  `	.data\n`;
  List.iter emit_item l

(* Beginning / end of an assembly file *)

let begin_assembly() =
  let lbl_begin = Compilenv.make_symbol (Some "data_begin") in
  `	.data\n`;
  `	.global	{emit_symbol lbl_begin}\n`;
  `{emit_symbol lbl_begin}:\n`;
  let lbl_begin = Compilenv.make_symbol (Some "code_begin") in
  `	.text\n`;
  `	.global	{emit_symbol lbl_begin}\n`;
  `{emit_symbol lbl_begin}:\n`

let end_assembly() =
  `	.text\n`;
  let lbl_end = Compilenv.make_symbol (Some "code_end") in
  `	.global	{emit_symbol lbl_end}\n`;
  `{emit_symbol lbl_end}:\n`;
  `	.data\n`;
  let lbl_end = Compilenv.make_symbol (Some "data_end") in
  `	.global	{emit_symbol lbl_end}\n`;
  `{emit_symbol lbl_end}:\n`;
  `	.word	0\n`;
  let lbl = Compilenv.make_symbol (Some "frametable") in
  rodata ();
  `	.global	{emit_symbol lbl}\n`;
  if Config.system = "solaris" then
    `	.type   {emit_symbol lbl},#object\n`;
  `{emit_symbol lbl}:\n`;
  `	.word	{emit_int (List.length !frame_descriptors)}\n`;
  List.iter emit_frame !frame_descriptors;
  emit_size lbl;
  frame_descriptors := []
