# -*- Mode: Python -*-

import string
import sys
import itypes
W = sys.stderr.write

from pdb import set_trace as trace

is_a = isinstance

class RegisterError:
    pass

class function:
    def __init__ (self, name):
        self.name = name
        self.known_allocs = 0

class c_backend:

    def __init__ (self, out, src, num_regs, context):
        self.out = out
        self.src = src
        self.num_regs = num_regs
        self.indent = 0
        self.context = context
        self.annotate = context.annotate
        self.trace = context.trace
        self.profile = context.profile
        self.toplevel_env = False
        self.current_fun = function ('toplevel')
        self.write_header()
        self.max_free = 0

    def go (self, insns):
        self.emit (insns)
        self.done()

    def write_header (self):
        for header in self.context.cincludes:
            self.out.write ('#include <%s>\n' % (header,))
        header = open ('header.c').read()
        # XXX ugh, this is getting lame.
        tag0 = '// CONSTRUCTED LITERALS //\n'
        stop0 = header.find (tag0) + len (tag0)
        self.out.write (header[:stop0])
        self.emit_constructed_literals()
        if self.profile:
            self.emit_profile_counters()
        tag1 = '// REGISTER_DECLARATIONS //\n'
        stop1 = header.find (tag1) + len (tag1)
        self.out.write (header[stop0:stop1])
        self.out.write (self.register_declarations())
        self.indent = 1
        self.emit_gc_copy_regs (self.num_regs)
        self.out.write (header[stop1:])

    def register_declarations (self):
        return '\n'.join (["  register object * r%d;" % i for i in range (self.num_regs + 1) ])

    def emit_constructed_literals (self):

        # we support three types of non-immediate literals:

        # 1) strings.  identical strings are *not* merged, since
        #      modifying strings is a reasonable choice.
        # 2) symbols.  this emits a string followed by a symbol tuple.
        #      these are collected so each is unique.  any runtime
        #      symbol table should be populated with these first.
        # 3) constructed.  trees of literals made of constructors
        #      (e.g. lists formed with QUOTE), and vectors.  each tree
        #      is rendered into a single C array where the first value
        #      in the array points to the beginning of the top-level
        #      object.

        name = None
        dtm = self.context.datatypes
        l = []

        def dotag (t):
            # XXX this is horrible.  I really want to be able to specify cons/nil symbolically
            #   when possible, in order to get lists handled somewhat automatically (especially
            #   when printing).  But the hairy UOXXX/UIXXX macros can't handle symbolic tags, yet.  FIX ME.
            if is_a (t, int):
                return t
            else:
                # these values will break as soon as the TC_XXX numbers get changed.
                return {'TC_PAIR':-3, 'TC_BOOL':-4, 'TC_SYMBOL':-2}[t]
        
        # now we walk the bitch, appending to a list of pxll_ints.
        def walk (exp):
            if exp.is_a ('primapp'):
                if exp.name.startswith ('%dtcon/'):
                    # constructor
                    ignore, dtname, alt = exp.name.split ('/')
                    dt = dtm[dtname]
                    tag = dt.tags[alt]
                    if dtname == 'symbol':
                        return 'UPTR(%d,1)' % (exp.index)
                    elif dt.uimm.has_key (alt):
                        # a single immediate argument, use it directly.
                        assert (len(exp.args) == 1)
                        return walk (exp.args[0])
                    elif len(exp.args):
                        # constructor with args, a tuple
                        # first - emit all the args
                        args = [ walk (x) for x in exp.args ]
                        # emit the header
                        addr = len (l)
                        l.append ('UOHEAD(%d,%d)' % (len(exp.args), dotag (tag)))
                        l.extend (args)
                        exp.index = -1
                        return 'UPTR(%d,%d)' % (i, addr)
                    elif tag in ('TC_NIL', 'PXLL_TRUE', 'PXLL_FALSE'):
                        # XXX ugh, special case again, fixme
                        return '(pxll_int)%s' % (tag,)
                    else:
                        # constructor with no args, an immediate
                        return 'UITAG(%d)' % (dotag (tag,))
                elif exp.name.startswith ('%vector-literal/'):
                    args = [ walk (x) for x in exp.args ]
                    addr = len (l)
                    l.append ('(%d<<8)|TC_VECTOR' % (len(exp.args),))
                    l.extend (args)
                    return 'UPTR(%d,%d)' % (i, addr)
                else:
                    raise ValueError ("unsupported primapp in constructed literal: %r" % (exp,))
            elif exp.is_a ('literal'):
                # XXX this duplicates smarts in cps.py that need to be moved into this file.
                if exp.ltype == 'int':
                    return (exp.value << 1) | 1
                elif exp.ltype == 'char':
                    if exp.value == 'eof':
                        return 257<<8|0x02
                    else:
                        return ord(exp.value)<<8|0x02
                elif exp.ltype == 'undefined':
                    return 0x0e
                elif exp.ltype == 'string':
                    return 'UPTR0(%d)' % (exp.index)
                else:
                    raise ValueError ("unsupported type in constructed literal: %r" % (exp,))
            elif exp.is_a ('constructed'):
                return 'UPTR(%d)' % (exp.index)
            else:
                raise ValueError ("unsupported type in constructed literal: %r" % (exp,))

        # synthesize an extra constant, a vector of all the symbols.

        # go through the list of top-level constructed literals, and emit them.
        lengths = []
        for i in range (len (self.context.constructed)):
            lit = self.context.constructed[i]
            if lit.is_a ('literal') and lit.ltype == 'string':
                # strings are a special case here because they have a non-uniform structure: the existence of
                #   the uint32_t <length> field means it's hard for us to put a UPTR in the front.
                s = lit.value
                slen = len(s)
                self.write ('pxll_string constructed_%d = { STRING_HEADER(%d), %d, "%s" };' % (i, slen, slen, self.c_string (s)))
            elif lit.is_a ('primapp') and lit.name == '%dtcon/symbol/t':
                # there's a temptation to skip the extra pointer at the front, but that would require additional smarts
                #   in insn_constructed (as already exist for strings).
                # NOTE: this reference to the string object only works because it comes before the symbol in self.context.constructed.
                self.write ('// symbol %r' % (lit.args[0],))
                self.write ('pxll_int constructed_%d[] = {UPTR(%d,1), SYMBOL_HEADER, UPTR0(%d)};' % (i, i, lit.args[0].index))
            else:
                # normal constructors
                l = [None]
                val = walk (lit)
                l[0] = val
                self.write ('pxll_int constructed_%d[] = {%s};' % (i, ', '.join ([str(x) for x in l])))
                lengths.append (len (l))

        #pxll_int constructed_8[] = {UPTR(8,1), (4<<8)|TC_VECTOR, UPTR(1,1), UPTR(3,1), UPTR(5,1), UPTR(7,1)};
        syms = []
        for sym, (index0, index1) in self.context.symbols.iteritems():
            syms.append ('UPTR(%d,1)' % (index1,))
        self.write ('pxll_int pxll_internal_symbols[] = {(%d<<8)|TC_VECTOR, %s};' % (len(syms), ', '.join (syms)))

    def emit_gc_copy_regs (self, nregs):
        # emit functions to copy registers in and out of the heap as gc roots
        self.write ('')
        self.write ('void gc_regs_in (int n) {')
        self.write ('  switch (n) {')
        for i in reversed (range (nregs+1)):
            self.write ('  case %d: heap1[%d] = r%d;' % (i+1, i+3, i))
        self.write ('}}')
        self.write ('void gc_regs_out (int n) {')
        self.write ('  switch (n) {')
        for i in reversed (range (nregs+1)):
            self.write ('  case %d: r%d = heap0[%d];' % (i+1, i, i+3))
        self.write ('}}')

    def emit_profile_counters (self):
        # for now, all we do is count function calls
        self.write ('// PROFILE COUNTERS')
        for fun in self.context.functions:
            label = self.function_label (fun)
            self.write ('pxll_int prof_count_%s = 0;' % label)

    def emit_dump_profile (self):
        for fun in self.context.functions:
            label = self.function_label (fun)
            self.write ('fprintf (stderr, "%%20ld %s\\n", prof_count_%s);' % (label, label))

    def emit (self, insns):
        for insn in insns:
            name = 'insn_%s' % (insn.name,)
            if self.annotate:
                self.write ('// %s' % (insn.print_info(),))
            fun = getattr (self, name, None)
            fun (insn)
            self.max_free = max (len(insn.free_regs), self.max_free)

    def done (self):
        #print 'max_free =', self.max_free
        # close out the vm() function...
        self.indent = 0
        self.write (' Lreturn:')
        if self.profile:
            self.emit_dump_profile()
        self.write (" return (pxll_int) result;\n}")
        if len(self.context.records2):
            # write out the record field lookup function
            self.out.write (
                "static int lookup_field (int tag, int label)\n"
                # "{ fprintf (stderr, \"[%d %d]\", tag, label);"
                "{ switch (tag) {\n"
                )
            for rec, tag in self.context.records2.iteritems():
                self.out.write ("  case %d:\n" % tag)
                self.out.write ("  switch (label) {\n")
                for i in range (len (rec)):
                    label = rec[i]
                    self.out.write ("    case %d: return %d; break;\n" % (self.context.labels2[label], i))
                self.out.write ("  } break;\n")
            self.out.write ("}}\n")
            

    label_counter = 0

    def new_label (self):
        "generate a fresh unused label"
        self.label_counter += 1
        return 'L%d' % (self.label_counter,)

    safe_name_map = {'!':'_bang','*':'_splat','?':'_question','+':'_plus','-':'_'}

    def frob_name (self, name):
        l = []
        for ch in name.lower():
            if self.safe_name_map.has_key (ch):
                r = self.safe_name_map[ch]
            elif ch in 'abcdefghijklmnopqrstuvwxyz_0123456789':
                r = ch
            else:
                r = '_%02x' % (ord (ch),)
            l.append (r)
        r = ''.join (l)
        if r == '_':
            # special case
            r = 'minus'
        return r

    def function_label (self, fun):
        "generate/memoize a unique label for a known function"
        if hasattr (fun, 'label'):
            return fun.label
        else:
            if fun.name:
                #fun.label = 'FUN_%d%s' % (fun.serial, self.frob_name (fun.name))
                fun.label = 'FUN_%s' % (self.frob_name (fun.name),)
            else:
                fun.label = 'FUN_%d_lambda' % (fun.serial,)
            return fun.label

    def write (self, line):
        "write out a 'line' of indented code, followed by a newline"
        self.out.write ('  ' * self.indent)
        self.out.write (line)
        self.out.write ('\n')

    # set in pxll.h
    head_room = 1024

    def alloc (self, line, insn, size):
        self.current_fun.known_allocs += size
        if self.current_fun.known_allocs >= self.head_room:
            # may have run out of head room, emit a heap check here.
            # 'may' since we're unfairly counting all sides of each branch...
            print 'mid-function heap check: free=%r' % (insn.free_regs)
            # XXX relies on the in-order allocation of registers
            self.check_free_regs (insn.free_regs)
            self.write ('check_heap (%d);' % (len (insn.free_regs)))
            self.current_fun.known_allocs = 0
        self.write (line)

    def insn_lit (self, insn):
        # for now
        lit = insn.params
        if insn.target is 'dead':
            # why bother with dead literals?
            comment = '//'
        else:
            comment = ''
        self.write ('%sr%r = (object *) %d;' % (comment, insn.target, lit))

    def insn_constructed (self, insn):
        # a reference to a constructed literal - refer to its variable directly.
        index = insn.params
        val = self.context.constructed[index]
        if val.is_a ('literal') and val.ltype == 'string':
            self.write ('r%d = (object*) &constructed_%d;' % (insn.target, index))
        else:
            self.write ('r%d = (object*) constructed_%d[0];' % (insn.target, index))

    def insn_return (self, insn):
        val_reg = insn.regs[0]
        self.write ('PXLL_RETURN(%d);' % (val_reg,))

    def insn_jump (self, insn):
        result, target = insn.regs
        # assign a target register for the other side of a jump (used by 'if' and 'vcase')
        if (target != 'dead') and result != target:
            self.write ('r%d = r%d;' % (target, result))

    # wrap_in, wrap_out: provide automatic type conversions when plausible
    def wrap_in (self, types, args):
        result = []
        for i in range (len (types)):
            t = types[i]
            if is_a (t, itypes.t_int):
                result.append ('unbox(%s)' % (args[i],))
            elif is_a (t, itypes.t_string):
                result.append ('((pxll_string*)(%s))->data' % (args[i],))
            elif is_a (t, itypes.t_predicate):
                if itypes.is_pred (t, 'raw'):
                    # 'raw' types...
                    if is_a (t.args[0], itypes.t_string):
                        result.append ('((pxll_string*)(%s))' % (args[i],))
                    else:
                        raise ValueError ("unknown 'raw' type: %r" % (t,))
                elif itypes.is_pred (t, 'arrow', 'vector', 'symbol', 'continuation'):
                    result.append (args[i])
                else:
                    raise ValueError ("unexpected predicate in cexp type sig: %r" % (t,))
            elif is_a (t, itypes.t_var):
                # tvars in cexp types
                result.append (args[i])
            elif is_a (t, itypes.t_base):
                # some other base type, untouched
                result.append (args[i])
            else:
                raise ValueError ("unknown element in cexp type sig: %r" % (t,))
        return tuple (result)

    def wrap_out (self, type, exp):
        if is_a (type, itypes.t_int):
            return 'box(%s)' % (exp,)
        elif itypes.is_pred (type, 'bool'):
            # hmm... this is more like a cast, and should probably be
            # expressed as such.
            return 'PXLL_TEST(%s)' % (exp,)
        else:
            return exp

    def guess_record_type (self, row):
        # XXX memoize
        orow = row
        row = set(row)
        row.discard ('...')
        # can we disambiguate this row type?
        candidates = []
        for sig, tag in self.context.records2.iteritems():
            if set(sig).issuperset (row):
                candidates.append (sig)
        #print self.context.records2
        if len(candidates) == 1:
            #print 'unambiguous', orow, candidates[0]
            # cast to list for python < 2.6 for 'index' method
            return list(candidates[0])
        else:
            #print 'ambiguous', orow, candidates
            return None

    def cexp_subst (self, template, values):
        r = []
        i = 0
        j = 0
        lt = len (template)
        while i < lt:
            ch = template[i]
            if ch == '%':
                r.append (template[j:i])
                i += 1
                ch = template[i]
                if ch == '%':
                    r.append ('%')
                elif ch in '0123456789':
                    r.append (values[int (ch)])
                else:
                    # for more than 10 args, we can introduce a new syntax like %{10}
                    raise ValueError ("bad template: %s" % template)
                j = i + 1
            i += 1
        r.append (template[j:])
        return ''.join (r)

    def insn_primop (self, insn):
        # XXX consider making some of these insns in their own right?
        regs = insn.regs
        primop = insn.params
        name = primop[0]
        if name == '%cexp':
            ignore, form, (tvars, sig) = primop
            if itypes.is_pred (sig, 'arrow'):
                result_type = sig.args[0]
                arg_types = sig.args[1:]
            else:
                result_type, arg_types = sig, ()
            regs = tuple('r%d' % x for x in regs)
            regs = self.wrap_in (arg_types, regs)
            exp = self.wrap_out (result_type, self.cexp_subst (form, regs))
            if insn.target == 'dead':
                # probably a side-effect expression
                self.write ('%s;' % (exp,))
            else:
                self.write ('r%d = %s;' % (insn.target, exp))
        elif name == '%make-tuple':
            ignore, type, tag = primop
            if insn.target == 'dead':
                print 'warning: dead %make-tuple', insn
            else:
                regs = insn.regs
                nargs = len (regs)
                if nargs == 0 and is_a (tag, int):
                    # unit type - use an immediate
                    self.write ('r%d = (object*)UITAG(%d);' % (insn.target, tag))
                else:
                    # tuple type
                    if nargs == 0:
                        # we can't have empty tuples - it breaks the sentinel trick used
                        #   in the garbage collector to avoid address range checks.
                        # each tuple type (that might be empty) needs a corresponding immediate
                        #   type to represent the empty version of it.
                        if tag == 'TC_VECTOR':
                            self.write ('r%d = (object *) TC_EMPTY_VECTOR;' % insn.target)
                        elif is_a (tag, str):
                            self.write ('r%d = (object *) %s;' % (insn.target, tag.upper()))
                        else:
                            raise ValueError ("attempt to create unsupported empty tuple type")
                    else:
                        if is_a (tag, int):
                            tag = '(TC_USEROBJ+%d)' % (tag << 2,)
                        self.alloc ('t = alloc_no_clear (%s, %d);' % (tag, nargs), insn, nargs)
                        self.write (' '.join (['t[%d] = r%d;' % (i+1, regs[i]) for i in range (nargs) ]))
                        self.write ('r%d = t;' % (insn.target,))
        elif name.startswith ('%array-ref'):
            [base, index] = insn.regs
            if name[-1] != '%':
                self.write ('range_check (GET_TUPLE_LENGTH(*(object*)r%d), unbox(r%d));' % (base, index))
            self.write ('r%d = ((pxll_vector*)r%d)->val[unbox(r%d)];' % (insn.target, base, index))
        elif name.startswith ('%array-set'):
            [base, index, val] = insn.regs
            if name[-1] != '%':
                self.write ('range_check (GET_TUPLE_LENGTH(*(object*)r%d), unbox(r%d));' % (base, index))
            self.write ('((pxll_vector*)r%d)->val[unbox(r%d)] = r%d;' % (base, index, val))
        elif name == '%record-get':
            [record] = insn.regs
            ignore, label, sig = primop
            sig = self.guess_record_type (sig)
            label_code = self.context.labels2[label]
            if not sig:
                # runtime lookup
                self.write (
                    'r%d = ((pxll_vector*)r%d)->val[lookup_field((GET_TYPECODE(*r%d)-TC_USEROBJ)>>2,%d)];' % (
                        insn.target, record, record, label_code
                        )
                    )
            else:
                # compile-time lookup
                self.write ('r%d = ((pxll_vector*)r%d)->val[%d];' % (insn.target, record, sig.index (label)))
        elif name == '%record-set':
            [record, val] = insn.regs
            ignore, label, sig = primop
            sig = self.guess_record_type (sig)
            label_code = self.context.labels2[label]
            if not sig:
                # runtime lookup
                self.write (
                    '((pxll_vector*)r%d)->val[lookup_field((GET_TYPECODE(*r%d)-TC_USEROBJ)>>2,%d)] = r%d;' % (
                        record, record, label_code, val
                        )
                    )
            else:
                # compile-time lookup
                self.write ('((pxll_vector*)r%d)->val[%d] = r%d;' % (record, sig.index (label), val))
            # XXX set! vs extend... an 'extension' of a pre-existing field becomes a set!, return the record.
            if (insn.target != 'dead') and record != insn.target:
                self.write ('r%d = r%d;' % (insn.target, record))
        elif name == '%extend-tuple':
            # extend a pre-existing tuple by merging it with one or more new field=value pairs.
            src = insn.regs[0]
            data = insn.regs[1:]
            ignore, new_fields, old_fields, new_tag = insn.params
            new_fields = list (new_fields)
            old_fields = list (old_fields)
            new_len = len (new_fields) + len (old_fields)
            new_tag = '(TC_USEROBJ+%d)' % (new_tag * 4)
            self.alloc ('t = alloc_no_clear (%s, %d);' % (new_tag, new_len), insn, new_len)
            j = 0
            for i in range (new_len):
                if not old_fields or new_fields and new_fields[0] < old_fields[0]:
                    # storing a new field value
                    self.write ('t[%d] = r%d;' % (i+1, data[0]))
                    new_fields.pop (0)
                    data.pop(0)
                else:
                    # copying an old field value
                    self.write ('t[%d] = r%d[%d];' % (i+1, src, j+1))
                    j += 1
                    old_fields.pop (0)
            self.write ('r%d = t;' % (insn.target,))
        elif name == '%make-vector':
            [vlen, vval] = insn.regs
            self.write ('if (unbox(r%d) == 0) { r%d = (object *) TC_EMPTY_VECTOR; } else {' % (vlen, insn.target))
            # XXX currently, this is the only alloc size not known at runtime.  need to check the heap
            #     specifically for this amount of room at runtime.
            self.write ('  t = alloc_no_clear (TC_VECTOR, unbox(r%d));' % (vlen,))
            self.write ('  for (i=0; i < unbox(r%d); i++) { t[i+1] = r%d; }' % (vlen, vval))
            self.write ('  r%d = t;' % (insn.target,))
            self.write ('}')
        elif name == '%make-vec16':
            [vlen] = insn.regs
            self.write ('if (unbox(r%d) == 0) { r%d = (object *) TC_EMPTY_VECTOR; } else {' % (vlen, insn.target))
            self.write ("  t=alloc_no_clear (TC_VEC16, VEC16_TUPLE_LENGTH(unbox(r%d)));" % vlen)
            self.write ("  ((pxll_vec16*)t)->len = unbox(r%d);" % vlen)
            self.write ('  r%d = t;' % (insn.target,))
            self.write ('}')
        elif name == '%vget':
            self.write ('r%d = UOBJ_GET(r%d,%s);' % (insn.target, insn.regs[0], primop[1]))
        elif name == '%vec16-ref':
            [vreg, ireg] = insn.regs
            self.write ('range_check (((pxll_vec16*)r%d)->len, unbox(r%d));' % (vreg, ireg))
            self.write ('r%d = box (((pxll_vec16*)r%d)->data[unbox(r%d)]);' % (insn.target, vreg, ireg))
        elif name == '%vec16-set':
            [vreg, ireg, areg] = insn.regs
            self.write ('range_check (((pxll_vec16*)r%d)->len, unbox(r%d));' % (vreg, ireg))
            self.write ('((pxll_vec16*)r%d)->data[unbox(r%d)] = unbox(r%d);' % (vreg, ireg, areg))
        else:
            raise ValueError ("unknown primop: %s" % name)

    def insn_test (self, insn):
        cexp, then_code, else_code = insn.params
        if cexp:
            # if we know we're testing a cexp, just inline it here
            code, (tvars, sig) = cexp
            regs = tuple ('r%d' % x for x in insn.regs)
            regs = self.wrap_in (sig.args[1:], regs)
            exp = self.wrap_out (sig.args[0], self.cexp_subst (code, regs))
            self.write ('if PXLL_IS_TRUE(%s) {' % exp)
        else:
            # this is a scheme-like definition of test/#t/#f
            self.write ('if PXLL_IS_TRUE(r%d) {' % (insn.regs[0],))
        self.indent += 1
        self.emit (then_code)
        self.indent -= 1
        self.write ('} else {')
        self.indent += 1
        self.emit (else_code)
        self.indent -= 1
        self.write ('}')

    def insn_pvcase (self, insn):
        # this version uses get_pvariant_tag() to avoid segregating into
        #   immediate/pointer cases... note that each unique label is given
        #   a unique tag, regardless of its arity (which can vary!)
        [test_reg] = insn.regs
        alt_formals, alts = insn.params
        units = []
        tuples = []
        self.write ('switch (get_case_noint (r%d)) {' % test_reg)
        for i in range (len (alts)):
            if i < len(alt_formals):
                label, orig_arity, formals = alt_formals[i]
                try:
                    tag = self.context.variant_labels[label]
                except KeyError:
                    raise ValueError ('variant constructor ":%s" never called; no runtime tag available.' % label)
                if orig_arity == 0:
                    tag = 'TC_USERIMM+%d' % (tag<<8)
                else:
                    tag = 'TC_USEROBJ+%d' % (tag * 4)
                case = 'case (%s): {' % (tag,)
            else:
                case = 'default: {'
            self.indent += 1
            self.write (case)
            self.indent += 1
            self.emit (alts[i])
            self.indent -= 1
            self.write ('} break;')
            self.indent -= 1
        self.write ('}')

    def which_typecode_fun (self, dt):
        # XXX cache this!
        # getting the typecode of an unknown object involves three steps:
        # 1) is it an integer? (check the lowest bit, return TC_INT == 0)
        # 2) is it an immediate (check the next lowest bit, return ob)
        # 3) it's a pointer (indirect through it, return (*ob)&0xff)
        #
        # if a datatype is all-immediate, or all-tuple, then using a
        # specific form of get_typecode(), can often lead to
        # single-instruction typecode fetching. (also, avoiding a branch)
        if len (dt.uimm):
            # if we're using the uimm hack, we have to check for everything, including TC_INT.
            return 'get_typecode'
        alts = dt.alts
        arity = len (alts[0][1])
        # dunno what I'm thinking here... how does differing arity trigger a requirement
        #  to check for int?
        for i in range (1, len (alts)):
            tag, prod = alts[i]
            if len(prod) != arity:
                return 'get_case_noint'
        if arity == 0:
            # all are of the same zero arity, and no function is
            # needed, just compare the value directly.
            return '(pxll_int)'
        else:
            return 'get_case_tup'

    def insn_nvcase (self, insn):
        [test_reg] = insn.regs
        dtype, tags, alts, ealt = insn.params
        dt = self.context.datatypes[dtype]
        use_else = len(dt.alts) != len(alts)
        if len(alts) == 1 and len (dt.alts) == 1:
            # nothing to switch on, just emit the code
            self.emit (alts[0])
        else:
            get_typecode = self.which_typecode_fun (dt)
            self.write ('switch (%s (r%d)) {' % (get_typecode, test_reg))
            # XXX re-order tags to put immediate tests first!
            for i in range (len (tags)):
                label = tags[i]
                tag = dt.tags[label]
                arity = dt.arity (label)
                uimm = False
                if is_a (tag, str):
                    # e.g., PXLL_FALSE
                    tag = '((pxll_int)%s)' % (tag,)
                elif arity == 0:
                    # immediate/unit-constructor
                    tag = 'TC_USERIMM+(%d<<8)' % tag
                elif arity == 1 and dt.uimm.has_key (label):
                    typename = dt.uimm[label].name.upper()
                    tag = 'TC_%s' % (typename)
                    uimm = True
                else:
                    # tuple constructor
                    tag = 'TC_USEROBJ+(%d<<2)' % tag
                self.indent += 1
                if uimm:
                    comment = '%s/uimm' % (label,)
                else:
                    comment = label
                if i == len(tags)-1 and not use_else:
                    self.write ('default: { // %s' % (comment,))
                else:
                    self.write ('case (%s): { // %s' % (tag, comment))
                self.indent += 1
                self.emit (alts[i])
                self.indent -= 1
                self.write ('} break;')
                self.indent -= 1
            if use_else:
                self.indent += 1
                self.write ('default: { // <else>')
                self.indent += 1
                self.emit (ealt)
                self.indent -= 1
                self.write ('}')
                self.indent -= 1
            self.write ('}')

    def insn_fatbar (self, insn):
        label, e1, e2 = insn.params
        self.emit (e1)
        self.write ('goto %s_over;' % (label,))
        self.write ('%s:' % (label,))
        self.emit (e2)
        # Note: the extra semicolon here is necessary because C99 requires a 'statement'
        #  to follow a label.  Sometimes there's no code after the label, so this avoids
        #  that problem.  [might be possible to look at the insn's continuation instead]
        self.write ('%s_over: ;' % (label,))

    def insn_fail (self, insn):
        label, npop = insn.params
        if npop:
            self.write ('lenv = ((object %s)lenv)%s;' % ('*' * npop, '[1]' * npop))
        self.write ('goto %s;' % (label,))

    def check_free_regs (self, free_regs):
        "describe the free_regs to new_env() for gc"
        # XXX do we need a bitfield, or an integer?
        # from everything I've seen, they're always in order ,
        # which makes sense because that's how the allocator works!
        # so let's just assume that and check for it here.
        n = len (free_regs)
        while n:
            n = n - 1
            if n not in free_regs:
                raise RegisterError ("Bad assumption about free registers!")

    def insn_new_env (self, insn):
        # val tuple is: <tag next val0 val1 val2 ...>
        size = insn.params
        # XXX our heap check is now done in a way that does not require looking at free regs.
        #self.check_free_regs (insn.regs)
        #self.write ('check_heap (%d); r%d = allocate (TC_TUPLE, %d);' % (len(insn.regs), insn.target, size + 1))
        self.alloc ('r%d = allocate (TC_TUPLE, %d);' % (insn.target, size + 1), insn, size + 1)
        if not self.toplevel_env:
            self.toplevel_env = True
            self.write ('top = r%d;' % (insn.target,))

    def insn_new_tuple (self, insn):
        tag, size = insn.params
        self.alloc ('r%d = allocate (%s, %d);' % (insn.target, tag, size), insn, size)

    def insn_store_tuple (self, insn):
        [arg_reg, tuple_reg] = insn.regs
        i, offset, n = insn.params
        self.write ('r%d[%d] = r%d;' % (tuple_reg, i+1+offset, arg_reg))
        # XXX this is a bit of a hack. Because of the confusing implementation of compile_rands,
        #     we have no way of passing the tuple to its continuation (when it's needed)
        if insn.target != 'dead' and insn.target != tuple_reg:
            self.write ('r%d = r%d;' % (insn.target, tuple_reg))

            
    # based on string.printable, but without the surprises at the end.
    isprint = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ' # \t\n\r\x0b\x0c'
    safe_for_c_string = {
        '\r' : '\\r',
        '\n' : '\\n',
        '\t' : '\\t',
        '\\' : '\\\\',
        '"'  : '\\"',
        }

    def c_string (self, s):
        r = []
        for ch in s:
            if self.safe_for_c_string.has_key (ch):
                r.append (self.safe_for_c_string[ch])
            elif ch in self.isprint:
                r.append (ch)
            else:
                # originally, used hex escapes here.  but the tricksy modern C spec allows for more than
                #  two digits (for non-ascii charsets).  that means \x0155 is one char, not three.  octal.
                r.append ('\\%03o' % (ord (ch)))
        return ''.join (r)

    def insn_build_env (self, insn):
        regs = insn.regs
        size = len (regs)
        self.alloc ('t = allocate (TC_TUPLE, %d);' % (size + 1,), insn, size + 1)
        for i in range (len (regs)):
            self.write ('t[%d] = r%d;' % (i+2, regs[i]))
        self.write ('r%d = t;' % (insn.target,))

    def insn_push_env (self, insn):
        [args_reg] = insn.regs
        if self.trace:
            self.write ('stack_depth_indent(k); fprintf (stderr, ">> [%d] push\\n", __LINE__);')
        self.write ('r%d[1] = lenv; lenv = r%d;' % (args_reg, args_reg))

    def insn_pop_env (self, insn):
        [src] = insn.regs
        if self.trace:
            self.write ('stack_depth_indent(k); fprintf (stderr, "<< [%d] pop\\n", __LINE__);')
        self.write ('lenv = lenv[1];')
        if insn.target != 'dead' and src != insn.target:
            self.write ('r%d = r%d;' % (insn.target, src))

    def insn_varref (self, insn):
        addr, is_top, var = insn.params
        name = var.name
        depth, index = addr
        if insn.target != 'dead':
            #if self.trace:
            #    self.write ('stack_depth_indent (k); fprintf (stderr, "(%d, %d)\\n");' % (depth, index))
            if is_top:
                self.write ('r%d = top[%d];' % (insn.target, index+2))
            else:
                # gcc generates identical code for these, and the latter is cleaner.
                #self.write ('r%d = ((object *%s) lenv) %s[%d];' % (insn.target, '*' * depth, '[1]' * depth, index+2)) 
               self.write ('r%d = varref (%d,%d);' % (insn.target, depth, index))

    def insn_varset (self, insn):
        val_reg = insn.regs[0]
        addr, is_top, var = insn.params
        #if insn.target != 'dead':
        #    print '[set! result used]',
        depth, index = addr
        if is_top:
            self.write ('top[%d] = r%d;' % (index+2, val_reg))
        else:
            # gcc generates identical code for these, and the latter is cleaner.
            #self.write ('((object *%s)lenv)%s[%d] = r%d;' % ('*' * depth, '[1]' * depth, index+2, val_reg))
            self.write ('varset (%d, %d, r%d);' % (depth, index, val_reg))

    def insn_close (self, insn):
        fun, body, free = insn.params
        proc_label = self.function_label (fun)
        jump_label = self.new_label()
        # emit a jump over function definition
        self.write ('// def %r' % (fun.name,))
        self.write ('goto %s;' % (jump_label,))
        # the function definition itself
        self.write ('%s:' % (proc_label,))
        self.indent += 1
        if self.trace:
            self.write ('stack_depth_indent(k); fprintf (stderr, ">> [%%d] %s\\n", __LINE__);' % ((fun.name or 'lambda'),))
        if self.profile:
            self.write ('prof_count_%s += 1;' % (proc_label,))
        if insn.allocates:
            self.write ('check_heap (0);')
        calling_fun = self.current_fun
        self.current_fun = function (fun.name)
        self.emit (body)
        self.indent -= 1
        self.write ('%s:' % (jump_label,))
        #print 'function %s known_allocs=%d' % (fun.name, self.current_fun.known_allocs)
        self.current_fun = calling_fun
        # create a closure object
        self.alloc ('r%d = allocate (TC_CLOSURE, 2);' % (insn.target,), insn, 2)
        # unfortunately, this is a gcc extension - '&&' takes the
        #   address of a label.  is there some portable way to this?
        self.write ('r%d[1] = &&%s; r%d[2] = lenv;' % (insn.target, proc_label, insn.target))

    def insn_invoke_tail (self, insn):
        closure_reg, args_reg = insn.regs
        fun = insn.params
        # call
        #   extend closure's environment with args, jump
        if fun:
            label = 'goto %s' % (self.function_label (fun),)
        else:
            label = 'goto *r%d[1]' % (closure_reg,)
        if args_reg is not None:
            self.write ('r%d[1] = r%d[2]; lenv = r%d; %s;' % (args_reg, closure_reg, args_reg, label))
        else:
            self.write ('lenv = r%d[2]; %s;' % (closure_reg, label))

    def insn_invoke (self, insn):
        closure_reg, args_reg = insn.regs
        free_regs, fun = insn.params
        return_label = self.new_label()
        nregs = len (free_regs)
        # sort these, might improve things
        free_regs = free_regs[:]
        free_regs.sort()
        # save
        self.alloc ('t = allocate (TC_SAVE, %d);' % (3 + nregs), insn, 3 + nregs)
        saves = []
        for i in range (nregs):
            saves.append ('t[%d] = r%d;' % (i+4, free_regs[i]))
        saves = ' '.join (saves)
        self.write ('t[1] = k; t[2] = lenv; t[3] = &&%s; %s k = t;' % (return_label, saves))
        # call
        #   extend closure's environment with args, jump
        if fun:
            label = 'goto %s' % (self.function_label (fun,),)
        else:
            label = 'goto *r%d[1]' % (closure_reg,)
        if args_reg is not None:
            self.write ('r%d[1] = r%d[2]; lenv = r%d; %s;' % (args_reg, closure_reg, args_reg, label))
        else:
            self.write ('lenv = r%d[2]; %s;' % (closure_reg, label))
        # label
        self.write ('%s:' % (return_label,))
        if self.trace:
            if fun:
                fun_name = fun.name
            else:
                fun_name = 'lambda'
            self.write ('stack_depth_indent(k); fprintf (stderr, "<< [%%d] %s\\n", __LINE__);' % (fun_name,))
        # restore
        restores = []
        for i in range (nregs):
            restores.append ('r%d = k[%d];' % (free_regs[i], i+4))
        restores = ' '.join (restores)
        self.write ('%s; lenv = k[2]; k = k[1];' % restores)
        if insn.target is not 'dead':
            self.write ('r%d = result;' % (insn.target,))

    def insn_tr_call (self, insn):
        regs = insn.regs
        depth, fun = insn.params
        nargs = len (regs)
        # we want to jump up the stack back to the start of this function.
        # to do that, we need to pop a certain number of levels off of <lenv>.
        # <depth> pops would put us at the function itself.
        # <depth-1> points us at the functions args, if any.
        npop = depth - 1
        if nargs == 0:
            # a zero-arg trcall needs an extra level of pop
            npop += 1
        if npop:
            self.write ('lenv = ((object %s)lenv)%s;' % ('*' * npop, '[1]' * npop))
        for i in range (nargs):
            self.write ('lenv[%d] = r%d;' % (2+i, regs[i]))
        self.write ('goto %s;' % (self.function_label (fun),))

    def insn_move (self, insn):
        reg_var, reg_src = insn.regs
        if reg_src is not None:
            # from varset
            self.write ('r%d = r%d;' % (reg_var, reg_src))
        elif insn.target != 'dead':
            # XXX need remove_moves() if we're keeping this code...
            self.write ('r%d = r%d;' % (insn.target, reg_var))
            pass
        else:
            pass