(***********************************************************************) (* *) (* Objective Caml *) (* *) (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) (* *) (* Copyright 1996 Institut National de Recherche en Informatique et *) (* Automatique. Distributed only by permission. *) (* *) (***********************************************************************) (* $Id$ *) (* Emission of Sparc 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 (* 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 + 15) | _ -> fatal_error "Emit.next_in_pair" (* Symbols are prefixed with _ under SunOS and BSD but not under Solaris nor Linux *) let symbol_prefix = if Config.system = "solaris" || Config.system = "linux" 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 (* Check if an integer or native integer is an immediate operand *) let is_immediate n = n <= 4095 && n >= -4096 let is_native_immediate n = Nativeint.cmp n 4095 <= 0 && Nativeint.cmp n (-4096) >= 0 (* Output a label *) let label_prefix = if Config.system = "solaris" || Config.system = "linux" 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) = ` .data\n`; ` .align 8\n`; `{emit_label lbl}: .double 0r{emit_string cst}\n` (* 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" | _ -> Misc.fatal_error "Emit.name_for_int_operation" let name_for_float_operation = function Inegf -> "fnegs" | Iabsf -> "fabss" | Iaddf -> "faddd" | Isubf -> "fsubd" | Imulf -> "fmuld" | Idivf -> "fdivd" | _ -> Misc.fatal_error "Emit.name_for_float_operation" 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 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} -> ` fmovs {emit_reg src}, {emit_reg dst}\n`; ` fmovs {emit_reg(next_in_pair src)}, {emit_reg(next_in_pair dst)}\n` | {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`; if rd land 1 = 0 then ` ldd [%sp + 96], {emit_reg dst}\n` else begin ` ld [%sp + 96], {emit_reg dst}\n`; ` ld [%sp + 100], {emit_reg(next_in_pair dst)}\n` end; ` 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) -> 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)) -> begin match i.res.(0).typ with Int | Addr -> let loadinstr = match chunk with Word -> "ld" | Byte_unsigned -> "ldub" | Byte_signed -> "ldsb" | Sixteen_unsigned -> "lduh" | Sixteen_signed -> "ldsh" in emit_load loadinstr addr i.arg i.res.(0) | Float -> emit_load "ld" addr i.arg i.res.(0); emit_load "ld" (offset_addressing addr 4) i.arg (next_in_pair i.res.(0)) end | Lop(Istore(chunk, addr)) -> begin match i.arg.(0).typ with Int | Addr -> let storeinstr = match chunk with Word -> "st" | Byte_unsigned | Byte_signed -> "stb" | Sixteen_unsigned | Sixteen_signed -> "sth" in emit_store storeinstr addr i.arg i.arg.(0) | Float -> emit_store "st" addr i.arg i.arg.(0); emit_store "st" (offset_addressing addr 4) i.arg (next_in_pair i.arg.(0)) end | Lop(Ialloc n) -> if !fastcode_flag then begin let lbl_cont = new_label() in ` ld [%l7], %g1\n`; ` sub %l6, {emit_int n}, %l6\n`; ` cmp %l6, %g1\n`; ` 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_alloc"}\n`; ` mov {emit_int n}, %g2\n`; (* in delay slot *) ` add %l6, 4, {emit_reg i.res.(0)}\n` end | Lop(Iintop(Icomp cmp)) -> let comp = name_for_int_comparison cmp in ` cmp {emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`; let 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` | Lop(Iintop Icheckbound) -> ` cmp {emit_reg i.arg.(0)}, {emit_reg i.arg.(1)}\n`; ` tleu 5\n` (* 5 = ST_RANGE_CHECK *) | 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(Idiv, n)) -> (* n is a power of 2 *) let l = Misc.log2 n in 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` | 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)) -> let comp = name_for_int_comparison cmp in ` cmp {emit_reg i.arg.(0)}, {emit_int n}\n`; let 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` | Lop(Iintop_imm(Icheckbound, n)) -> ` cmp {emit_reg i.arg.(0)}, {emit_int n}\n`; ` tleu 5\n` (* 5 = ST_RANGE_CHECK *) | 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`; ` 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) -> fatal_error "Emit: specific" | Lreloadretaddr -> let n = frame_size() in ` ld [%sp + {emit_int(n - 4 + 96)}], %o7\n` | Lreturn -> let n = frame_size() in ` retl\n`; ` 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 | _ -> 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(); stack_offset := 0; float_constants := []; ` .text\n`; ` .align 4\n`; ` .global {emit_symbol fundecl.fun_name}\n`; `{emit_symbol fundecl.fun_name}:\n`; 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; List.iter emit_float_constant !float_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 (lbl + 100000)}:\n` | Cint8 n -> ` .byte {emit_int n}\n` | Cint16 n -> ` .half {emit_int n}\n` | Cint n -> ` .word {emit_nativeint n}\n` | Cfloat f -> ` .double 0r{emit_string f}\n` | Csymbol_address s -> ` .word {emit_symbol s}\n` | Clabel_address lbl -> ` .word {emit_label (lbl + 100000)}\n` | Cstring s -> let l = String.length s in if l = 0 then () else if l < 80 then ` .ascii {emit_string_literal s}\n` else begin let i = ref 0 in while !i < l do let n = min (l - !i) 80 in ` .ascii {emit_string_literal(String.sub s !i n)}\n`; i := !i + n done end | 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.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() = ` .text\n`; let lbl_end = Compilenv.current_unit_name() ^ "_code_end" in ` .global {emit_symbol lbl_end}\n`; `{emit_symbol lbl_end}:\n`; ` .data\n`; let lbl_end = Compilenv.current_unit_name() ^ "_data_end" in ` .global {emit_symbol lbl_end}\n`; `{emit_symbol lbl_end}:\n`; ` .word 0\n`; let lbl = Compilenv.current_unit_name() ^ "_frametable" in ` .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 := []