// optimise.cc -- aggressive optimiser				-*- C++ -*-
// 
// Author: Ian.Piumarta@INRIA.Fr
//
// Last edited: 2/28/01 by marcus@ira.uka.de

// NOTES
//   Trying to understand this file could provoke serious migraine.  The
//   functions genSpecialArithmetic() and genRelation() are particularly
//   entertaining.  If you're determined to grok this stuff then go to the
//   end of the file and begin with genEquivalent() and genSpecialBitwise()
//   instead, which are probably the easiest of the heavy optimisers to
//   understand.  (I strongly advise against industrial doses of paracetamol
//   and/or [especially] aspirin: try ibuprofen instead -- it's much more
//   effective, and lots kinder to your system.)
// 
// BUGS
//   This file should be lots more complicated, passing N args in registers
//   reg[0] through reg[N] and always receiving the result in reg[0].  But
//   this is sadly impossible (well, okay -- just plain difficult) until Dan
//   gets his act together and implements right-to-left pushing of args
//   (followed by receiver) in the image, along with primitives that are
//   independent of the direction of stack growth.


#include "Object.h"

#include "archdep.h"

#include "debug.h"
#include "specials.h"
#include "compile.h"
#include "optimise.h"
#include "generate.h"
#include "machine.h"

#include ARCHDEP(defs.h)
#include ARCHDEP(emit.h)

#include "Frame.h"

// no optimisation: subroutine-threading to machine.cc implementation
#undef	OPT_MACHINE

// simple optimisation: unoptimised inline code, full stack, reified Booleans, ...
#undef	OPT_INLINE

// full optimisation: inline code, stack/tag elision, non-reified Booleans, ...
#define	OPT_FULL

// generate linked special sends for SI traps (rather than calls to `g_Operator()')
#define	LINKED_SI_TRAPS

// generate inline code for closure creation
#define	INLINE_LAMBDA

// generate inline code for Point>>{x,y}
#define	INLINE_XY


#include "dcg.h"


void opt_initialise(void)
{
  dcg_initialise();
}


void opt_dumpStack(void)
{
  for (int idx= 0; idx <= stackp; ++idx)
    printf("  %s", stack[idx].printString());
  printf("\n");
}


/// 
/// optimisation control
/// 


// prepare the optimiser for a new method

void genReset()
{
  stackp= -1;
  DeferredSend::reset();
}


// prepare the optimiser for a new method

void genFinalise(int pass)
{
  DeferredSend::generateAll(pass);
}


// we're about to exit from a basic block: last insn in block has been
// generated, but we haven't yet recorded the address of the next
// basic block.

void genBlockExit(void)
{
  PRINTF(("blockExit\n"));
  flushStack();
}


// we're about to enter a basic block: address of block has been
// recorded, but we haven't yet generated its first insn.

void genBlockEntry(void)
{
  fatal("this should not happen");
}


// recombine stack at control flow join.  Note: if we follow an
// unconditional control transfer then it's okay to (non-destructively!)
// grow or truncate the stack in the linear path (we assume that the
// depth from the non-linear flow into the recombination point is
// correct); otherwise the depths must match, since the two paths into
// the next instruction must never create an ambiguous stack.

void genRecombine(int depth, bool followsControl)
{
  assert(stackIsStable());
  PRINTF(("RECOMBINE %d\n", depth));
  if (stackDepth() != depth)
    {
      if (!followsControl)
	fatal("inconsistent stack (%d->%d) at recombination point",
	      depth, stackDepth());
      if (stackDepth() > depth)
	pop(stackDepth() - depth);
      else if (stackDepth() < depth)
	{
	  flushStack();
	  while (stackDepth() < depth)
	    push(Stack());
	}
    }
}


/// 
/// code generation functions
/// 


#define preDCGflushStack()	flushStack()


#ifdef OPT_FULL
#undef OPT_INLINE
#endif


void genPop(void)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  emit_call(g_Pop);
  pop(1);
# elif defined(OPT_INLINE)
  emit_drop(1);
  pop(1);
# elif defined(OPT_FULL)
  if (tos().isStack())
    {
      assert(stackIsStable());
      emit_drop(1);
    }
  pop(1);
# else
#   error: broken options
# endif
}


void genDup(void)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  emit_call(g_Dup);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_S_r(0, tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  switch (stackType(0))
    {
    case Stack::Type:
      {
	int regNo= allocReg();
	emit_move_S_r(0, regNo);
	push(Register(regNo));
	break;
      }
    case Integer::Type:
      {
	int val= stackValue(0);
	push(Integer(val));
	break;
      }
    case Register::Type:
      {
	// must not alias the register
	int regNo= allocReg();
	if (stackDescriptor(0).isRegister())
	  {
	    emit_move_r_r(stackValue(0), regNo);
	    push(Register(regNo));
	  }
	else
	  {
	    assert(stackDescriptor(0).isStack());
	    emit_move_S_r(0, regNo);
	    push(Register(regNo));
	  }
	break;
      }
    case Condition::Type:
      {
	const Descriptor &cond= stackDescriptor(0);
	assert(cond.isTrue() || cond.isFalse());
	push(cond);
	break;
      }
    default:
      fatal("this cannot happen");
    }
# else
#   error: broken options
# endif
}


void genLdSelf(bool blockFlag, bool tagFlag)
{
# if defined(OPT_MACHINE)
  if (blockFlag) {
    emit_call(g_bLdSelf);
    push(Stack());
  } else {
    emit_call(g_LdSelf);
    push(Stack());
  }
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    emit_b_move_R_r(tmp[0]);
  } else {
    emit_move_R_r(tmp[0]);
  }
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  int regNo= allocReg();
  if (blockFlag) {
    emit_b_move_R_r(regNo);
  } else {
    emit_move_R_r(regNo);
  }
  push(Register(regNo));
  tos().beSelf();
  if (tagFlag)
    tos().beTagged();
# else
#   error: broken options
# endif
}


void genLdTrue(void)
{
# if defined(OPT_MACHINE)
  emit_call(g_LdTrue);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_1_r(tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  push(Condition::Tr);
# else
#   error: broken options
# endif
}


void genLdFalse(void)
{
# if defined(OPT_MACHINE)
  emit_call(g_LdFalse);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_0_r(tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  push(Condition::Fa);
# else
#   error: broken options
# endif
}


void genLdNil(void)
{
# if defined(OPT_MACHINE)
  emit_call(g_LdNil);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_N_r(tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  int regNo= allocReg();
  emit_move_N_r(regNo);
  push(Register(regNo));
# else
#   error: broken options
# endif
}


void genLdInt(int val)
{
# if defined(OPT_MACHINE)
  emit_call_l(g_LdInt, val);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_i_r((int)Object::integer(val), tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  push(Integer(val));
# else
#   error: broken options
# endif
}


void genLdThisContext(bool blockFlag)
{
  flushStack();
  emit_call(g_LdThisContext);
  push(Stack());
}


void genLdLit(int idx, bool blockFlag)
{
# if defined(OPT_MACHINE)
  if (blockFlag) {
    emit_call_s(g_bLdLit, idx);
  } else {
    emit_call_s(g_LdLit, idx);
  }
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_L_r(idx, tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  int regNo= allocReg();
  if (blockFlag) { 
    emit_b_move_L_r(idx, regNo);
  } else {
    emit_move_L_r(idx, regNo);
  }
  push(Register(regNo));
# else
#   error: broken options
# endif
}


void genLdInst(int idx, bool blockFlag, bool ctxFlag)
{
# if defined(OPT_MACHINE)
  if (blockFlag) {
    if (ctxFlag) {
      emit_call_s(g_bpLdInst, idx);
    } else {
      emit_call_s(g_bLdInst, idx);
    }
  } else {
    if (ctxFlag) {
      emit_call_s(g_pLdInst, idx);
    } else {
      emit_call_s(g_LdInst, idx);
    }
  }
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    if (ctxFlag) {
      emit_call_s(g_bpLdInst, idx);
    } else {
      emit_b_move_I_r(idx, tmp[0]);
      emit_push_r(tmp[0]);
    }
  } else {
    if (ctxFlag) {
      emit_call_s(g_pLdInst, idx);
    } else {
      emit_move_I_r(idx, tmp[0]);
      emit_push_r(tmp[0]);
    }
  }
  push(Stack());
# elif defined(OPT_FULL)
  if (blockFlag) {
    if (ctxFlag) {
      flushStack();
      emit_call_s(g_bpLdInst, idx);
      push(Stack());
    } else {
      int regNo= allocReg();
      emit_b_move_I_r(idx, regNo);
      push(Register(regNo));
    }
  } else {
    if (ctxFlag) {
      flushStack();
      emit_call_s(g_pLdInst, idx);
      push(Stack());
    } else {
      int regNo= allocReg();
      emit_move_I_r(idx, regNo);
      push(Register(regNo));
    }
  }
# else
#   error: broken options
# endif
}


#if defined(OPT_FULL)

static void optStInst(bool blockFlag, int idx, bool popFlag)
{
  bool needStoreCheck= true;
  switch (stackType(0))
    {
    case Stack::Type:
      emit_move_S_r(0, tmp[1]);			// value
      break;
    case Integer::Type:
      emit_move_i_r((int)Object::integer(stackValue(0)), tmp[1]);
      needStoreCheck= false;
      break;
    case Register::Type:
      assert(stackValue(0) != tmp[0]);
      assert(stackValue(0) != tmp[1]);
      emit_move_r_r(stackValue(0), tmp[1]);	// value
      break;
    case Condition::Type:
      switch (stackValue(0))
	{
	case Condition::TR:
	  emit_move_1_r(tmp[1]);		// value
	  break;
	case Condition::FA:
	  emit_move_0_r(tmp[1]);		// value
	  break;
	default:
	  fatal("this cannot happen");
	  break;
	}
      break;
    default:
      fatal("this cannot happen");
      break;
    }
  if (blockFlag) {
    emit_b_move_R_r(tmp[0]);			// dest
  } else {
    emit_move_R_r(tmp[0]);			// dest
  }
  emit_put_r_i_r(tmp[1], ((idx+1)*4), tmp[0]);	// store pointer
  if (popFlag && stackDescriptor(0).isStack())
    {
      emit_drop(1);
    }
  if (needStoreCheck)
    {
      emit_call(g_storeCheck);
    }
}

#endif

void genStInst(int idx, bool blockFlag, bool ctxFlag)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  if (blockFlag) {
    if (ctxFlag) {
      emit_call_s(g_bpStInst, idx);
    } else {
      emit_call_s(g_bStInst, idx);
    }
  } else {
    if (ctxFlag) {
      emit_call_s(g_pStInst, idx);
    } else {
      emit_call_s(g_StInst, idx);
    }
  }
  // no stack effect
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    if (ctxFlag) {
      emit_call_s(g_bpStInst, idx);
    } else {
      emit_call_s(g_bStInst, idx);
    }
  } else {
    if (ctxFlag) {
      emit_call_s(g_pStInst, idx);
    } else {
      insn *asm_org= asm_pc, *trap= asm_pc, *done= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_move_R_r(tmp[0]);		// dest
	  emit_move_S_r(0, tmp[1]);		// value
	  emit_put_r_i_r(tmp[1], ((idx+1)*4), tmp[0]);
	  emit_call(g_storeCheck);
	  trap= asm_pc;
	  done= asm_pc;
	}
      asm_pass= 0;
    }
  }
  // no stack effect
# elif defined(OPT_FULL)
  if (ctxFlag) {
    flushStack();
    if (blockFlag) {
      emit_call_s(g_bpStInst, idx);
    } else {
      emit_call_s(g_pStInst, idx);
    }
  } else {
    optStInst(blockFlag, idx, false);
  }
  // no stack effect
# else
#   error: broken options
# endif
}


// NOTE: we can't fold this into stInst like we do with the other
// popXyz operations because of potential pops into a context inst var
// which require a sane PC (i.e. pointing to the next valid vPC
// equivalent nPC in the method).  Ho hum.  We could probably fold the
// two into a single function though, with a flag saying whether or
// not to pop...

void genPopInst(int idx, bool blockFlag, bool ctxFlag)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  if (blockFlag) {
    if (ctxFlag) {
      emit_call_s(g_bpPopInst, idx);
    } else {
      emit_call_s(g_bPopInst, idx);
    }
  } else {
    if (ctxFlag) {
      emit_call_s(g_pPopInst, idx);
    } else {
      emit_call_s(g_PopInst, idx);
    }
  }
  pop(1);
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    if (ctxFlag) {
      emit_call_s(g_bpPopInst, idx);
    } else {
      emit_call_s(g_bPopInst, idx);
    }
  } else {
    if (ctxFlag) {
      emit_call_s(g_pPopInst, idx);
    } else {
      insn *asm_org= asm_pc, *trap= asm_pc, *done= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_move_R_r(tmp[0]);		// dest
	  emit_move_S_r(0, tmp[1]);		// value
	  emit_put_r_i_r(tmp[1], ((idx+1)*4), tmp[0]);
	  emit_drop(1);
	  emit_call(g_storeCheck);
	  trap= asm_pc;
	  done= asm_pc;
	}
      asm_pass= 0;
    }
  }
  pop(1);
# elif defined(OPT_FULL)
  if (ctxFlag) {
    flushStack();
    if (blockFlag) {
      emit_call_s(g_bpPopInst, idx);
    } else {
      emit_call_s(g_pPopInst, idx);
    }
  } else {
    optStInst(blockFlag, idx, true);
  }
  pop(1);
# else
#   error: broken options
# endif
}


void genLdTemp(int idx, bool blockFlag)
{
# if defined(OPT_MACHINE)
  if (blockFlag) {
    emit_call_s(g_bLdTemp, idx);
  } else {
    emit_call_s(g_LdTemp, idx);
  }
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    static const size_t px_frame_off= memberOffset(PseudoContext, stack);
    insn *asm_org= asm_pc, *trap= asm_pc, *done= asm_pc;
    for (asm_pass= 1; asm_pass < 3; ++asm_pass)
      {
	asm_pc= asm_org;
	emit_move_R_r(tmp[0]);					// home context
	emit_get_i_r_r(0, tmp[0], tmp[1]);			// base header
	emit_ccick_i_r(PseudoContextCCI, tmp[1]);
	emit_bne(trap);
	emit_get_i_r_r(px_frame_off, tmp[0], tmp[0]);		// frame
#      ifdef STACK_GROWS_DOWN
	emit_get_i_r_r((f_stack_off+(LargeFrame*4)-4-(idx*4)), tmp[0], tmp[0]);	// fetch temp
#      else
	emit_get_i_r_r((f_stack_off+(idx*4)), tmp[0], tmp[0]);	// fetch temp
#      endif
	emit_push_r(tmp[0]);
	emit_jmp(done);
	trap= asm_pc;
	emit_call_s(g_bLdTemp, idx);
	done= asm_pc;
      }
    asm_pass= 0;
  } else {
    emit_move_T_r(idx, tmp[0]);
    emit_push_r(tmp[0]);
  }
  push(Stack());
# elif defined(OPT_FULL)
  if (blockFlag)
    {
      static const size_t px_frame_off= memberOffset(PseudoContext, stack);
      static const size_t mx_stack_off= memberOffset(MethodContext, stack);
      int regNo= allocReg();
      insn *asm_org= asm_pc, *pseudo= asm_pc, *done= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_move_R_r(tmp[0]);				// home context
	  emit_get_i_r_r(0, tmp[0], tmp[1]);			// base header
	  emit_ccick_i_r(PseudoContextCCI, tmp[1]);
	  emit_beq(pseudo);
	  // home is MethodContext in heap
	  emit_get_i_r_r((mx_stack_off+(idx*4)), tmp[0], regNo);
	  emit_jmp(done);
	  pseudo=asm_pc;
	  // home is frame on stack
	  emit_get_i_r_r(px_frame_off, tmp[0], tmp[0]);		// frame
#	 ifdef STACK_GROWS_DOWN
	  emit_get_i_r_r((f_stack_off+(LargeFrame*4)-4-(idx*4)), tmp[0], regNo);	// fetch temp
#	 else
	  emit_get_i_r_r((f_stack_off+(idx*4)), tmp[0], regNo);	// fetch temp
#	 endif
	  done= asm_pc;
	}
      asm_pass= 0;
      push(Register(regNo));
    }
  else
    {
      int regNo= allocReg();
      emit_move_T_r(idx, regNo);
      push(Register(regNo));
    }
# else
#   error: broken options
# endif
}


void genStTemp(int idx, bool blockFlag)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  if (blockFlag) {
    emit_call_s(g_bStTemp, idx);
  } else {
    emit_call_s(g_StTemp, idx);
  }
  // no stack effect
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    static const size_t px_frame_off= memberOffset(PseudoContext, stack);
    insn *asm_org= asm_pc, *trap= asm_pc, *done= asm_pc;
    for (asm_pass= 1; asm_pass < 3; ++asm_pass)
      {
	asm_pc= asm_org;
	emit_move_R_r(tmp[0]);					// home context
	emit_get_i_r_r(0, tmp[0], tmp[1]);			// base header
	emit_ccick_i_r(PseudoContextCCI, tmp[1]);
	emit_bne(trap);
	emit_get_i_r_r(px_frame_off, tmp[0], tmp[0]);		// frame
	emit_move_S_r(0, tmp[1]);
#      ifdef STACK_GROWS_DOWN
	emit_put_r_i_r(tmp[1], (f_stack_off+(LargeFrame*4)-4-(idx*4)), tmp[0]);	// store temp
#      else
	emit_put_r_i_r(tmp[1], (f_stack_off+(idx*4)), tmp[0]);	// store temp
#      endif
	emit_jmp(done);
	trap= asm_pc;
	emit_call_s(g_bStTemp, idx);
	done= asm_pc;
      }
    asm_pass= 0;
  } else {
    emit_move_S_r(0, tmp[0]);
    emit_move_r_T(tmp[0], idx);
  }
  // no stack effect
# elif defined(OPT_FULL)
  switch (stackType(0))
    {
    case Stack::Type:
      emit_move_S_r(0, tmp[1]);				// value
      break;
    case Integer::Type:
      emit_move_i_r((int)Object::integer(stackValue(0)), tmp[1]);
      break;
    case Register::Type:
      assert(stackValue(0) != tmp[0]);
      assert(stackValue(0) != tmp[1]);
      emit_move_r_r(stackValue(0), tmp[1]);		// value
      break;
    case Condition::Type:
      switch (stackValue(0))
	{
	case Condition::TR:
	  emit_move_1_r(tmp[1]);			// value
	  break;
	case Condition::FA:
	  emit_move_0_r(tmp[1]);			// value
	  break;
	default:
	  fatal("this cannot happen");
	  break;
	}
      break;
    default:
      fatal("this cannot happen");
      break;
    }
  if (blockFlag)
    {
      static const size_t px_frame_off= memberOffset(PseudoContext, stack);
      static const size_t mx_stack_off= memberOffset(MethodContext, stack);
      insn *asm_org= asm_pc, *pseudo= asm_pc, *done= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_move_R_r(tmp[0]);				// home context
	  emit_get_i_r_r(0, tmp[0], tmp[0]);			// base header
	  emit_ccick_i_r(PseudoContextCCI, tmp[0]);
	  emit_beq(pseudo);
	  emit_move_R_r(tmp[0]);				// home context
	  emit_put_r_i_r(tmp[1], (mx_stack_off+(idx*4)), tmp[0]);	// store temp
	  emit_jmp(done);
	  pseudo= asm_pc;
	  emit_move_R_r(tmp[0]);				// home context
	  emit_get_i_r_r(px_frame_off, tmp[0], tmp[0]);			// frame
#	 ifdef STACK_GROWS_DOWN
	  emit_put_r_i_r(tmp[1], (f_stack_off+(LargeFrame*4)-4-(idx*4)), tmp[0]);	// store temp
#	 else
	  emit_put_r_i_r(tmp[1], (f_stack_off+(idx*4)), tmp[0]);	// store temp
#	 endif
	  done= asm_pc;
	}
      asm_pass= 0;
    }
  else
    {
      emit_move_r_T(tmp[1], idx);
    }
  // no stack effect
# else
#   error: broken options
# endif
}


static const size_t as_value_off= memberOffset(Association, value);


void genLdLitInd(int idx, bool blockFlag)
{
# if defined(OPT_MACHINE)
  if (blockFlag) {
    emit_call_s(g_bLdLitInd, idx);
  } else {
    emit_call_s(g_LdLitInd, idx);
  }
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_L_r(idx, tmp[0]);
  emit_get_i_r_r(as_value_off, tmp[0], tmp[0]);
  emit_push_r(tmp[0]);
  push(Stack());
# elif defined(OPT_FULL)
  int regNo= allocReg();
  emit_move_L_r(idx, regNo);
  emit_get_i_r_r(as_value_off, regNo, regNo);
  push(Register(regNo));
# else
#   error: broken options
# endif
}

void genStLitInd(int idx, bool blockFlag)
{
# if defined(OPT_MACHINE)
  assert(stackDepth() > 0);
  if (blockFlag) {
    emit_call_s(g_bStLitInd, idx);
  } else {
    emit_call_s(g_StLitInd, idx);
  }
  // no stack effect
# elif defined(OPT_INLINE)
  preDCGflushStack();
  assert(stackDepth() > 0);
  if (blockFlag) {
    emit_call_s(g_bStLitInd, idx);
  } else {
    emit_call_s(g_StLitInd, idx);
  }
  // no stack effect
# elif defined(OPT_FULL)
  assert(stackDepth() > 0);
  // storing a literal variable is RARE -- so who cares?
  flushStack();
  if (blockFlag) {
    emit_call_s(g_bStLitInd, idx);
  } else {
    emit_call_s(g_StLitInd, idx);
  }
  // no stack effect
# else
#   error: broken options
# endif
}


extern "C" { void checkForInterrupts(void); }

void genJmp(insn *dst)
{
# if defined(OPT_MACHINE)
  if (dst <= asm_pc) {
    emit_call_l(g_Jmp, dst);
  } else {
    emit_jmp(dst);
  }
  // no stack effect
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (dst <= asm_pc) {
    emit_jmpck(dst);
    emit_call_l(g_Jmp, dst);
  } else {
    emit_jmp(dst);
  }
  // no stack effect
# elif defined(OPT_FULL)
  flushStack();
  if (dst <= asm_pc) {
    emit_jmpck(dst);
    // interrupt check
#  if 0
    emit_move_i_r((int)dst, tmp[0]);
    emit_savepc_r(tmp[0]);
    emit_extern();
    emit_gcprotect();
    emit_ccall(checkForInterrupts);
#   ifndef NDEBUG
    emit_gcunprotect();
#   endif
    emit_jmp(dst);
#  else
    emit_call_l(g_Jmp, dst);
#   endif
  } else {
    emit_jmp(dst);
  }
  // no stack effect
# else
#   error: broken options
# endif
}


void genJmpF(insn *dst)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  emit_call_l(g_JmpF, dst);
  pop(1);
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (dst <= asm_pc) {
    insn *fail= asm_pc, *ok= asm_pc, *asm_org= asm_pc;
    for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
      asm_pc= asm_org;
      emit_pop_r(tmp[0]);
      emit_jmp1_r(tmp[0], ok);
      emit_jmpN0_r(tmp[0], fail);
      emit_jmpck(dst);
      fail= asm_pc;
      emit_push_r(tmp[0]);
      emit_call_l(g_JmpF, dst);
      ok= asm_pc;
    }
    asm_pass= 0;
  } else {
    insn *ok= asm_pc, *asm_org= asm_pc;
    for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
      asm_pc= asm_org;
      emit_pop_r(tmp[0]);
      emit_jmp0_r(tmp[0], dst);
      emit_jmp1_r(tmp[0], ok);
      emit_push_r(tmp[0]);
      emit_call_l(g_JmpF, dst);
      ok= asm_pc;
    }
    asm_pass= 0;
  }
  pop(1);
# elif defined(OPT_FULL)
  if (stackDescriptor(0).isInteger())
    {
      // don't waste code space by bothering to compile inline jump
      fprintf(stderr, "WARNING: guaranteed non-Boolean receiver in conditional jump\n");
      flushStack();
      emit_call_l(g_JmpF, dst);
      pop(1);
      return;
    }
  if (stackDescriptor(0).isCondition())
    {
      // unambiguous: compile either unconditional jump or nothing at all
      switch (stackValue(0))
	{
	case Condition::FA:	// false: jump always taken => unconditional jump
	  pop(1);
	  genJmp(dst);
	  break;
	case Condition::TR:	// true: jump never taken => no code emitted
	  pop(1);
	  break;
	default:		// something else...
	  fatal("unexpected conditional condition in conditional jump");
	  break;
	}
      return;
    }
  if (stackDescriptor(0).isStack())
    {
      // best approach is to pop into a register and fall though
      assert(stackIsStable());
      int regNo= allocReg();
      emit_pop_r(regNo);
      stackDescriptor(0)= Register(regNo);
    }
  assert(stackDescriptor(0).isRegister());
  int regNo= stackValue(0);
  pop(1);
  flushStack();		// ASSUME: does not kill regNo
  if (dst <= asm_pc)
    {
      // backward jump: incorporate quick interrupt check
      insn *fail= asm_pc, *cont= asm_pc, *asm_org= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_jmp1_r(regNo, cont);	// true => jump not taken
	  emit_jmpN0_r(regNo, fail);	// !false => trap
	  emit_jmpck(dst);		// !interrupt => jump taken
	  fail= asm_pc;
#	  if 0
	  emit_move_i_r((int)dst, tmp[0]);
	  emit_savepc_r(tmp[0]);
	  emit_extern();
	  emit_gcprotect();
	  emit_ccall(checkForInterrupts);
#	  ifndef NDEBUG
	  emit_gcunprotect();
#	  endif
	  emit_jmp(dst);
#	  else
	  emit_push_r(regNo);
	  emit_call_l(g_JmpF, dst);
#	  endif
	  cont= asm_pc;
	}
      asm_pass= 0;
    }
  else
    {
      // forward jump: no interrupt check needed
      insn *cont= asm_pc, *asm_org= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_jmp0_r(regNo, dst);	// false => jump taken
	  emit_jmp1_r(regNo, cont);	// true => jump not taken
	  emit_push_r(regNo);		// trap...
	  emit_call_l(g_JmpF, dst);
	  cont= asm_pc;
	}
      asm_pass= 0;
    }
  // stack already popped (before flush)
# else
#   error: broken options
# endif
}


void genJmpT(insn *dst)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  emit_call_l(g_JmpT, dst);
  pop(1);
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (dst < asm_pc) {
    insn *fail= asm_pc, *ok= asm_pc, *asm_org= asm_pc;
    for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
      asm_pc= asm_org;
      emit_pop_r(tmp[0]);
      emit_jmp0_r(tmp[0], ok);
      emit_jmpN1_r(tmp[0], fail);
      emit_jmpck(dst);
      fail= asm_pc;
      emit_push_r(tmp[0]);
      emit_call_l(g_JmpT, dst);
      ok= asm_pc;
    }
    asm_pass= 0;
  } else {
    insn *ok= asm_pc, *asm_org= asm_pc;
    for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
      asm_pc= asm_org;
      emit_pop_r(tmp[0]);
      emit_jmp1_r(tmp[0], dst);
      emit_jmp0_r(tmp[0], ok);
      emit_push_r(tmp[0]);
      emit_call_l(g_JmpT, dst);
      ok= asm_pc;
    }
    asm_pass= 0;
  }
  pop(1);
# elif defined(OPT_FULL)
  if (stackDescriptor(0).isInteger())
    {
      // don't waste code space by bothering to compile inline jump
      fprintf(stderr, "WARNING: guaranteed non-Boolean receiver in conditional jump\n");
      flushStack();
      emit_call_l(g_JmpT, dst);
      pop(1);
      return;
    }
  if (stackDescriptor(0).isCondition())
    {
      // unambiguous: compile either unconditional jump or nothing at all
      switch (stackValue(0))
	{
	case Condition::TR:	// true: jump always taken => unconditional jump
	  pop(1);
	  genJmp(dst);
	  break;
	case Condition::FA:	// false: jump never taken => no code emitted
	  pop(1);
	  break;
	default:		// something else...
	  fatal("unexpected conditional condition in conditional jump");
	  break;
	}
      return;
    }
  if (stackDescriptor(0).isStack())
    {
      // best approach is to pop into a register and fall though
      assert(stackIsStable());
      int regNo= allocReg();
      emit_pop_r(regNo);
      stackDescriptor(0)= Register(regNo);
    }
  assert(stackDescriptor(0).isRegister());
  int regNo= stackValue(0);
  pop(1);
  flushStack();		// ASSUME: does not kill regNo
  if (dst <= asm_pc)
    {
      // backward jump: incorporate quick interrupt check
      insn *fail= asm_pc, *cont= asm_pc, *asm_org= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_jmp0_r(regNo, cont);	// false => jump not taken
	  emit_jmpN1_r(regNo, fail);	// !true => trap
	  emit_jmpck(dst);		// !interrupt => jump taken
	  fail= asm_pc;
#	  if 0
	  emit_move_i_r((int)dst, tmp[0]);
	  emit_savepc_r(tmp[0]);
	  emit_extern();
	  emit_gcprotect();
	  emit_ccall(checkForInterrupts);
#	  ifndef NDEBUG
	  emit_gcunprotect();
#	  endif
	  emit_jmp(dst);
#	  else
	  emit_push_r(regNo);
	  emit_call_l(g_JmpT, dst);
#	  endif
	  cont= asm_pc;
	}
      asm_pass= 0;
    }
  else
    {
      // forward jump: no interrupt check needed
      insn *cont= asm_pc, *asm_org= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  emit_jmp1_r(regNo, dst);	// true => jump taken
	  emit_jmp0_r(regNo, cont);	// false => jump not taken
	  emit_push_r(regNo);		// trap...
	  emit_call_l(g_JmpT, dst);
	  cont= asm_pc;
	}
      asm_pass= 0;
    }
  // stack already popped (before flush)
# else
#   error: broken options
# endif
}


void genSuper(int selIndex, int nArgs)
{
# if defined(OPT_MACHINE)
  assert(stackDepth() > nArgs);
  emit_call_l(g_SuperUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# elif defined(OPT_INLINE)
  assert(stackDepth() > nArgs);
  preDCGflushStack();
  emit_call_l(g_SuperUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# elif defined(OPT_FULL)
  assert(stackDepth() > nArgs);
  flushStack();
  emit_call_l(g_SuperUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# else
#   error: broken options
# endif
}


void genSend(int selIndex, int nArgs)
{
# if defined(OPT_MACHINE)
  assert(stackDepth() > nArgs);
  emit_call_l(g_SendUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# elif defined(OPT_INLINE)
  assert(stackDepth() > nArgs);
  preDCGflushStack();
  emit_call_l(g_SendUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# elif defined(OPT_FULL)
  assert(stackDepth() > nArgs);
  flushStack();
  emit_call_l(g_SendUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# else
#   error: broken options
# endif
}


void genSpecial(int selIndex, int nArgs)
{
# if defined(OPT_MACHINE)
  assert(stackDepth() > nArgs);
  emit_call_l(g_SpecialUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# elif defined(OPT_INLINE)
  assert(stackDepth() > nArgs);
  preDCGflushStack();
  emit_call_l(g_SpecialUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# elif defined(OPT_FULL)
  assert(stackDepth() > nArgs);
  flushStack();
  emit_call_l(g_SpecialUnlinked, ((selIndex << 8) | nArgs));
  pop(nArgs + 1);
  push(Stack());
# else
#   error: broken options
# endif
}

void genLocalRetTop(void)
{
  // this is to catch self-modifying code in MethodContext>>answer:
  if (stackDepth() == 0)
    push(Stack());
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  emit_call(g_LocalRetTop);
  pop(1);
# elif defined(OPT_INLINE)
  preDCGflushStack();
  PRINTF(("%p emit LocalRetTop\n", asm_pc));
  emit_move_S_r(0, reg[0]);
  emit_extern();		// SP for cannotReturn
  emit_popf();
  emit_intern();
  emit_push_r(reg[0]);
  emit_resume();
  pop(1);
# elif defined(OPT_FULL)
  PRINTF(("%p emit LocalRetTop\n", asm_pc));
  switch (stackType(0))
    {
    case Stack::Type:
      emit_move_S_r(0, reg[0]);				// answer
      break;
    case Integer::Type:
      emit_move_i_r((int)Object::integer(stackValue(0)), reg[0]);
      break;
    case Register::Type:
      if (stackValue(0) != reg[0])
	{
	  emit_move_r_r(stackValue(0), reg[0]);		// answer
	}
      break;
    case Condition::Type:
      switch (stackValue(0))
	{
	case Condition::TR:
	  emit_move_1_r(reg[0]);			// answer
	  break;
	case Condition::FA:
	  emit_move_0_r(reg[0]);			// answer
	  break;
	default:
	  fatal("this cannot happen");
	  break;
	}
      break;
    default:
      fatal("this cannot happen (%s)", stackDescriptor(0).printString());
      break;
    }
  emit_extern();		// SP for cannotReturn
  emit_popf();
  emit_intern();
  emit_push_r(reg[0]);  
  emit_resume();
  pop(1);
  // NOTE: we have IMPLICITLY TRUNCATED THE STACK to the last flushed item.
  // Maybe we don't care, but any #cannotReturn: code that depends on seeing
  // an up-to-date exiting Context will get horribly, horribly confused.
  // Exceptions seem to work, but who knows...
# else
#   error: broken options
# endif
}


static Frame *g_c_RemoteRetCheck(Frame *frame)
{
  if (frame->senderFrame()->isBaseFrame())
    return 0;
# ifndef BLOCK_CLOSURES
  assert(frame->hasPseudoContext());
  assert(frame->pseudoContext()->isPseudoBlockContext());
# endif
  PseudoContext *home= frame->receiver->asPseudoContext();
  if (home->isMethodContext())
    return 0;
  assert(home->isPseudoMethodContext());
  Frame *homeFrame= home->frame();
  Frame *returneeFrame= homeFrame->senderFrame();
  if (returneeFrame->isBaseFrame())
    return 0;
  // check we can reach the home frame
  {
    Frame *f= frame;
    while (f != returneeFrame)
      {
	if (f == 0)
	  return 0;
	f= f->senderFrame();
      }
  }
  // stabilise through home frame
  {
    Frame *f= frame;
    while (f != returneeFrame)
      {
	if (f->hasPseudoContext())
	  {
	    GC_PROTECT(f, {
	      f->stabiliseForReturn();
	    });
	  }
	f= f->senderFrame();
      }
  }
  returneeFrame->clearFault();	// already stabilised home frame
  return returneeFrame;
}


void genRemoteRetTop(void)
{
# if defined(OPT_MACHINE)
  assert(stackDepth() > 0);
  emit_call(g_RemoteRetTop);
  pop(1);
# elif defined(OPT_INLINE)
  assert(stackDepth() > 0);
  preDCGflushStack();
# if 0
  insn *asm_org= asm_pc, *fail= asm_pc;
  for (asm_pass= 1;  asm_pass < 3;  ++asm_pass)
    {
      asm_pc= asm_org;
      emit_extern();
      emit_move_F_r(reg[0]);		// returning frame
      emit_mkcargs1();
      emit_ccall(g_c_RemoteRetCheck);
      emit_killcargs1();
      emit_test_r(reg[0]);
      emit_beq(fail);			// false -> use glue 
      emit_move_S_r(0, tmp[0]);		// answer
      emit_popfto_r(reg[0]);		// pop to returnee frame
      emit_intern();
      emit_push_r(tmp[0]);
      emit_resume();
      fail= asm_pc;
    }
  asm_pass= 0;
# endif
  emit_call(g_RemoteRetTop);
  pop(1);
# elif defined(OPT_FULL)
  flushStack();				// AIEE !!!
  insn *asm_org= asm_pc, *fail= asm_pc;
  for (asm_pass= 1;  asm_pass < 3;  ++asm_pass)
    {
      asm_pc= asm_org;
      emit_extern();
      emit_move_F_r(reg[0]);		// returning frame
      emit_mkcargs1();
      emit_ccall(g_c_RemoteRetCheck);
      emit_killcargs1();
      emit_test_r(reg[0]);
      emit_beq(fail);			// false -> use glue 
      emit_move_S_r(0, tmp[0]);		// answer
      emit_popfto_r(reg[0]);		// pop to returnee frame
      emit_intern();
      emit_push_r(tmp[0]);
      emit_resume();
      fail= asm_pc;
    }
  asm_pass= 0;
  emit_call(g_RemoteRetTop);
  pop(1);
# else
#   error: broken options
# endif
}


#if defined(OPT_INLINE) || defined(OPT_FULL)

// answers whether tag check was generated

static inline bool genTagCk2Trap(const Descriptor &a, const Descriptor &b,
				 const insn *trap)
{
  PRINTF(("genTagCk2Trap(%s, %s, %p)\n", a.printString(), b.printString(), trap));

  assert(a.isRegister() || a.isInteger());
  assert(b.isRegister() || b.isInteger());

  if (!a.isTagged())
    {
      if (!b.isTagged())
	{
	  assert(a.isRegister());
	  assert(b.isRegister());
	  emit_tagck_r_r_t(a.value, b.value, trap);
	  return true;
	}
      else
	{
	  assert(a.isRegister());
	  emit_tagck_r_t(a.value, trap);
	  return true;
	}
    }
  else
    if (!b.isTagged())
      {
	assert(b.isRegister());
	emit_tagck_r_t(b.value, trap);
	return true;
      }
  return false;
}


static inline void genS2CkTrap(int ra, int rb, insn *trap)
{
  emit_move_S_r(1, ra);	// receiver
  emit_move_S_r(0, rb);	// argument
  emit_tagck_r_r_t(ra, rb, trap);
}

static inline void genS2CkUntagTrap(int ra, int rb, insn *trap)
{
  genS2CkTrap(ra, rb, trap);
  emit_untag_r(ra);
  emit_untag_r(rb);
}

static inline void genCkEntagS1Trap(int ra, insn *trap)
{
  emit_entagintck_r_t(ra, trap);
  emit_move_r_popS(ra, 1);
}

#endif


#if defined(OPT_FULL)

static void genSpecialArithmetic(int index, insn *glue, int pass)
{
  Descriptor rcv= stackDescriptor(1);
  Descriptor arg= stackDescriptor(0);

  if ((rcv.isCondition() || arg.isCondition())
      || (((index == SelectorModIndex) || (index == SelectorDivIndex))
	  && ((rcv.isInteger() && rcv.value < 0)
	      || (arg.isInteger() && arg.value <= 0))))
    {
      flushStack();
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((index << 8) | 1));
#     else
      emit_call(glue);
#     endif
      pop(2);
      push(Stack());
      return;
    }

  if (rcv.isInteger() && arg.isInteger())
    {
      assert(Object::isIntegerValue(rcv.value));
      assert(Object::isIntegerValue(arg.value));
      int result= 0;
      bool ok= true;
      switch (index)
	{
	case SelectorAddIndex:
	  result= rcv.value + arg.value;
	  break;
	case SelectorSubtractIndex:
	  result= rcv.value - arg.value;
	  break;
	case SelectorMultiplyIndex:
	  result= rcv.value * arg.value;
	  ok= ((arg.value == 0) || ((result / arg.value) == rcv.value));
	  break;
	case SelectorModIndex:
	  assert(rcv.value >= 0);
	  assert(arg.value > 0);
	  result= rcv.value % arg.value;
	  break;
	case SelectorDivIndex:
	  assert(rcv.value >= 0);
	  assert(arg.value > 0);
	  result= rcv.value / arg.value;
	  break;
	default:
	  fatal("this cannot happen");
	  break;
	}
      if (ok && Object::isIntegerValue(result))
	{
	  pop(2);
	  push(Integer(result));
	  return;
	}
      flushStack();
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((index << 8) | 1));
#     else
      emit_call(glue);
#     endif
      pop(2);
      push(Stack());
      return;
    }

  size_t depth= 0;
  
  if (rcv.isStack() && arg.isStack())
    {
      emit_move_S_r(1, tmp[0]);
      emit_move_S_r(0, tmp[1]);
      rcv.beRegister(tmp[0]);
      arg.beRegister(tmp[1]);
      depth= 2;
    }
  else if (rcv.isStack())
    {
      emit_move_S_r(0, tmp[0]);
      rcv.beRegister(tmp[0]);
      depth= 1;
    }
  else if (arg.isStack())
    {
      emit_move_S_r(0, tmp[1]);
      arg.beRegister(tmp[1]);
      depth= 1;
    }

  bool needTrap= true, needTagCk= true;

  if (rcv.isTagged() && arg.isTagged())
    {
      needTagCk= false;
      if ((index == SelectorModIndex) || (index == SelectorDivIndex))
	needTrap= true;	// SHOULD BE FALSE!!!
    }

  if (needTrap)
    flushStack(2);

  DeferredSend &trap= DeferredSend::allocate(index, pass);

  if (needTagCk)
    genTagCk2Trap(rcv, arg, trap.enter());

  bool needEntag= true;

  switch (types(rcv, arg))
    {
    typeCases(Register, Register):
      {
	//emit_tagck_r_r_t(rcv.value, arg.value, trap.enter());
	switch (index)
	  {
	  case SelectorModIndex:
	  case SelectorDivIndex:
	    emit_untaggeck_r_r_t(rcv.value, tmp[0], trap.enter());
	    emit_untaggtck_r_r_t(arg.value, tmp[1], trap.enter());
	    break;
#	  if defined(emit_tadd_r_r_r_t)
	  case SelectorAddIndex:
#	  endif
#	  if defined(emit_tsub_r_r_r_t)
	  case SelectorSubtractIndex:
#	  endif
	    break;
	  default:
	    emit_untag_r_r(rcv.value, tmp[0]);
	    emit_untag_r_r(arg.value, tmp[1]);
	    break;
	  }
	switch (index)
	  {
	  case SelectorAddIndex:
#	    if defined(emit_tadd_r_r_r_t)
	    PRINTF(("taddrr %p\n", asm_pc));
	    emit_tadd_r_r_r_t(rcv.value, arg.value, tmp[0], trap.enter());
	    needEntag= false;
#	    else
	    emit_add_r_r(tmp[1], tmp[0]);
#	    endif
	    break;
	  case SelectorSubtractIndex:
#	    if defined(emit_tsub_r_r_r_t)
	    PRINTF(("tsubrr %p\n", asm_pc));
	    emit_tsub_r_r_r_t(arg.value, rcv.value, tmp[0], trap.enter());
	    needEntag= false;
#	    else
	    emit_sub_r_r(tmp[1], tmp[0]);
#	    endif
	    break;
	  case SelectorMultiplyIndex:
	    emit_mul_r_r_t(tmp[1], tmp[0], trap.enter());
	    break;
	  case SelectorModIndex:
	    emit_mod_r_r(tmp[1], tmp[0]);
	    break;
	  case SelectorDivIndex:
	    emit_div_r_r(tmp[1], tmp[0]);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;	// result in tmp[0]
      }
    typeCases(Integer, Register):
      {
	assert(Object::isIntegerValue(rcv.value));
	//emit_tagck_r_t(arg.value, trap.enter());
	switch (index)
	  {
	  case SelectorModIndex:
	  case SelectorDivIndex:
	    emit_untaggtck_r_r_t(arg.value, tmp[0], trap.enter());
	    break;
	  default:
	    emit_untag_r_r(arg.value, tmp[0]);
	    break;
	  }
	switch (index)
	  {
	  case SelectorAddIndex:
	    emit_add_i_r(rcv.value, tmp[0]);			// commutative
	    break;
	  case SelectorSubtractIndex:
	    emit_neg_r(tmp[0]);
	    emit_add_i_r(rcv.value, tmp[0]);
	    break;
	  case SelectorMultiplyIndex:
	    emit_mul_i_r_t(rcv.value, tmp[0], trap.enter());	// commutative
	    break;
	  case SelectorModIndex:
	    assert(rcv.value >= 0);
	    emit_move_r_r(tmp[0], tmp[1]);
	    emit_move_i_r(rcv.value, tmp[0]);
	    emit_mod_r_r(tmp[1], tmp[0]);
	    break;
	  case SelectorDivIndex:
	    assert(rcv.value >= 0);
	    emit_move_r_r(tmp[0], tmp[1]);
	    emit_move_i_r(rcv.value, tmp[0]);
	    emit_div_r_r(tmp[1], tmp[0]);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;	// result in tmp[0]
      }
    typeCases(Register, Integer):
      {
	assert(Object::isIntegerValue(arg.value));
	//emit_tagck_r_t(rcv.value, trap.enter());
	switch (index)
	  {
	  case SelectorModIndex:
	  case SelectorDivIndex:
	    emit_untaggeck_r_r_t(rcv.value, tmp[0], trap.enter());
	    break;
#	  if defined(emit_tadd_i_r_r_t)
	  case SelectorAddIndex:
#	  endif
#	  if defined(emit_tsub_i_r_r_t)
	  case SelectorSubtractIndex:
#	  endif
	    break;
	  default:
	    emit_untag_r_r(rcv.value, tmp[0]);
	    break;
	  }
	switch (index)
	  {
	  case SelectorAddIndex:
#	    if defined(emit_tadd_i_r_r_t)
	    PRINTF(("taddir %p\n", asm_pc));
	    emit_tadd_i_r_r_t((int)Object::integer(arg.value), rcv.value, tmp[0],
			      trap.enter());
	    needEntag= false;
#	    else
	    emit_add_i_r(arg.value, tmp[0]);
#	    endif
	    break;
	  case SelectorSubtractIndex:
#	    if defined(emit_tsub_i_r_r_t)
	    PRINTF(("tsubir %p\n", asm_pc));
	    emit_tsub_i_r_r_t((int)Object::integer(arg.value), rcv.value, tmp[0],
			      trap.enter());
	    needEntag= false;
#	    else
	    emit_sub_i_r(arg.value, tmp[0]);
#	    endif
	    break;
	  case SelectorMultiplyIndex:
	    emit_mul_i_r_t(arg.value, tmp[0], trap.enter());
	    break;
	  case SelectorModIndex:
	    emit_mod_i_r(arg.value, tmp[0]);
	    break;
	  case SelectorDivIndex:
	    emit_div_i_r(arg.value, tmp[0]);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;	// result in tmp[0]
      }
    default:
      fatal("this cannot happen");
      break;
    }

  if (needEntag)
    {
      // unchecked, untagged result in tmp[0]
      emit_entagintck_r_t(tmp[0], trap.enter());
    }

  // pop args if necessary then push result
  switch (depth)
    {
    case 0:  emit_push_r(tmp[0]);		break;
    case 1:  emit_move_r_S(tmp[0], 0);		break;
    case 2:  emit_move_r_popS(tmp[0], 1);	break;
    default: fatal("this cannot happen");	break;
    }

  pop(2);
  push(Stack());
  trap.resume(pass);
}

#endif


void genAdd(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Add);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass)
    {
      asm_pc= asm_org;
      genS2CkUntagTrap(tmp[0], tmp[1], trap);
      emit_add_r_r(tmp[1], tmp[0]);
      genCkEntagS1Trap(tmp[0], trap);
      emit_jmp(cont);
      trap= asm_pc;
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((SelectorAddIndex << 8) | 1));
#     else
      emit_call(g_Add);
#     endif
      cont= asm_pc;
    }
  asm_pass= 0;
  pop(2);
  push(Stack());
#elif defined(OPT_FULL)
  genSpecialArithmetic(SelectorAddIndex, g_Add, pass);
# else
#   error: broken options
# endif
}


void genSubtract(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Subtract);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
    asm_pc= asm_org;
    genS2CkUntagTrap(tmp[0], tmp[1], trap);
    emit_sub_r_r(tmp[1], tmp[0]);
    genCkEntagS1Trap(tmp[0], trap);
    emit_jmp(cont);
    trap= asm_pc;
#   if defined(LINKED_SI_TRAPS) // send will be linked
    emit_call_l(g_SpecialUnlinked, ((SelectorSubtractIndex << 8) | 1));
#   else
    emit_call(g_Subtract);
#   endif
    cont= asm_pc;
  }
  asm_pass= 0;
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  genSpecialArithmetic(SelectorSubtractIndex, g_Subtract, pass);
# else
#   error: broken options
# endif
}


static bool genRelation(int cond, const insn *reify, const int selIndex,
			const int jump, const insn *dest, const insn *next, bool squash,
			int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(reify);
  pop(2);
  push(Stack());
  return false;
# elif defined(OPT_INLINE)
  preDCGflushStack();
  insn *asm_org= asm_pc, *sett= asm_pc, *setok= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass)
    {
      asm_pc= asm_org;
      genS2CkTrap(tmp[0], tmp[1], trap);
      if (jump)
	{
	  emit_drop(2);
	}
      emit_cmp_r_r(tmp[0], tmp[1]);
      if (jump < 0)
	{
	  genInvertedJump(cond, dest);
	  emit_jmp(next);
	}
      else if (jump > 0)
	{
	  genCondJump(cond, dest);
	  emit_jmp(next);
	}
      else
	{
	  genCondJump(cond, sett);
	  emit_move_0_r(tmp[0]);
	  emit_jmp(setok);
	  sett= asm_pc;
	  emit_move_1_r(tmp[0]);
	  setok= asm_pc;
	  emit_move_r_popS(tmp[0], 1);
	  emit_jmp(cont);
	}
      trap= asm_pc;
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((selIndex << 8) | 1));
#     else
      emit_call(reify);
#     endif
      cont= asm_pc;
    }
  asm_pass= 0;
  pop(2);
  push(Stack());
  return false;
# elif defined(OPT_FULL)
  Descriptor rcv= stackDescriptor(1);
  Descriptor arg= stackDescriptor(0);

  if ((rcv.isCondition()) || arg.isCondition())
    {
      flushStack();
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((selIndex << 8) | 1));
#     else
      emit_call(reify);
#     endif
      pop(2);
      push(Stack());
      return false;
    }

  if (rcv.isInteger() && arg.isInteger())
    {
      assert(Object::isIntegerValue(rcv.value));
      assert(Object::isIntegerValue(arg.value));
      bool answer= false;
      switch (cond)
	{
	case Condition::LT: answer= (rcv.value <  arg.value); break;
	case Condition::LE: answer= (rcv.value <= arg.value); break;
	case Condition::EQ: answer= (rcv.value == arg.value); break;
	case Condition::NE: answer= (rcv.value != arg.value); break;
	case Condition::GT: answer= (rcv.value >  arg.value); break;
	case Condition::GE: answer= (rcv.value >= arg.value); break;
	default:
	  fatal("this cannot happen");
	  break;
	}
      pop(2);
      if (answer)
	push(Condition::Tr);
      else
	push(Condition::Fa);
      return false;	// Jmp[TF] know how to optimise this
    }

# if 0 // not when one side is a non-SmallInt magnitude = 1
  if (rcv.isTagged() || arg.isTagged())
    {
      // tagged equality is equivalent to equivalence
      switch (cond)
	{
	case Condition::EQ:
	  PRINTF(("EQ->EQV %2dj %cs %s %s\n", jump, (squash ? 'T' : 'F'),
		  rcv.printString(), arg.printString()));
	  return genEquivalent(jump, dest, next, squash, false);
	case Condition::NE:
	  return genEquivalent(jump, dest, next, squash, true);
	}
    }
# endif

  size_t depth= 0;
  
  if (rcv.isStack() && arg.isStack())
    {
      emit_move_S_r(1, tmp[0]);
      rcv.beRegister(tmp[0]);
      emit_move_S_r(0, tmp[1]);
      arg.beRegister(tmp[1]);
      depth= 2;
    }
  else if (rcv.isStack())
    {
      emit_move_S_r(0, tmp[0]);
      rcv.beRegister(tmp[0]);
      depth= 1;
    }
  else if (arg.isStack())
    {
      emit_move_S_r(0, tmp[1]);
      arg.beRegister(tmp[1]);
      depth= 1;
    }

  flushStack(2);	// must be prepared for real send and control flow change
  DeferredSend &trap= DeferredSend::allocate(selIndex, pass);

  pop(2);

  bool hasTrap= genTagCk2Trap(rcv, arg, trap.enter());
  if (hasTrap)
    squash= false;	// trap must have a resumption point

  if (jump && depth) {
    emit_drop(depth);
  }

  switch (types(rcv, arg))
    {
    typeCases(Register, Register):
      {
	//emit_tagck_r_r_t(rcv.value, arg.value, trap.enter());
	emit_cmp_r_r(rcv.value, arg.value);
	break;
      }
    typeCases(Integer, Register):
      {
	assert(Object::isIntegerValue(rcv.value));
	//emit_tagck_r_t(arg.value, trap.enter());
#	if defined(emit_cmp_i_r)
	emit_cmp_i_r((int)Object::integer(rcv.value), arg.value);
#	elif defined(emit_cmp_r_i)
	emit_cmp_r_i(arg.value, (int)Object::integer(rcv.value));
	cond= Condition::invertedInequality((Condition::ccode)cond);
#	else
#	error: cannot find compare immediate instruction
#	endif
	break;
      }
    typeCases(Register, Integer):
      {
	assert(Object::isIntegerValue(arg.value));
	//emit_tagck_r_t(rcv.value, trap.enter());
#	if defined(emit_cmp_r_i)
	emit_cmp_r_i(rcv.value, (int)Object::integer(arg.value));
#	elif defined(emit_cmp_i_r)
	emit_cmp_i_r((int)Object::integer(arg.value), rcv.value);
	cond= Condition::invertedInequality((Condition::ccode)cond);
#	else
#	error: cannot find compare immediate instruction
#	endif
	break;
      }
    default:
      fatal("this cannot happen");
      break;
    }

  if (jump)
    {
      assert(dest != 0);
      if (jump < 0) {
	genInvertedJump(cond, dest);
      } else {
	assert(jump > 0);
	genCondJump(cond, dest);
      }
      assert(next != 0);
      if (!squash) {
	emit_jmp(next);
	push(Stack());	// next insn is dest -- gets input from stack
	assert(stackIsStable());
      }
    }
  else
    {
      int regNo= allocReg();
      insn *asm_org= asm_pc, *sett= asm_pc, *setok= asm_pc;
      for (asm_pass= 1;  asm_pass < 3;  ++asm_pass)
	{
	  asm_pc= asm_org;
	  genCondJump(cond, sett);
	  emit_move_0_r(regNo);
	  emit_jmp(setok);
	  sett= asm_pc;
	  emit_move_1_r(regNo);
	  setok= asm_pc;
	}
      asm_pass= 0;
      // pop args if necessary then push result
#if 0
      if (depth) {
	emit_drop(depth);
      }
      push(Register(regNo));
#else
      switch (depth)
	{
	case 0:  emit_push_r(regNo);		break;
	case 1:  emit_move_r_S(regNo, 0);	break;
	case 2:  emit_move_r_popS(regNo, 1);	break;
	default: fatal("this cannot happen");	break;
	}
      push(Stack());
      assert(stackIsStable());
#endif
    }
  if (hasTrap)
    {
      trap.resume(pass);
    }
  else
    {
      trap.annul();
    }
  return squash;
# else
#   error: broken options
# endif
}


bool genLessThan(int pass, int jump, insn *dest, insn *next, bool squash)
{
  return genRelation(Condition::LT, g_LessThan, SelectorLessThanIndex, jump, dest, next, squash, pass);
}


bool genGreaterThan(int pass, int jump, insn *dest, insn *next, bool squash)
{
  return genRelation(Condition::GT, g_GreaterThan, SelectorGreaterThanIndex, jump, dest, next, squash, pass);
}


bool genLessOrEqual(int pass, int jump, insn *dest, insn *next, bool squash)
{
  return genRelation(Condition::LE, g_LessOrEqual, SelectorLessOrEqualIndex, jump, dest, next, squash, pass);
}


bool genGreaterOrEqual(int pass, int jump, insn *dest, insn *next, bool squash)
{
  return genRelation(Condition::GE, g_GreaterOrEqual, SelectorGreaterOrEqualIndex, jump, dest, next, squash, pass);
}


bool genEqual(int pass, int jump, insn *dest, insn *next, bool squash)
{
  return genRelation(Condition::EQ, g_Equal, SelectorEqualIndex, jump, dest, next, squash, pass);
}


bool genNotEqual(int pass, int jump, insn *dest, insn *next, bool squash)
{
  return genRelation(Condition::NE, g_NotEqual, SelectorNotEqualIndex, jump, dest, next, squash, pass);
}


void genMultiply(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Multiply);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass)
    {
      asm_pc= asm_org;
      genS2CkUntagTrap(tmp[0], tmp[1], trap);
      emit_mul_r_r_t(tmp[1], tmp[0], trap);	// trap if 32-bit overflow
      genCkEntagS1Trap(tmp[0], trap);
      emit_jmp(cont);
      trap= asm_pc;
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((SelectorMultiplyIndex << 8) | 1));
#     else
      emit_call(g_Multiply);
#     endif
      cont= asm_pc;
    }
  asm_pass= 0;
  pop(2);
  push(Stack());
#elif defined(OPT_FULL)
  genSpecialArithmetic(SelectorMultiplyIndex, g_Multiply, pass);
# else
#   error: broken options
# endif
}


void genDivide(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Divide);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
# if defined(LINKED_SI_TRAPS) // send will be linked
  emit_call_l(g_SpecialUnlinked, ((SelectorDivideIndex << 8) | 1));
# else
  emit_call(g_Divide);
# endif
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  flushStack();
# if defined(LINKED_SI_TRAPS) // send will be linked
  emit_call_l(g_SpecialUnlinked, ((SelectorDivideIndex << 8) | 1));
# else
  emit_call(g_Divide);
# endif
  pop(2);
  push(Stack());
# else
#   error: broken options
# endif
}


void genMod(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Mod);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  // we only do the case where rcv and arg are both positive, punting
  // to glue for the other three cases (which are tricky due to
  // rounding rules, and fortunately rather rare)
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass)
    {
      asm_pc= asm_org;
      genS2CkTrap(tmp[0], tmp[1], trap);
      emit_untag_r(tmp[0]);
      emit_untagnzck_r_t(tmp[1], trap);	// divide by zero
      emit_posck_r_r_t(tmp[0], tmp[1], trap);
      emit_mod_r_r(tmp[1], tmp[0]);
      genCkEntagS1Trap(tmp[0], trap);
      emit_jmp(cont);
      trap= asm_pc;
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((SelectorModIndex << 8) | 1));
#     else
      emit_call(g_Mod);
#     endif
      cont= asm_pc;
    }
  asm_pass= 0;
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  genSpecialArithmetic(SelectorModIndex, g_Mod, pass);
# else
#   error: broken options
# endif
}


void genMakePoint(void)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  preDCGflushStack();
  emit_call(g_MakePoint);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_call(g_MakePoint);
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  flushStack();
  emit_call(g_MakePoint);
  pop(2);
  push(Stack());
# else
#   error: broken options
# endif
}


#ifdef OPT_FULL
#define OPT_INLINE
#endif


void genBitShift(void)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  emit_call(g_BitShift);
  pop(2);
  push(Stack());
}


#ifdef OPT_FULL
#undef OPT_INLINE
#endif


void genDiv(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Div);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  // we only do the case where rcv and arg are both positive, punting
  // to glue for the other three cases (which are tricky due to
  // rounding rules, and fortunately rather rare)
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass)
    {
      asm_pc= asm_org;
      genS2CkTrap(tmp[0], tmp[1], trap);
      emit_untag_r(tmp[0]);
      emit_untagnzck_r_t(tmp[1], trap);	// divide by zero
      emit_posck_r_r_t(tmp[0], tmp[1], trap);
      emit_div_r_r(tmp[1], tmp[0]);
      genCkEntagS1Trap(tmp[0], trap);
      emit_jmp(cont);
      trap= asm_pc;
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((SelectorDivIndex << 8) | 1));
#     else
      emit_call(g_Div);
#     endif
      cont= asm_pc;
    }
  asm_pass= 0;
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  genSpecialArithmetic(SelectorDivIndex, g_Div, pass);
# else
#   error: broken options
# endif
}


#if defined(OPT_FULL)

static void genSpecialBitwise(int index, insn *glue, int pass)
{
  Descriptor rcv= stackDescriptor(1);
  Descriptor arg= stackDescriptor(0);

  if ((rcv.isCondition() || arg.isCondition())
      || (rcv.isInteger() && !Object::isPositiveIntegerValue(rcv.value))
      || (arg.isInteger() && !Object::isPositiveIntegerValue(arg.value)))
    {
      flushStack();
#     if defined(LINKED_SI_TRAPS) // send will be linked
      emit_call_l(g_SpecialUnlinked, ((index << 8) | 1));
#     else
      emit_call(glue);
#     endif
      pop(2);
      push(Stack());
      return;
    }

  if (rcv.isInteger() && arg.isInteger())
    {
      int result= 0;
      switch (index)
	{
	case SelectorBitAndIndex: result= rcv.value & arg.value;  break;
	case SelectorBitOrIndex:  result= rcv.value | arg.value;  break;
	default:
	  fatal("this cannot happen");
	  break;
	}
      pop(2);
      assert(Object::isPositiveIntegerValue(result));
      push(Integer(result));
      return;
    }

  flushStack(2);	// must be prepared for real send
  DeferredSend &trap= DeferredSend::allocate(index, pass);
  size_t depth= 0;
  
  if (rcv.isStack() && arg.isStack())
    {
      emit_move_S_r(1, tmp[0]);
      emit_move_S_r(0, tmp[1]);
      rcv.beRegister(tmp[0]);
      arg.beRegister(tmp[1]);
      depth= 2;
    }
  else if (rcv.isStack())
    {
      emit_move_S_r(0, tmp[0]);
      rcv.beRegister(tmp[0]);
      depth= 1;
    }
  else if (arg.isStack())
    {
      emit_move_S_r(0, tmp[1]);
      arg.beRegister(tmp[1]);
      depth= 1;
    }

  switch (types(rcv, arg))
    {
    typeCases(Register, Register):
      {
	emit_taggeck_r_r_t(rcv.value, arg.value, trap.enter());
	switch (index)
	  {
	  case SelectorBitAndIndex: emit_and_r_r(arg.value, rcv.value);  break;
	  case SelectorBitOrIndex:   emit_or_r_r(arg.value, rcv.value);  break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;	// result in rcv.value
      }
    typeCases(Integer, Register):
      {
	assert(Object::isPositiveIntegerValue(rcv.value));
	emit_taggeck_r_t(arg.value, trap.enter());
	switch (index)
	  {
	  case SelectorBitAndIndex:
	    emit_and_i_r((int)Object::integer(rcv.value), arg.value);
	    break;
	  case SelectorBitOrIndex:
	    emit_or_i_r((int)Object::integer(rcv.value), arg.value);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	rcv= arg;
	break;	// result in rcv.value
      }
    typeCases(Register, Integer):
      {
	assert(Object::isPositiveIntegerValue(arg.value));
	emit_taggeck_r_t(rcv.value, trap.enter());
	switch (index)
	  {
	  case SelectorBitAndIndex:
	    emit_and_i_r((int)Object::integer(arg.value), rcv.value);
	    break;
	  case SelectorBitOrIndex:
	    emit_or_i_r((int)Object::integer(arg.value), rcv.value);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;	// result in rcv.value
      }
    default:
      fatal("this cannot happen");
      break;
    }
  // result in rcv.value

  // pop args if necessary then push result
  switch (depth)
    {
    case 0:  emit_push_r(rcv.value);		break;
    case 1:  emit_move_r_S(rcv.value, 0);	break;
    case 2:  emit_move_r_popS(rcv.value, 1);	break;
    default: fatal("this cannot happen");	break;
    }

  pop(2);
  push(Stack());
  trap.resume(pass);
}

#endif


void genBitAnd(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_BitAnd);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
    asm_pc= asm_org;
    genS2CkTrap(tmp[0], tmp[1], trap);
    emit_and_r_r(tmp[1], tmp[0]);
    emit_move_r_popS(tmp[0], 1);
    emit_jmp(cont);
    trap= asm_pc;
#   if defined(LINKED_SI_TRAPS) // send will be linked
    emit_call_l(g_SpecialUnlinked, ((SelectorBitAndIndex << 8) | 1));
#   else
    emit_call(g_BitAnd);
#   endif
    cont= asm_pc;
  }
  asm_pass= 0;
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  genSpecialBitwise(SelectorBitAndIndex, g_BitAnd, pass);
# else
#   error: broken options
# endif
}


void genBitOr(int pass)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_BitOr);
  pop(2);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
    asm_pc= asm_org;
    genS2CkTrap(tmp[0], tmp[1], trap);
    emit_or_r_r(tmp[1], tmp[0]);
    emit_move_r_popS(tmp[0], 1);
    emit_jmp(cont);
    trap= asm_pc;
#   if defined(LINKED_SI_TRAPS) // send will be linked
    emit_call_l(g_SpecialUnlinked, ((SelectorBitOrIndex << 8) | 1));
#   else
    emit_call(g_BitOr);
#   endif
    cont= asm_pc;
  }
  asm_pass= 0;
  pop(2);
  push(Stack());
# elif defined(OPT_FULL)
  genSpecialBitwise(SelectorBitOrIndex, g_BitOr, pass);
# else
#   error: broken options
# endif
}


#ifdef OPT_FULL
#define OPT_INLINE
#endif


void genAt(void)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  genSpecial(SelectorAtIndex, 1);
}


void genAtPut(void)
{
  assert(stackDepth() > 2);
  preDCGflushStack();
  genSpecial(SelectorAtPutIndex, 2);
}


void genSize(void)
{
  assert(stackDepth() > 0);
  preDCGflushStack();
  genSpecial(SelectorSizeIndex, 0);
}


void genNext(void)
{
  assert(stackDepth() > 0);
  preDCGflushStack();
  genSpecial(SelectorNextIndex, 0);
}


void genNextPut(void)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  genSpecial(SelectorNextPutIndex, 1);
}


void genAtEnd(void)
{
  assert(stackDepth() > 0);
  preDCGflushStack();
  genSpecial(SelectorAtEndIndex, 0);
}


#ifdef OPT_FULL
#undef OPT_INLINE
//#define OPT_INLINE
#endif


bool genEquivalent(int jump, const insn *dest, const insn *next, bool squash, bool invert)
{
  assert(stackDepth() > 1);
# if defined(OPT_MACHINE)
  emit_call(g_Equivalent);
  pop(2);
  push(Stack());
  return false;
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_pop_r(tmp[1]);
  emit_pop_r(tmp[0]);
  pop(2);
  emit_cmp_r_r(tmp[0], tmp[1]);
  if (jump != 0)
    {
      if (invert)
	jump= (0-jump);

      if (jump < 0)
	{
	  emit_bne(dest);
	}
      else
	{
	  assert(jump > 0);
	  emit_beq(dest);
	}
      if (!squash)
	{
	  emit_jmp(next);
	  push(Stack());
	}
    }
  else
    {
      insn *asm_org= asm_pc, *sett= asm_pc, *setok= asm_pc;
      for (asm_pass= 1; asm_pass < 3; ++asm_pass)
	{
	  asm_pc= asm_org;
	  if (invert) {
	    emit_bne(sett);
	  } else {
	    emit_beq(sett);
	  }
	  emit_move_0_r(tmp[0]);
	  emit_jmp(setok);
	  sett= asm_pc;
	  emit_move_1_r(tmp[0]);
	  setok= asm_pc;
	  emit_push_r(tmp[0]);
	}
      asm_pass= 0;
      push(Stack());
    }
  return squash;
# elif defined(OPT_FULL)
  Descriptor rcv= stackDescriptor(1);
  Descriptor arg= stackDescriptor(0);

  switch (types(rcv, arg))
    {
    typeCases(Condition, Condition):
    typeCases(Integer, Integer):
      {
	pop(2);
	if (rcv.value == arg.value)
	  push(invert ? Condition::Fa : Condition::Tr);
	else
	  push(invert ? Condition::Tr : Condition::Fa);
	return false;
      }
    typeCases(Condition, Integer):
    typeCases(Integer, Condition):
      {
	pop(2);
	push(invert ? Condition::Tr : Condition::Fa);
	return false;
      }
    }

  if (jump)
    {
      flushStack(2);
      if (invert)
	jump= 0 - jump;
    }

  int depth= 0;

  if (rcv.isStack() && arg.isStack())
    {
      emit_move_S_r(1, tmp[0]);
      emit_move_S_r(0, tmp[1]);
      rcv= Register(tmp[0]);
      arg= Register(tmp[1]);
      depth= 2;
    }
  else if (rcv.isStack())
    {
      emit_move_S_r(0, tmp[0]);
      rcv= Register(tmp[0]);
      depth= 1;
    }
  else if (arg.isStack())
    {
      emit_move_S_r(0, tmp[1]);
      arg= Register(tmp[1]);
      depth= 1;
    }

  pop(2);

  switch (types(rcv, arg))
    {
    typeCases(Register, Register):
      {
	if (jump && depth) {
	  emit_drop(depth);
	}
	emit_cmp_r_r(rcv.value, arg.value);
	break;
      }
    typeCases(Integer, Register):
      {
	if (jump && depth) {
	  emit_drop(depth);
	}
#	if defined(emit_cmp_i_r)
	emit_cmp_i_r((int)Object::integer(rcv.value), arg.value);
#	elif defined(emit_cmp_r_i)
	emit_cmp_r_i(arg.value, (int)Object::integer(rcv.value));
#	else
#	error: cannot find compare immediate instruction
#	endif
	break;
      }
    typeCases(Register, Integer):
      {
	if (jump && depth) {
	  emit_drop(depth);
	}
#	if defined(emit_cmp_r_i)
	emit_cmp_r_i(rcv.value, (int)Object::integer(arg.value));
#	elif defined(emit_cmp_i_r)
	emit_cmp_i_r((int)Object::integer(arg.value), rcv.value);
#	else
#	error: cannot find compare immediate instruction
#	endif
	break;
      }
    typeCases(Condition, Register):
      {
	if (jump && depth) {
	  emit_drop(depth);
	}
	switch (rcv.value)
	  {
	  case Condition::TR:
	    emit_cmp_1_r(arg.value);
	    break;
	  case Condition::FA:
	    emit_cmp_0_r(arg.value);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;
      }
    typeCases(Register, Condition):
      {
	if (jump && depth) {
	  emit_drop(depth);
	}
	switch (arg.value)
	  {
	  case Condition::TR:
	    emit_cmp_1_r(rcv.value);
	    break;
	  case Condition::FA:
	    emit_cmp_0_r(rcv.value);
	    break;
	  default:
	    fatal("this cannot happen");
	    break;
	  }
	break;
      }
    default:
      fatal("this cannot happen");
      break;
    }

  if (jump)
    {
      assert(dest != 0);
      if (jump < 0) {
	emit_bne(dest);
      } else {
	assert(jump > 0);
	emit_beq(dest);
      }
      assert(next != 0);
      if (!squash) {
	emit_jmp(next);
	push(Stack());	// next insn is dest -- gets input from stack
	assert(stackIsStable());
      }
    }
  else
    {
      int regNo= allocReg();
      insn *asm_org= asm_pc, *sett= asm_pc, *setok= asm_pc;
      for (asm_pass= 1;  asm_pass < 3;  ++asm_pass)
	{
	  asm_pc= asm_org;
	  if (invert) {
	    emit_bne(sett);
	  } else {
	    emit_beq(sett);
	  }
	  emit_move_0_r(regNo);
	  emit_jmp(setok);
	  sett= asm_pc;
	  emit_move_1_r(regNo);
	  setok= asm_pc;
	}
      asm_pass= 0;
      // pop args if necessary then push result
#if 1
      if (depth) {
	emit_drop(depth);
      }
      push(Register(regNo));
#else
      switch (depth)
	{
	case 0:  emit_push_r(tmp[0]);		break;
	case 1:  emit_move_r_S(tmp[0], 0);	break;
	case 2:  emit_move_r_popS(tmp[0], 1);	break;
	default: fatal("this cannot happen");	break;
	}
      push(Stack());
#endif
    }
  return squash;
# else
#   error: broken options
# endif
}


#ifdef OPT_FULL
#define OPT_INLINE
#endif


static Class *g_c_fetchClassOf(oop obj)
{
  if (obj->isInteger())
    return ClassSmallInteger;
  register int cc= obj->_ccBits();
  if (cc == 0)
    return obj->_classHeader();
  cc>>= 12;
  Class *cls= CompactClasses->at(cc - 1)->asClass();
  assert(cls->notNil());
  if (cc != PseudoContextCCI)
    return cls;
  assert(obj->isPseudoContext());
  if (obj->isPseudoMethodContext())
    {
      return ClassMethodContext;
    }
  else
    {
      assert(obj->isPseudoBlockContext());
      return ClassBlockContext;
    }
}


void genClass(void)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE)
  emit_call(g_Class);
  pop(1);
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  emit_move_S_r(0, reg[0]);
  emit_mkcargs1();
  emit_ccall(g_c_fetchClassOf);
  emit_killcargs1();
  emit_move_r_S(reg[0], 0);
  pop(1);
  push(Stack());
#elif defined(OPT_FULL)
  emit_move_S_r(0, tmp[0]);
  emit_tagck_r_t(tmp[0], notint);
  emit_move_v_r(specialObjectsOop, tmp[0]);
  emit_get_i_r_r(((1+5)*4), tmp[0], tmp[0]);	// ClassSmallInteger
  emit_jmp(done);

  notint= asm_pc;
  emit_get_i_r_r(0, tmp[0], tmp[1]);		// base header
  emit_bfext_i_i_r_r(12, 5, tmp[1], tmp[1]);	// cc index
  emit_beq(notcc);
  emit_move_v_r(specialObjectsOop, tmp[0]);
  emit_get_i_r_r(((1+28)*4), tmp[0], tmp[0]);	// CompactClasses
  emit_lsl_i_r(2, tmp[1]);			// idx >= 1 => +4 (header)
  emit_get_r_r_r(tmp[1], tmp[0], tmp[0]);	// CompactClasses[tmp[1]]
  emit_jmp(done);

  notcc= asm_pc;
  emit_get_i_r_r(-4, tmp[0], tmp[0]);		// class header
  emit_bfclr_i_i_r(0, 2, tmp[0]);		// clear type bits

  done= asm_pc;
  emit_move_r_S(tmp[0], 0);
  pop(1);
  push(Stack());
# else
#   error: broken options
# endif
}


void genBlockCopy(int startPC, bool blockFlag)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  genSpecial(SelectorBlockCopyIndex, 1);
}


void genLambda(size_t startPC, size_t nArgs, bool blockFlag)
{
# if defined(OPT_MACHINE) || !defined(INLINE_LAMBDA)
  preDCGflushStack();
  if (blockFlag) {
    emit_call_l(g_bLambda, (startPC << 8) | nArgs);
  } else {
    emit_call_l(g_Lambda, (startPC << 8) | nArgs);
  }
  push(Stack());
# elif defined(OPT_INLINE)
  preDCGflushStack();
  if (blockFlag) {
    emit_extern();
    emit_move_F_r(reg[0]);
    emit_move_i_r((int)Object::integer(startPC), reg[1]);
    emit_move_i_r((int)Object::integer(nArgs), reg[2]);
    emit_mkcargs3();
    emit_ccall(g_c_bLambda);
    emit_killcargs3();
    emit_push_r(reg[0]);
  } else {
    emit_extern();
    emit_move_F_r(reg[0]);
    emit_move_i_r((int)Object::integer(startPC), reg[1]);
    emit_move_i_r((int)Object::integer(nArgs), reg[2]);
    emit_mkcargs3();
    emit_ccall(g_c_Lambda);
    emit_killcargs3();
    emit_push_r(reg[0]);
  }
  push(Stack());

# else
#   error: broken options
# endif
}


void genValue(void)
{
  assert(stackDepth() > 0);
  preDCGflushStack();
  genSpecial(SelectorValueIndex, 0);
}


void genValueWithArg(void)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  genSpecial(SelectorValueWithArgIndex, 1);
}


void genDo(void)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  genSpecial(SelectorDoIndex, 1);
}


void genNew(void)
{
  assert(stackDepth() > 0);
  preDCGflushStack();
  genSpecial(SelectorNewIndex, 0);
}


void genNewWithArg(void)
{
  assert(stackDepth() > 1);
  preDCGflushStack();
  genSpecial(SelectorNewWithArgIndex, 1);
}


void genPointX(void)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE) || !defined(INLINE_XY)
  genSpecial(SelectorPointXIndex, 0);
# elif defined(OPT_INLINE)
  preDCGflushStack();
  static const int pt_x_off= memberOffset(j_Point, x);
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
    asm_pc= asm_org;
    emit_move_S_r(0, tmp[0]);
    emit_notagck_r_t(tmp[0], trap);
    emit_get_i_r_r(0, tmp[0], tmp[1]);
    emit_ccick_i_r(PointCCI, tmp[1]);
    emit_bne(trap);
    emit_get_i_r_r(pt_x_off, tmp[0], tmp[0]);
    emit_move_r_S(tmp[0], 0);
    emit_jmp(cont);
    trap= asm_pc;
    // send will be linked
    emit_call_l(g_SpecialUnlinked, ((SelectorPointXIndex << 8) | 0));
    cont= asm_pc;
  }
  asm_pass= 0;
  pop(1);
  push(Stack());
# else
#   error: broken options
# endif
}


void genPointY(void)
{
  assert(stackDepth() > 0);
# if defined(OPT_MACHINE) || !defined(INLINE_XY)
  genSpecial(SelectorPointYIndex, 0);
# elif defined(OPT_INLINE)
  preDCGflushStack();
  static const int pt_y_off= memberOffset(j_Point, y);
  insn *asm_org= asm_pc, *trap= asm_pc, *cont= asm_pc;
  for (asm_pass= 1; asm_pass < 3; ++asm_pass) {
    asm_pc= asm_org;
    emit_move_S_r(0, tmp[0]);
    emit_notagck_r_t(tmp[0], trap);
    emit_get_i_r_r(0, tmp[0], tmp[1]);
    emit_ccick_i_r(PointCCI, tmp[1]);
    emit_bne(trap);
    emit_get_i_r_r(pt_y_off, tmp[0], tmp[0]);
    emit_move_r_S(tmp[0], 0);
    emit_jmp(cont);
    trap= asm_pc;
    // send will be linked
    emit_call_l(g_SpecialUnlinked, ((SelectorPointYIndex << 8) | 0));
    cont= asm_pc;
  }
  asm_pass= 0;
  pop(1);
  push(Stack());
# else
#   error: broken options
# endif
}
