(***********************************************************************)
(*                                                                     *)
(*                           Objective Caml                            *)
(*                                                                     *)
(*            Xavier Leroy, projet Cristal, INRIA Rocquencourt         *)
(*                                                                     *)
(*  Copyright 1998 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,v 1.11 2001/04/02 08:14:51 xleroy Exp $ *)

(* Emission of ARM assembly code *)

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

(* Tradeoff between code size and code speed *)

let fastcode_flag = ref true

(* Output a label *)

let emit_label lbl =
  emit_string ".L"; emit_int lbl

(* Output a symbol *)

let emit_symbol s =
  Emitaux.emit_symbol '$' s

(* Output a pseudo-register *)

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

(* Output the next register after the given pseudo-register *)

let emit_next_reg r =
  match r.loc with
    Reg r -> emit_string (register_name(r + 1))
  | _ -> fatal_error "Emit_arm.emit_next_reg"

(* Layout of the stack frame *)

let stack_offset = ref 0

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

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

(* 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_arm.emit_stack"

(* Output an addressing mode *)

let emit_addressing addr r n =
  match addr with
    Iindexed ofs ->
      `[{emit_reg r.(n)}, #{emit_int ofs}]`

(* 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} + 4\n`;
  `	.short	{emit_int fd.fd_frame_size}\n`;
  `	.short	{emit_int (List.length fd.fd_live_offset)}\n`;
  List.iter
    (fun n ->
      `	.short	{emit_int n}\n`)
    fd.fd_live_offset;
  `	.align	2\n`

(* Names of various instructions *)

let name_for_comparison = function
    Isigned Ceq -> "eq" | Isigned Cne -> "ne" | Isigned Cle -> "le"
  | Isigned Cge -> "ge" | Isigned Clt -> "lt" | Isigned Cgt -> "gt"
  | Iunsigned Ceq -> "eq" | Iunsigned Cne -> "ne" | Iunsigned Cle -> "ls"
  | Iunsigned Cge -> "cs" | Iunsigned Clt -> "cc" | Iunsigned Cgt -> "hi"

let name_for_float_comparison cmp neg =
  match cmp with
    Ceq -> if neg then "ne" else "eq"
  | Cne -> if neg then "eq" else "ne"
  | Cle -> if neg then "hi" else "ls"
  | Cge -> if neg then "lt" else "ge"
  | Clt -> if neg then "pl" else "mi"
  | Cgt -> if neg then "le" else "gt"

let name_for_int_operation = function
    Iadd -> "add"
  | Isub -> "sub"
  | Imul -> "mul"
  | Iand  -> "and"
  | Ior   -> "orr"
  | Ixor  -> "eor"
  | _ -> assert false

let name_for_shift_operation = function
    Ilsl -> "lsl"
  | Ilsr -> "lsr"
  | Iasr -> "asr"
  | _ -> assert false

let name_for_shift_int_operation = function
    Ishiftadd -> "add"
  | Ishiftsub -> "sub"
  | Ishiftsubrev -> "rsb"

let name_for_float_operation = function
    Inegf -> "mnfd"
  | Iabsf -> "absd"
  | Iaddf -> "adfd"
  | Isubf -> "sufd"
  | Imulf -> "mufd"
  | Idivf -> "dvfd"
  | Ifloatofint -> "fltd"
  | Iintoffloat -> "fixz"
  | _ -> assert false

(* Recognize immediate operands *)

(* Immediate operands are 8-bit immediate values, zero-extended, and rotated
   right by 0, 2, 4, ... 30 bits.
   We check only with 8-bit values shifted left 0 to 24 bits. *)

let rec is_immed n shift =
  shift <= 24 &&
  (Nativeint.logand n (Nativeint.shift_left (Nativeint.of_int 0xFF) shift) = n
   || is_immed n (shift + 2))

let is_immediate n = is_immed n 0

(* General functional to decompose a non-immediate integer constant
   into 8-bit chunks shifted left 0 ... 24 bits *)

let decompose_intconst n fn =
  let i = ref n in
  let shift = ref 0 in
  let ninstr = ref 0 in
  while !i <> Nativeint.zero do
    if Nativeint.to_int (Nativeint.shift_right !i !shift) land 3 = 0 then
      shift := !shift + 2
    else begin
      let mask = Nativeint.shift_left (Nativeint.of_int 0xFF) !shift in
      let bits = Nativeint.logand !i mask in
      fn bits;
      shift := !shift + 8;
      i := Nativeint.sub !i bits;
      incr ninstr
    end
  done;
  !ninstr

(* Emit a non-immediate integer constant *)

let emit_complex_intconst r n =
  let first = ref true in
  decompose_intconst n
    (fun bits ->
      if !first
      then `	mov	{emit_reg r}, #{emit_nativeint bits} @ {emit_nativeint n}\n`
      else `	add	{emit_reg r}, {emit_reg r}, #{emit_nativeint bits}\n`;
      first := false)

(* Adjust sp (up or down) by the given byte amount *)

let emit_stack_adjustment instr n =
  if n <= 0 then 0 else
    decompose_intconst (Nativeint.of_int n)
      (fun bits ->
        `	{emit_string instr}	sp, sp, #{emit_nativeint bits}\n`)

(* Adjust alloc_ptr down by the given byte amount *)

let emit_alloc_decrement n =
  decompose_intconst (Nativeint.of_int n)
    (fun bits ->
       `	sub	alloc_ptr, alloc_ptr, #{emit_nativeint bits}\n`)

(* Name of current function *)
let function_name = ref ""
(* Entry point for tail recursive calls *)
let tailrec_entry_point = ref 0
(* Table of symbols referenced *)
let symbol_constants = (Hashtbl.create 11 : (string, int) Hashtbl.t)
(* Table of floating-point literals *)
let float_constants = (Hashtbl.create 11 : (string, int) Hashtbl.t)
(* Total space (in word) occupied by pending literals *)
let num_literals = ref 0
(* True if we've at least one pending float literal *)
let pending_float = ref false

(* Label a symbol or float constant *)
let label_constant tbl s size =
  try
    Hashtbl.find tbl s
  with Not_found ->
    let lbl = new_label() in
    Hashtbl.add tbl s lbl;
    num_literals := !num_literals + size;
    lbl

(* Emit all pending constants *)

let emit_constants () =
  Hashtbl.iter
    (fun s lbl ->
      `{emit_label lbl}:        .word   {emit_symbol s}\n`)
    symbol_constants;
  Hashtbl.iter
    (fun s lbl ->
      `{emit_label lbl}:        .double {emit_string s}\n`)
    float_constants;
  Hashtbl.clear symbol_constants;
  Hashtbl.clear float_constants;
  num_literals := 0;
  pending_float := false

(* Output the assembly code for an instruction *)

let emit_instr i =
    match i.desc with
      Lend -> 0
    | Lop(Imove | Ispill | Ireload) ->
        let src = i.arg.(0) and dst = i.res.(0) in
        if src.loc = dst.loc then 0 else begin
          match (src, dst) with
            {loc = Reg rs; typ = Int|Addr}, {loc = Reg rd; typ = Int|Addr} ->
              `	mov	{emit_reg dst}, {emit_reg src}\n`; 1
          | {loc = Reg rs; typ = Float}, {loc = Reg rd; typ = Float} ->
              `	mvfd	{emit_reg dst}, {emit_reg src}\n`; 1
          | {loc = Reg rs; typ = Float}, {loc = Reg rd; typ = Int|Addr} ->
              `	stfd	{emit_reg src}, [sp, #-8]!\n`;
              `	ldmfd	sp!, \{{emit_reg dst}, {emit_next_reg dst}}\n`; 2
          | {loc = Reg rs; typ = Int|Addr}, {loc = Stack sd} ->
              `	str	{emit_reg src}, {emit_stack dst}\n`; 1
          | {loc = Reg rs; typ = Float}, {loc = Stack sd} ->
              `	stfd	{emit_reg src}, {emit_stack dst}\n`; 1
          | {loc = Stack ss; typ = Int|Addr}, {loc = Reg rd} ->
              `	ldr	{emit_reg dst}, {emit_stack src}\n`; 1
          | {loc = Stack ss; typ = Float}, {loc = Reg rd} ->
              `	ldfd	{emit_reg dst}, {emit_stack src}\n`; 1
          | _ ->
              assert false
        end
    | Lop(Iconst_int n) ->
        let r = i.res.(0) in
        let nr = Nativeint.lognot n in
        if is_immediate n then begin
          `	mov	{emit_reg r}, #{emit_nativeint n}\n`; 1
        end else if is_immediate nr then begin
          `	mvn	{emit_reg r}, #{emit_nativeint nr}\n`; 1
        end else
          emit_complex_intconst r n
    | Lop(Iconst_float s) ->
        if float_of_string s = 0.0 then
          `	mvfd	{emit_reg i.res.(0)}, #0.0\n`
        else begin
          let lbl = label_constant float_constants s 2 in
          pending_float := true;
          `	ldfd	{emit_reg i.res.(0)}, {emit_label lbl} @ {emit_string s}\n`
        end;
        1
    | Lop(Iconst_symbol s) ->
        let lbl = label_constant symbol_constants s 1 in
        `	ldr	{emit_reg i.res.(0)}, {emit_label lbl} @ {emit_symbol s}\n`; 1
    | Lop(Icall_ind) ->
        `	mov	lr, pc\n`;
        `{record_frame i.live}  mov     pc, {emit_reg i.arg.(0)}\n`; 2
    | Lop(Icall_imm s) ->
        `{record_frame i.live}  bl      {emit_symbol s}\n`; 1
    | Lop(Itailcall_ind) ->
        let n = frame_size() in
        if !contains_calls then
          `	ldr	lr, [sp, #{emit_int (n-4)}]\n`;
        ignore (emit_stack_adjustment "add" n);
        `	mov	pc, {emit_reg i.arg.(0)}\n`; 3
    | Lop(Itailcall_imm s) ->
        if s = !function_name then begin
          `	b	{emit_label !tailrec_entry_point}\n`; 1
        end else begin
          let n = frame_size() in
          if !contains_calls then
            `	ldr	lr, [sp, #{emit_int (n-4)}]\n`;
          ignore (emit_stack_adjustment "add" n);
          `	b	{emit_symbol s}\n`; 3
        end
    | Lop(Iextcall(s, alloc)) ->
        if alloc then begin
          let lbl = label_constant symbol_constants s 1 in
          `	ldr	r10, {emit_label lbl} @ {emit_symbol s}\n`;
          `{record_frame i.live}        bl      caml_c_call\n`; 2
        end else begin
          `	bl	{emit_symbol s}\n`; 1
        end
    | Lop(Istackoffset n) ->
        let ninstr =
          if n >= 0
          then emit_stack_adjustment "sub" n
          else emit_stack_adjustment "add" (-n) in
        stack_offset := !stack_offset + n;
        ninstr
    | Lop(Iload(Single, addr)) ->
        let r = i.res.(0) in
        `	ldfs	{emit_reg	r}, {emit_addressing addr i.arg 0}\n`;
        `	mvfd	{emit_reg	r}, {emit_reg r}\n`;
        2
    | Lop(Iload(size, addr)) ->
        let r = i.res.(0) in
        let instr =
          match size with
            Byte_unsigned -> "ldrb"
          | Byte_signed -> "ldrsb"
          | Sixteen_unsigned -> "ldrh"
          | Sixteen_signed -> "ldrsh"
          | Double | Double_u -> "ldfd"
          | _ (* Word | Thirtytwo_signed | Thirtytwo_unsigned *) -> "ldr" in
        `	{emit_string	instr}     {emit_reg r}, {emit_addressing addr i.arg 0}\n`;
        1
    | Lop(Istore(Single, addr)) ->
        let r = i.arg.(0) in
        `	mvfs	f7,	{emit_reg r}\n`;
        `	stfs	f7,	{emit_addressing addr i.arg 1}\n`;
        2
    | Lop(Istore(size, addr)) ->
        let r = i.arg.(0) in
        let instr =
          match size with
            Byte_unsigned | Byte_signed -> "strb"
          | Sixteen_unsigned | Sixteen_signed -> "strh"
          | Double | Double_u -> "stfd"
          | _ (* Word | Thirtytwo_signed | Thirtytwo_unsigned *) -> "str" in
        `	{emit_string	instr}     {emit_reg r}, {emit_addressing addr i.arg 1}\n`;
        1
    | Lop(Ialloc n) ->
        if !fastcode_flag then begin
          `	ldr	r10, [alloc_limit, #0]\n`;
          let ni = emit_alloc_decrement n in
          `	cmp	alloc_ptr, r10\n`;
          `{record_frame i.live}        blcc    caml_call_gc\n`;
          `	add	{emit_reg i.res.(0)}, alloc_ptr, #4\n`;
          4 + ni
        end else if n = 8 || n = 12 || n = 16 then begin
            `{record_frame i.live}      bl      caml_alloc{emit_int ((n-4)/4)}\n`;
            `	add	{emit_reg i.res.(0)}, alloc_ptr, #4\n`; 2
        end else begin
            let nn = Nativeint.of_int n in
            let ni =
              if is_immediate nn then begin
                `	mov	r10, #{emit_int n}\n`; 1
              end else
                emit_complex_intconst (phys_reg 8 (*r10*)) nn in
            `{record_frame i.live}      bl      caml_alloc\n`;
            `	add	{emit_reg i.res.(0)}, alloc_ptr, #4\n`;
            2 + ni
        end
    | Lop(Iintop(Ilsl | Ilsr | Iasr as op)) ->
        let shift = name_for_shift_operation op in
        `	mov	{emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, {emit_string shift} {emit_reg i.arg.(1)}\n`; 1
    | Lop(Iintop(Icomp cmp)) ->
        let comp = name_for_comparison cmp in
        `	cmp	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
        `	mov	{emit_reg i.res.(0)}, #0\n`;
        `	mov{emit_string	comp}   {emit_reg i.res.(0)}, #1\n`; 3
    | Lop(Iintop(Icheckbound)) ->
        `	cmp	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
        `	blls	caml_array_bound_error\n`; 2
    | Lop(Iintop op) ->
        let instr = name_for_int_operation op in
        `	{emit_string	instr}     {emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`; 1
    | Lop(Iintop_imm(Idiv, n)) -> (* n is a power of 2 *)
        let l = Misc.log2 n in
        let r = i.res.(0) in
        `	movs	{emit_reg r}, {emit_reg i.arg.(0)}\n`;
        if n <= 256 then
          `	addlt	{emit_reg r}, {emit_reg r}, #{emit_int (n-1)}\n`
        else begin
          `	addlt	{emit_reg r}, {emit_reg r}, #{emit_int n}\n`;
          `	sublt	{emit_reg r}, {emit_reg r}, #1\n`
        end;
        `	mov	{emit_reg r}, {emit_reg r}, asr #{emit_int l}\n`; 4
    | Lop(Iintop_imm(Imod, n)) -> (* n is a power of 2 *)
        let l = Misc.log2 n in
        let a = i.arg.(0) in
        let r = i.res.(0) in
        let lbl = new_label() in
        `	cmp	{emit_reg a}, #0\n`;
        `	mov	{emit_reg r}, {emit_reg a}, lsl #{emit_int (32-l)}\n`;
        `	mov	{emit_reg r}, {emit_reg r}, lsr #{emit_int (32-l)}\n`;
        `	bpl	{emit_label lbl}\n`;
        `	cmp	{emit_reg r}, #0\n`;
        `	subne	{emit_reg r}, {emit_reg r}, #{emit_int n}\n`;
        `{emit_label lbl}:\n`; 6
    | Lop(Iintop_imm((Ilsl | Ilsr | Iasr as op), n)) ->
        let shift = name_for_shift_operation op in
        `	mov	{emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, {emit_string shift} #{emit_int n}\n`; 1
    | Lop(Iintop_imm(Icomp cmp, n)) ->
        let comp = name_for_comparison cmp in
        `	cmp	{emit_reg i.arg.(0)}, #{emit_int n}\n`;
        `	mov	{emit_reg i.res.(0)}, #0\n`;
        `	mov{emit_string	comp}   {emit_reg i.res.(0)}, #1\n`; 3
    | Lop(Iintop_imm(Icheckbound, n)) ->
        `	cmp	{emit_reg i.arg.(0)}, #{emit_int n}\n`;
        `	blls	caml_array_bound_error\n`; 2
    | Lop(Iintop_imm(op, n)) ->
        let instr = name_for_int_operation op in
        `	{emit_string	instr}     {emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, #{emit_int n}\n`; 1
    | Lop(Inegf | Iabsf | Ifloatofint | Iintoffloat as op) ->
        let instr = name_for_float_operation op in
        `	{emit_string	instr}     {emit_reg i.res.(0)}, {emit_reg i.arg.(0)}\n`; 1
    | Lop(Iaddf | Isubf | Imulf | Idivf as op) ->
        let instr = name_for_float_operation op in
        `	{emit_string	instr}     {emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`; 1
    | Lop(Ispecific(Ishiftarith(op, shift))) ->
        let instr = name_for_shift_int_operation op in
        `	{emit_string	instr}     {emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}`;
        if shift >= 0
        then `, lsl #{emit_int shift}\n`
        else `, asr #{emit_int (-shift)}\n`;
        1
    | Lop(Ispecific(Ishiftcheckbound shift)) ->
        `	cmp	{emit_reg i.arg.(1)}, {emit_reg i.arg.(0)}, lsr #{emit_int shift}\n`;
        `	blcs	caml_array_bound_error\n`; 2
    | Lop(Ispecific(Irevsubimm n)) ->
        `	rsb	{emit_reg i.res.(0)}, {emit_reg i.arg.(0)}, #{emit_int n}\n`; 1
    | Lreloadretaddr ->
        let n = frame_size() in
        `	ldr	lr, [sp, #{emit_int(n-4)}]\n`; 1
    | Lreturn ->
        let n = frame_size() in
        ignore(emit_stack_adjustment "add" n);
        `	mov	pc, lr\n`; 2
    | Llabel lbl ->
        `{emit_label lbl}:\n`; 0
    | Lbranch lbl ->
        `	b	{emit_label lbl}\n`; 1
    | Lcondbranch(tst, lbl) ->
        begin match tst with
          Itruetest ->
            `	cmp	{emit_reg i.arg.(0)}, #0\n`;
            `	bne	{emit_label lbl}\n`
        | Ifalsetest ->
            `	cmp	{emit_reg i.arg.(0)}, #0\n`;
            `	beq	{emit_label lbl}\n`
        | Iinttest cmp ->
            `	cmp	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`;
            let comp = name_for_comparison cmp in
            `	b{emit_string	comp}     {emit_label lbl}\n`
        | Iinttest_imm(cmp, n) ->
            `	cmp	{emit_reg i.arg.(0)}, #{emit_int n}\n`;
            let comp = name_for_comparison cmp in
            `	b{emit_string	comp}     {emit_label lbl}\n`
        | Ifloattest(cmp, neg) ->
            begin match cmp with
              Ceq | Cne ->
                `	cmf	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`
            | _ ->
                `	cmfe	{emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`
            end;
            let comp = name_for_float_comparison cmp neg in
            `	b{emit_string	comp}     {emit_label lbl}\n`
        | Ioddtest ->
            `	tst	{emit_reg i.arg.(0)}, #1\n`;
            `	bne	{emit_label lbl}\n`
        | Ieventest ->
            `	tst	{emit_reg i.arg.(0)}, #1\n`;
            `	beq	{emit_label lbl}\n`
        end;
        2
  | Lcondbranch3(lbl0, lbl1, lbl2) ->
        `	cmp	{emit_reg i.arg.(0)}, #1\n`;
        begin match lbl0 with
          None -> ()
        | Some lbl -> `	blt	{emit_label lbl}\n`
        end;
        begin match lbl1 with
          None -> ()
        | Some lbl -> `	beq	{emit_label lbl}\n`
        end;
        begin match lbl2 with
          None -> ()
        | Some lbl -> `	bgt	{emit_label lbl}\n`
        end;
        4
  | Lswitch jumptbl ->
        `	ldr	pc, [pc, {emit_reg i.arg.(0)}, lsl #2]\n`;
        `	mov	r0, r0\n`;      (* nop *)
        for i = 0 to Array.length jumptbl - 1 do
          `	.word	{emit_label jumptbl.(i)}\n`
        done;
        2 + Array.length jumptbl
    | Lsetuptrap lbl ->
        `	bl	{emit_label lbl}\n`; 1
    | Lpushtrap ->
        stack_offset := !stack_offset + 8;
        `	stmfd	sp!, \{trap_ptr, lr}\n`;
        `	mov	trap_ptr, sp\n`; 2
    | Lpoptrap ->
        `	ldmfd	sp!, \{trap_ptr, lr}\n`;
        stack_offset := !stack_offset - 8; 1
    | Lraise ->
        `	mov	sp, trap_ptr\n`;
        `	ldmfd	sp!, \{trap_ptr, pc}\n`; 2

(* Emission of an instruction sequence *)

let no_fallthrough = function
    Lop(Itailcall_ind | Itailcall_imm _) -> true
  | Lreturn -> true
  | Lbranch _ -> true
  | Lswitch _ -> true
  | Lraise -> true
  | _ -> false

let rec emit_all ninstr i =
  if i.desc = Lend then () else begin
    let n = emit_instr i in
    let ninstr' = ninstr + n in
    let limit = (if !pending_float then 127 else 511) - !num_literals in
    if ninstr' >= limit - 32 && no_fallthrough i.desc then begin
      emit_constants();
      emit_all 0 i.next
    end else
    if ninstr' >= limit then begin
      let lbl = new_label() in
      `	b	{emit_label lbl}\n`;
      emit_constants();
      `{emit_label lbl}:\n`;
      emit_all 0 i.next
    end else
      emit_all ninstr' i.next
  end

(* Emission of a function declaration *)

let fundecl fundecl =
  function_name := fundecl.fun_name;
  fastcode_flag := fundecl.fun_fast;
  tailrec_entry_point := new_label();
  stack_offset := 0;
  Hashtbl.clear symbol_constants;
  Hashtbl.clear float_constants;
  `	.text\n`;
  `	.align	0\n`;
  `	.global	{emit_symbol fundecl.fun_name}\n`;
  `{emit_symbol fundecl.fun_name}:\n`;
  let n = frame_size() in
  ignore(emit_stack_adjustment "sub" n);
  if !contains_calls then
    `	str	lr, [sp, #{emit_int(n - 4)}]\n`;
  `{emit_label !tailrec_entry_point}:\n`;
  emit_all 0 fundecl.fun_body;
  emit_constants()

(* Emission of data *)

let emit_item = function
    Cdefine_symbol s ->
      `	.global	{emit_symbol s}\n`;
      `{emit_symbol s}:\n`
  | Cdefine_label lbl ->
      `{emit_label (10000 + lbl)}:\n`
  | Cint8 n ->
      `	.byte	{emit_int n}\n`
  | Cint16 n ->
      `	.short	{emit_int n}\n`
  | Cint32 n ->
      `	.word	{emit_nativeint	n}\n`
  | Cint n ->
      `	.word	{emit_nativeint n}\n`
  | Csingle f ->
      `	.float	{emit_string	f}\n`
  | Cdouble f ->
      `	.align	0\n`;
      `	.double	{emit_string f}\n`
  | Csymbol_address s ->
      `	.word	{emit_symbol s}\n`
  | Clabel_address lbl ->
      `	.word	{emit_label (10000 + lbl)}\n`
  | Cstring s ->
      emit_string_directive "   .ascii  " s
  | Cskip n ->
      if n > 0 then `	.space	{emit_int n}\n`
  | Calign n ->
      `	.align	{emit_int(Misc.log2 n)}\n`

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

(* Beginning / end of an assembly file *)

let begin_assembly() =
  `trap_ptr     .req    r11\n`;
  `alloc_ptr    .req    r8\n`;
  `alloc_limit  .req    r9\n`;
  `sp           .req    r13\n`;
  `lr           .req    r14\n`;
  `pc           .req    r15\n`;
  let lbl_begin = Compilenv.current_unit_name() ^ "_data_begin" in
  `	.data\n`;
  `	.global	{emit_symbol lbl_begin}\n`;
  `{emit_symbol lbl_begin}:\n`;
  let lbl_begin = Compilenv.current_unit_name() ^ "_code_begin" in
  `	.text\n`;
  `	.global	{emit_symbol lbl_begin}\n`;
  `{emit_symbol lbl_begin}:\n`

let end_assembly () =
  let lbl_end = Compilenv.current_unit_name() ^ "_code_end" in
  `	.text\n`;
  `	.global	{emit_symbol lbl_end}\n`;
  `{emit_symbol lbl_end}:\n`;
  let lbl_end = Compilenv.current_unit_name() ^ "_data_end" in
  `	.data\n`;
  `	.global	{emit_symbol lbl_end}\n`;
  `{emit_symbol lbl_end}:\n`;
  `	.word	0\n`;
  let lbl = Compilenv.current_unit_name() ^ "_frametable" in
  `	.data\n`;
  `	.global	{emit_symbol lbl}\n`;
  `{emit_symbol lbl}:\n`;
  `	.word	{emit_int (List.length !frame_descriptors)}\n`;
  List.iter emit_frame !frame_descriptors;
  frame_descriptors := []
