// machine.cc -- Squeak Abstract Machine, native stack frame version
//
// Author: Ian.Piumarta@INRIA.Fr
//
// Last edited: 2000-11-21 14:45:53 by piumarta on emilia.rd.wdi.disney.com

#undef	DEBUG
#undef	DO_TRACE
#undef	DO_STRACE

#include "debug.h"

#include "Object.h"
#include "NativeMethod.h"
#include "NativeProcess.h"
#include "RelinkCache.h"

#include "Frame.h"

#include "tramp.h"

#include "compile.h"
#include "machine.h"
#include "primitive.h"
#include "specials.h"

#include "generate.h"
#include "atcache.h"
#include "cachebits.h"
#include "ICLine.h"

// the following is broken since indirect inline caches
#undef	USE_RELINK_CACHE

// the following is broken since 2.8 ( why??? )
#undef	FAST_CTX_ALLOC

#define	LINKED_SUPERS
#define	LINKED_SENDS
#define	LINKED_SPECIALS

#define	INDIRECT_ICACHE	true


# define DOTRACE(ARGS) {				\
    assert(activeFrame == 0);				\
    printf(">>> %d ",					\
	   frame->nativeMethod()->includesNPC(frame->pc) ? frame->vPC() : 0);	\
    frame->nativeMethod()->print(); putchar('\n');	\
    printf("%p %p [%d] ", frame, frame->pc, frame->stackIndex());	\
    if (frame->stackIndex() > 0)			\
      frame->stackValue(0)->print();			\
    else						\
      printf("<empty>");				\
    printf("\n  ");					\
    printf ARGS;					\
    putchar('\n');					\
    fflush(stdout);					\
    assert(frame->stackIndex() <= LargeFrame); /* FIX THIS */		\
  }

#ifdef DO_TRACE
# define TRACE(ARGS)	{ /*frame->okayStackOops();*/ DOTRACE(ARGS); }
#else
# define TRACE(ARGS)	// { frame->okayOops(); }
#endif

#ifdef DO_STRACE
# define STRACE(ARGS)	{ /*frame->okayStackOops();*/ DOTRACE(ARGS); }
#else
# define STRACE(ARGS)	// { frame->okayOops(); }
#endif


#define unimplemented	fatal("unimplemented")

#define TOS()	(frame->stackValue(0))


extern int  withSpy;
extern void spyStatistics(void);
extern "C" { void checkForInterrupts(void); }

#define quickCheckForInterrupts() {		\
  if ((interruptCheckCounter -= 1) <= 0) {	\
    if (withSpy) spyStatistics();		\
    checkForInterrupts();			\
  }						\
}


int insnCount= 0;


Frame *activeFrame= 0;


unsigned siTraps= 0;
unsigned tfTraps= 0;


static insn *linkTo(Frame *frame, oop rcvr,
		    NativeMethod *nMeth, unsigned flags,
		    insn *dssIP,
		    ICLine *icl,
		    bool fromRelink)
{
  assert((flags & ~CacheMask) == 0);
  assert(sizeof(ICLine) == sizeof(insn *) + sizeof(unsigned));

  if ((!rcvr->isInteger() && rcvr->isPseudoContext()) || (!nMeth->canBeLinked()))
    {
#     if 0//ndef NDEBUG
      printf("refusing linkTo ");
      rcvr->print();
      printf(">>");
      nMeth->selector()->print();
      putchar('\n');
#     endif
      return nMeth->controlEntry;//checkedEntry;
    }

# if 0
  printf("linkTo ");
  rcvr->print();
  printf(">>");
  nMeth->selector()->print();
  putchar('\n');
  printf("FROM ");
  frame->nativeMethod()->print();
  printf("\n");
# endif

# ifdef USE_RELINK_CACHE
  if (flags == 0)
    {
      const MemoIndex rcIndex= classMemoizer->indexOf(rcvr->fetchClass());
      const MemoIndex cmIndex= nMeth->methodIndex;
      unsigned cachedArg= 0;
      NativeMethod *cachedMeth= 0;
      insn *entry= RelinkCache::lookup(rcIndex, cmIndex, cachedMeth, cachedArg);
      if (entry != 0)
	{
#	  if 0//ndef NDEBUG
	  printf("CACHED linkTo ");
	  rcvr->print();
	  printf(" ");
	  cachedMeth->print();
	  printf(" %x\n", cachedArg);
#	  endif
	  
	  assert(cachedMeth->methodIndex == cmIndex);
	  // fails if inline cache broken...
	  gen_linkTo(frame->pc, entry, cachedArg);
	  return cachedMeth->controlEntry;//checkedEntry;
	}
    }
# endif
  
  unsigned primIndex= nMeth->primitiveIndex;
  unsigned arg= flags;
  insn *icEntry= 0;

  // figure out which entry point to use

  switch (nMeth->cacheType)
    {
    case pvCacheType:
      {
	if (rcvr->isInteger()) goto doSI;
	if (rcvr->isCompact()) goto doCC;
	goto doNC;
	break;
      }
    case siCacheType: doSI:
      {
	assert(rcvr->isInteger());
	icEntry= nMeth->siEntry;
	assert(icEntry != 0);
	break;
      }
    case ccCacheType: doCC:
      {
	assert(!rcvr->isInteger());
	assert(rcvr->isCompact());
	assert(!rcvr->isPseudoContext());
#	if 0
	const unsigned ccBits= rcvr->_ccBits();
	if ((ccBits == (PseudoContextCCI << 12))
	    || (ccBits == (BlockContextCCI << 12))
	    || (ccBits == (MethodContextCCI << 12)))
	  {
	    printf("SQUASHED\n");
	    return nMeth->controlEntry;//checkedEntry;
	  }
#	endif
	arg|= rcvr->_ccBits();
	icEntry= nMeth->ccEntry;
	assert(icEntry != 0);
	break;
      }
    case ncCacheType: doNC:
      {
#       if defined(OOPS_IN_CACHE)
#       warning: NAKED OOPS IN NC CACHE
	unsigned classOop= (unsigned)rcvr->fetchClass();
	assert((classOop & SuperedBit) == 0);
	arg|= classOop;
	icEntry= nMeth->ncEntry;
#       else
	MemoIndex rcvrClassIndex;
	GC_PROTECT(frame, {
	  rcvrClassIndex= classMemoizer->indexOf(rcvr->fetchClass());	// GC!
	});
	// rcvr is TRASHED!
	// Note: we shift the index left two bits so that the major/minor
	// indices will be precisely where the inline cache wants them
	unsigned ncIndex= (rcvrClassIndex.asLong()) << 2;
	assert((ncIndex & CacheMask) == 0);
	arg|= ncIndex;
	icEntry= nMeth->ncEntry;
#       endif
	assert(icEntry != 0);
	break;
      }
    case ixCacheType:
      {
	assert(rcvr->isInteger() || !rcvr->isPseudoContext());
#	if 0
	const unsigned ccBits= rcvr->_ccBits();
	if (((ccBits == BlockContextCCI) && (nMeth->primitiveIndex != 81))
	    || (ccBits == (PseudoContextCCI << 12))
	    || (ccBits == (MethodContextCCI << 12)))
	  {
	    printf("SQUASHED\n");
	    return nMeth->controlEntry;//checkedEntry;
	  }
#	endif
	icEntry= nMeth->entry;
	assert(icEntry != 0);
	break;
      }
    default:
      fatal("this cannot happen");
      break;
    }

# if 0//ndef NDEBUG
  printf("FULL linkTo ");
  rcvr->print();
  printf(" ");
  nMeth->print();
  printf(" %x\n", arg);
  printf("FROM ");
  frame->nativeMethod()->print();
  printf("\n");
# endif

  if ((arg & DeferredBit) != 0)
    {
      if (flags & IndirectBit)
	{
	  assert(arg & IndirectBit);
	  PRINTF(("indirect relink %p = %p %x ", frame->pc, icl, arg));
	  PRINTLN(nMeth);
	  icl->setEntryArg(icEntry, arg);
	}
      else
	{
	  if (INDIRECT_ICACHE && fromRelink /*&& !(arg & SuperedBit)*/)
	    {
	      assert(!(arg & IndirectBit));
	      iclCache->reserve(sizeof(ICLine));
	      icl= (ICLine *)iclCache->allocate(sizeof(ICLine));
	      icl->setEntryArg(icEntry, arg | IndirectBit);
	      PRINTF(("indirect link %p = %p %x ", frame->pc, icl, arg));
	      PRINTLN(nMeth);
	      gen_linkDeferredTo(dssIP, g_sendLinkedIndirect, (unsigned)icl);
	    }
	  else
	    {
	      gen_linkDeferredTo(dssIP, icEntry, arg);
	    }
	}
    }
  else
    {
#     ifdef USE_RELINK_CACHE
      if (flags == 0)
	{
#	  ifndef NDEBUG
	  printf("RelinkCache add %s ",
		 classMemoizer->indexOf(rcvr->fetchClass()).printString());
	  rcvr->fetchClass()->print();
	  printf(" %s ", nMeth->methodIndex.printString());
	  nMeth->print();
	  printf(" %p %x\n", icEntry, arg);
#	  endif
	  if ((arg & CacheMask) == 0)
	    RelinkCache::add(classMemoizer->indexOf(rcvr->fetchClass()),
			     nMeth, icEntry, arg);
	}
#     endif
      if (flags & IndirectBit)
	{
	  assert(arg & IndirectBit);
	  PRINTF(("indirect relink %p = %p %x ", frame->pc, icl, arg));
	  PRINTLN(nMeth);
	  icl->setEntryArg(icEntry, arg);
	}
      else
	{
	  if (INDIRECT_ICACHE && fromRelink /*&& !(arg & SuperedBit)*/)
	    {
	      assert(!(arg & IndirectBit));
	      iclCache->reserve(sizeof(ICLine));
	      icl= (ICLine *)iclCache->allocate(sizeof(ICLine));
	      icl->setEntryArg(icEntry, arg | IndirectBit);
	      PRINTF(("indirect link %p = %p %x ", frame->pc, icl, arg));
	      PRINTLN(nMeth);
	      gen_linkTo(frame->pc, g_sendLinkedIndirect, (unsigned)icl);
	    }
	  else
	    {
	      gen_linkTo(frame->pc, icEntry, arg);
	    }
	}
    }
  return nMeth->controlEntry;//checkedEntry;
}



insn *c_relink(Frame *frame, NativeMethod *nMeth, insn *dssIP, unsigned arg, ICLine *icl)
{
  PRINTF(("c_relink: %p[%p] -> %p(%x) @ %p\n", frame->pc, dssIP, nMeth, arg, icl));

  size_t nArgs= nMeth->argumentCount;
  oop rcvr= frame->stackValue(nArgs);

# ifdef IC_STATS
  extern unsigned icMisses;
  ++icMisses;
# endif

# ifdef USE_RELINK_CACHE
  if ((arg & CacheMask) == 0)
    {
      const MemoIndex rcIndex= classMemoizer->indexOf(rcvr->fetchClass());
      const MemoIndex cmIndex= nMeth->methodIndex;
      unsigned cachedArg= 0;
      NativeMethod *destMeth= 0;
      insn *entry= RelinkCache::lookup(rcIndex, cmIndex, destMeth, cachedArg);
      if (entry != 0)
	{
#	  if 0//ndef NDEBUG
	  printf("CACHED linkTo ");
	  rcvr->print();
	  printf(" ");
	  destMeth->print();
	  printf(" %x\n", cachedArg);
#	  endif

	  assert(destMeth->methodIndex == cmIndex);
	  // fails if inline cache broken...
	  assert((destMeth != nMeth) || (arg != cachedArg));
	  gen_linkTo(frame->pc, entry, cachedArg);

	  return destMeth->controlEntry;//checkedEntry;
	}
    }
  rcvr= frame->stackValue(nArgs);	// possible GC in classMemoizer->indexOf
# endif // USE_RELINK_CACHE
  
  Symbol *selector= nMeth->selector();
  assert(selector->isSymbol());

# if 0
  if (nMeth->cacheType == ixCacheType)
    {
      assert(rcvr->fetchClass() == nMeth->receiverClass());
      // refill at cache and reset send site cache arg
      assert((arg & DeferredBit) == 0);
      assert((arg & SuperedBit) == 0);
#     ifndef NDEBUG
      AtCacheLine *acl= (AtCacheLine *)(arg & ~CacheMask);
      assert(acl >= atCache);
      assert(acl < atCache + AtCacheEntries);
      assert((acl->object != rcvr) || (rcvr->fetchClass() != nMeth->receiverClass()));
#     endif

      return linkTo(frame, rcvr, nMeth, arg & CacheMask, 0, icl, true);
    }
# endif

# if 0
  if (rcvr->isInteger())
    {
      if (nMeth->cacheType == 1)
	printf("relink after SI cache hit\n");
    }
  else
    {
      unsigned ccb= rcvr->_ccBits();
      if (ccb)
	{
	  if ((nMeth->cacheType == 2) && (ccb == (arg & ~CacheMask)))
	    printf("relink after NC cache hit\n");
	}
      else
	{
	  if (rcvr->fetchClass()
	      == classMemoizer->elementAt(MemoIndex((arg & ~CacheMask) >> 2)))
	    printf("relink after CC cache hit\n");
	}
    }
# endif

  NativeMethod *destMeth= 0;

  if ((arg & SuperedBit) != 0)		// relink super send
    {
      GC_PROTECT(frame, {
	rcvr->pushRemappable();
	destMeth= NativeMethod::find(selector, nArgs, rcvr,
				     frame->nativeMethod()->methodClass()->superclass);
	rcvr= popRemappableOop();
      });
    }
  else					// relink normal send
    {
      GC_PROTECT(frame, {
	rcvr->pushRemappable();
	destMeth= NativeMethod::find(selector, nArgs, rcvr);
	rcvr= popRemappableOop();
      });
    }

# if 0//ndef NDEBUG
  printf("relink arg == %08x for ", arg);
  (rcvr)->print();  printf(">>");  println(selector);
# endif

  return linkTo(frame, rcvr, destMeth, arg & CacheMask, dssIP, icl, true);
}


Frame *c_mxFault(Frame *returnFrame)
{
  fatal("return into MethodContext with uninitialised pc after method change");
  return 0;
}



// NOTE: the current faulting scheme WILL BREAK on any system where signals
// (or any other kind of interrupts) are taken on the process stack.  It's
// safe in Linux, and probably on any other SVr4 derivative, but this might
// break in CRUEL AND UNUSUAL WAYS under MacOS, etc...

// NOTE: we're fglue, which means we're supposed to answer the frame in
// which control resumes after handling the fault.

Frame *c_cxFault(Frame *returnFrame)
{
  PRINTF(("cx fault\n"));
  returnFrame->clearFault();

  if (returnFrame->isBaseFrame())
    {
      // should really send a "cannotReturn:" here!!!
      fatal("Process %p fell off the bottom of its stack and drowned",
	    NativeProcess::activeProcess);
    }

  Frame *faultFrame= returnFrame - 1;	// AAAARRRRRRGGGGGGGGGHHHHHHHHHHH!!!!!!!!!

  assert(faultFrame->hasPseudoContext());
  GC_PROTECT(faultFrame, {
    faultFrame->stabiliseForReturn();
  });
  return returnFrame;
}



static inline oop cxLdInst(Frame *sender, oop cx, int idx)
{
  if (cx->isMethodContext() || cx->isBlockContext())
    {
      return cx->_oops(idx);
    }
  assert(cx->isPseudoContext());
  oop field= 0;
  GC_PROTECT(sender, {
    field= cx->asPseudoContext()->frame()->instVarAt(idx);
  });
  return field;
}


static inline oop cxStInst(Frame *sender, oop cx, int idx, oop val)
{
  if (cx->isMethodContext() || cx->isBlockContext())
    {
      cx->checkStore(cx->_oops(idx)= val);
      return val;
    }
  assert(cx->isPseudoContext());
  oop field= 0;
  GC_PROTECT(sender, {
    field= cx->asPseudoContext()->frame()->instVarAtPut(idx, val);
  });
  return field;
}



void c_Pop(Frame *frame)
{
  TRACE(("Pop"));
  frame->drop(1);
}


void c_Dup(Frame *frame)
{
  TRACE(("Dup"));
  oop tos= frame->stackValue(0);
  frame->push(tos);
}


void c_LdSelf(Frame *frame)
{
  TRACE(("LdSelf"));

  frame->push(frame->receiver);
}


void c_bLdSelf(Frame *frame)
{
  TRACE(("bLdSelf"));

  oop home= frame->receiver->asContext();
  assert(home->isMethodContext() || home->isPseudoMethodContext());

  if (home->isPseudoContext())
    frame->push(home->asPseudoContext()->frame()->receiver);
  else
    frame->push(home->asMethodContext()->receiver);
}


void c_LdTrue(Frame *frame)
{
  TRACE(("LdTrue"));
  frame->push(trueObj);
}


void c_LdFalse(Frame *frame)
{
  TRACE(("LdFalse"));
  frame->push(falseObj);
}


void c_LdNil(Frame *frame)
{
  TRACE(("LdNil"));
  frame->push(nilObj);
}


void c_LdInt(Frame *frame, int val)
{
  TRACE(("LdInt %d", val));

  frame->push(Object::integer(val));
}


void c_LdThisContext(Frame *frame)
{
  GC_PROTECT(frame, {
    frame->push(frame->allocatePseudoContext());
  });

  TRACE(("LdThisContext %p", frame->pseudoContext()));

  PRINTF(("LdThisContext => "));  PRINTLN(frame->stackValue(0));
}


void c_LdLit(Frame *frame, int idx)
{
  TRACE(("LdLit %d", idx));
  oop literal= frame->nativeMethod()->literalAt(idx);
  frame->push(literal);
}


void c_bLdLit(Frame *frame, int idx)
{
  TRACE(("bLdLit %d", idx));
  oop literal= frame->nativeMethod()->literalAt(idx);
  frame->push(literal);
}


void c_LdInst(Frame *frame, int idx)
{
  TRACE(("LdInst %d", idx));
  frame->push(frame->receiver->_oops(idx));
}


void c_bLdInst(Frame *frame, int idx)
{
  TRACE(("LdInst %d", idx));
  oop home= frame->receiver;
  assert(home->isPseudoMethodContext() || home->isMethodContext());
  if (home->isPseudoContext())
    {
      assert(home->asPseudoContext()->frame()->receiver ==
	     home->asMethodContext()->receiver);
    }
  frame->push(home->asMethodContext()->receiver->_oops(idx));
}


void c_pLdInst(Frame *frame, int idx)
{
  TRACE(("pLdInst %d", idx));
  assert((!frame->hasPseudoContext()) || frame->pseudoContext()->isPseudoMethodContext());
  oop rcvr= frame->receiver;
  oop field= cxLdInst(frame, rcvr, idx);
  if (field == 0)
    fatal("pLdInst: index %d out of range", idx);
  frame->push(field);
}


void c_bpLdInst(Frame *frame, int idx)
{
  TRACE(("bpLdInst %d", idx));
  oop home= frame->receiver;
  assert(home->isPseudoMethodContext() || home->isMethodContext());
  oop cx= 0;
  if (home->isPseudoContext())
    cx= home->asPseudoContext()->frame()->receiver;
  else
    cx= home->asMethodContext()->receiver;
  assert(cx->isMethodContext() || cx->isBlockContext() || cx->isPseudoContext());
  oop field= cxLdInst(frame, cx, idx);
  if (field == 0)
    fatal("bpLdInst: index %d out of range", idx);
  frame->push(field);
}


void c_StInst(Frame *frame, int idx)
{
  TRACE(("StInst %d", idx));
  oop obj= frame->stackValue(0);
  oop rcv= frame->receiver;
  rcv->checkStore(rcv->_oops(idx)= obj);
}


void c_bStInst(Frame *frame, int idx)
{
  TRACE(("bStInst %d", idx));
  oop home= frame->receiver;
  assert(home->isPseudoMethodContext() || home->isMethodContext());
  oop rcvr= 0;
  if (home->isPseudoContext())
    rcvr= home->asPseudoContext()->frame()->receiver;
  else
    rcvr= home->asMethodContext()->receiver;
  rcvr->checkStore(rcvr->_oops(idx)= frame->stackValue(0));
}


void c_pStInst(Frame *frame, int idx)
{
  TRACE(("pStInst %d", idx));
  oop result= cxStInst(frame, frame->receiver, idx, frame->stackValue(0));
  if (result == 0)
    fatal("store of Context fixed field %d failed", idx);
}


void c_bpStInst(Frame *frame, int idx)		{ unimplemented; }


void c_PopInst(Frame *frame, int idx)
{
  TRACE(("PopInst %d", idx));
  oop obj= frame->stackValue(0);
  oop rcv= frame->receiver;
  rcv->checkStore(rcv->_oops(idx)= obj);
  frame->drop(1);
}


void c_bPopInst(Frame *frame, int idx)
{
  TRACE(("bPopInst %d", idx));
  oop home= frame->receiver;
  assert(home->isPseudoMethodContext() || home->isMethodContext());
  oop rcvr= 0;
  if (home->isPseudoContext())
    rcvr= home->asPseudoContext()->frame()->receiver;
  else
    rcvr= home->asMethodContext()->receiver;
  rcvr->checkStore(rcvr->_oops(idx)= frame->stackValue(0));
  frame->drop(1);
}


void c_pPopInst(Frame *frame, int idx)
{
  TRACE(("pPopInst %d", idx));
  oop result= cxStInst(frame, frame->receiver, idx, frame->stackValue(0));
  if (result == 0)
    fatal("store of Context fixed field %d failed", idx);
  frame->drop(1);
}


void c_bpPopInst(Frame *frame, int idx)		{ unimplemented; }


void c_LdTemp(Frame *frame, int idx)
{
  TRACE(("LdTemp %d", idx));
  frame->push(frame->tempAt(idx));
}


void c_bLdTemp(Frame *frame, int idx)
{
  TRACE(("bLdTemp %d", idx));
  
  oop home= frame->receiver;
  assert(home->isMethodContext() || home->isPseudoMethodContext());
  assert((!frame->hasPseudoContext()) || (((BlockContext *)(frame->pseudoContext()))->home) == home);

  if (home->isPseudoContext())
    {
      frame->push(home->asPseudoContext()->frame()->tempAt(idx));
    }
  else
    {
      frame->push(home->asMethodContext()->stack[idx]);
    }
}


void c_StTemp(Frame *frame, int idx)
{
  TRACE(("StTemp %d", idx));
  frame->tempAt(idx)= frame->stackValue(0);
}


void c_bStTemp(Frame *frame, int idx)
{
  TRACE(("bStTemp %d", idx));

  oop home= frame->receiver;
  assert(home->isMethodContext() || home->isPseudoMethodContext());

  if (home->isPseudoContext())
    home->asPseudoContext()->frame()->tempAt(idx)= frame->stackValue(0);
  else
    // the `home->beRoot()' in primValue.cc obviates the checkStore...
    //home->asMethodContext()->checkStore
    (home->asMethodContext()->stack[idx]= frame->stackValue(0));
}


void c_LdLitInd(Frame *frame, int idx)
{
  TRACE(("LdLitInd %d", idx));
  oop literal= frame->nativeMethod()->literalAt(idx)->asAssociation()->value;
  frame->push(literal);
}


void c_bLdLitInd(Frame *frame, int idx)
{
  TRACE(("bLdLitInd: %d", idx));
  oop literal= frame->nativeMethod()->literalAt(idx)->asAssociation()->value;
  frame->push(literal);
}


void c_StLitInd(Frame *frame, int idx)
{
  TRACE(("StLitInd %d", idx));
  Association *assoc= frame->nativeMethod()->literalAt(idx)->asAssociation();
  oop obj= frame->stackValue(0);
  assoc->checkStore(assoc->value= obj);
}
		       	
		       	
void c_bStLitInd(Frame *frame, int idx)
{
  TRACE(("bStLitInd %d", idx));
  Association *assoc= frame->nativeMethod()->literalAt(idx)->asAssociation();
  oop obj= frame->stackValue(0);
  assoc->checkStore(assoc->value= obj);
}


insn *c_Jmp(Frame *frame, insn *dest)
{			
  TRACE(("Jmp %p", dest));
  //if (dest < frame->pc)
    {
      frame->pc= dest;
      GC_PROTECT(frame, {
	quickCheckForInterrupts();
      });
    }
  return dest;		
}


// Note: this is sglue, for the benefit of non-Boolean receivers

insn *c_JmpF(Frame *frame, insn *dest)
{
  TRACE(("JmpF %p", dest));

  oop tf= frame->pop();

  if (tf == trueObj)
    return 0;

  if (tf == falseObj)
    {
      if (dest < frame->pc)
	{
	  frame->pc= dest;
	  GC_PROTECT(frame, {
	    quickCheckForInterrupts();
	  });
	}
      frame->pc= dest;
      return 0;
    }

  frame->push(tf);

  NativeMethod *nMeth= 0;
  GC_PROTECT(frame, {
    nMeth= NativeMethod::find(SelectorMustBeBoolean, 0, tf);
  });

  return nMeth->entry;
}


// Note: this is sglue, for the benefit of non-Boolean receivers

insn *c_JmpT(Frame *frame, insn *dest)
{
  TRACE(("JmpT %p", dest));

  oop tf= frame->pop();

  if (tf == falseObj)
    return 0;

  if (tf == trueObj)
    {
      if (dest < frame->pc)
	{
	  frame->pc= dest;
	  GC_PROTECT(frame, {
	    quickCheckForInterrupts();
	  });
	}
      frame->pc= dest;
      return 0;
    }

  frame->push(tf);

  NativeMethod *nMeth= 0;
  GC_PROTECT(frame, {
    nMeth= NativeMethod::find(SelectorMustBeBoolean, 0, tf);
  });

  return nMeth->entry;
}


insn *c_SuperUnlinked(Frame *frame, int selArgs)
{
  int selIdx= selArgs >> 8;
  int nArgs= selArgs & 0xff;

  STRACE(("SuperUnlinked %d %d", selIdx, nArgs));

  oop rcvr= frame->stackValue(nArgs);
  Symbol *selector= frame->nativeMethod()->literalAt(selIdx)->asSymbol();
  assert(selector->isSymbol());

  PRINTF((" ==> "));  PRINT(rcvr);  PRINTF((" "));  PRINTLN(selector);

  NativeMethod *nMeth= 0;

  GC_PROTECT(frame, {
    nMeth= NativeMethod::find(selector, nArgs, rcvr,
			      frame->nativeMethod()->methodClass()->superclass);
    // the mechanism is ugly and arbitrary
    newNativeMethod= 0;
    methodClass= nMeth->methodClass();
    gen_compile();
    assert(newNativeMethod != 0);
    nMeth= newNativeMethod;
  });

#if 0
  printf("FROM SUPER: ");
  printNameOfClasscount(lkupClass, 5);
  printf("->");
  printNameOfClasscount(receiverClass, 5);
  printf("(");
  printNameOfClasscount(methodClass, 5);
  printf(")>>");
  printStringOf(messageSelector);
  putchar('\n');
#endif

  PRINTF(("resume at %p\n", nMeth->controlEntry/*checkedEntry*/));  PRINTLN(nMeth);

# ifdef LINKED_SUPERS
  return linkTo(frame, rcvr, nMeth, SuperedBit, 0, 0, false);
# else
  PRINTF(("resume at %p\n", nMeth->controlEntry/*checkedEntry*/)); PRINTLN(nMeth);
  return nMeth->controlEntry;//checkedEntry;
# endif
}


insn *c_SendUnlinked(Frame *frame, int selArgs)
{
  int selIdx= selArgs >> 8;
  int nArgs= selArgs & 0xff;

  STRACE(("SendUnlinked %d %d", selIdx, nArgs));

  assert(frame->okayOops());

  oop rcvr= frame->stackValue(nArgs);
  Symbol *selector= frame->nativeMethod()->literalAt(selIdx)->asSymbol();
  assert(selector->isSymbol());

  PRINTF((" ==> "));  PRINT(rcvr);  PRINTF((" "));  PRINTLN(selector);

  NativeMethod *nMeth= 0;

  GC_PROTECT(frame, {
    rcvr->pushRemappable();
    nMeth= NativeMethod::find(selector, nArgs, rcvr);
    rcvr= popRemappableOop();
  });

# ifdef DO_STRACE
  nMeth->print(); printf("\n");
# endif

# ifdef LINKED_SENDS
  return linkTo(frame, rcvr, nMeth, 0, 0, 0, false);
# else
  PRINTF(("resume at %p\n", nMeth->controlEntry/*checkedEntry*/)); PRINTLN(nMeth);
  return nMeth->controlEntry;//checkedEntry;
# endif
}


insn *c_SpecialUnlinked(Frame *frame, int selArgs)
{
  int selIdx= selArgs >> 8;
  int nArgs= selArgs & 0xff;

  STRACE(("SpecialUnlinked %d %d", selIdx, nArgs));

  oop rcvr= frame->stackValue(nArgs);
  Symbol *selector= SpecialSelectors->at(selIdx * 2)->asSymbol();
  assert(selector->isSymbol());
  assert(nArgs == SpecialSelectors->at(selIdx * 2 + 1)->integerValue());

  PRINTF((" ==> "));  PRINT(rcvr);  PRINTF((" "));  PRINTLN(selector);

  NativeMethod *nMeth= 0;

  GC_PROTECT(frame, {
    rcvr->pushRemappable();
    nMeth= NativeMethod::find(selector, nArgs, rcvr);
    rcvr= popRemappableOop();
  });

# ifdef DO_STRACE
  nMeth->print(); printf("\n");
# endif

# ifdef LINKED_SPECIALS
  return linkTo(frame, rcvr, nMeth, 0, 0, 0, false);
# else
  PRINTF(("resume at %p\n", nMeth->controlEntry/*checkedEntry*/)); PRINTLN(nMeth);
  return nMeth->controlEntry;//checkedEntry;	// preactivate to run primitive or method
# endif
}


insn *c_SpecialDeferred(Frame *frame, int selArgs, insn *dssIP)
{
  int selIdx= selArgs >> 8;
  int nArgs= selArgs & 0xff;

  STRACE(("SpecialDeferred %d %d %p", selIdx, nArgs, dssIP));

  oop rcvr= frame->stackValue(nArgs);
  Symbol *selector= SpecialSelectors->at(selIdx * 2)->asSymbol();
  assert(selector->isSymbol());
  assert(nArgs == SpecialSelectors->at(selIdx * 2 + 1)->integerValue());

  PRINTF((" ==> "));  PRINT(rcvr);  PRINTF((" "));  PRINTLN(selector);

  NativeMethod *nMeth= 0;

  GC_PROTECT(frame, {
    rcvr->pushRemappable();
    nMeth= NativeMethod::find(selector, nArgs, rcvr);
    rcvr= popRemappableOop();
  });

# ifdef LINKED_SPECIALS
  return linkTo(frame, rcvr, nMeth, DeferredBit, dssIP, 0, false);
# else
  PRINTF(("resume at %p\n", nMeth->controlEntry/*checkedEntry*/)); PRINTLN(nMeth);
  return nMeth->controlEntry;//checkedEntry;	// preactivate to run primitive or method
# endif
}


// This is used only to fail out of an inlined special send.
// Must NOT try to relink the send site (since the send site isn't a send!).

static insn *c_SendSpecial(Frame *frame, int selArgs)
{
  int selIdx= selArgs >> 8;
  int nArgs= selArgs & 0xff;

  TRACE(("SpecialUnlinked %d %d", selIdx, nArgs));

  oop rcvr= frame->stackValue(nArgs);
  Symbol *selector= SpecialSelectors->at(selIdx * 2)->asSymbol();
  assert(selector->isSymbol());
  assert(nArgs == SpecialSelectors->at(selIdx * 2 + 1)->integerValue());

  PRINTF((" ==> "));  PRINT(rcvr);  PRINTF((" "));  PRINTLN(selector);

  NativeMethod *nMeth= 0;

  GC_PROTECT(frame, {
    nMeth= NativeMethod::find(selector, nArgs, rcvr);
  });

  PRINTF(("resume at %p\n", nMeth->controlEntry/*checkedEntry*/)); PRINTLN(nMeth);
  return nMeth->controlEntry;//checkedEntry;	// preactivate to run primitive or method
}


static Frame *cannotReturn(Frame *returningFrame, oop result)
{
  PRINTF(("CANNOT RETURN "));  PRINT(result);
  PRINTF((" FROM "));  PRINT(returningFrame);

  if (!returningFrame->hasPseudoContext())
    {
      GC_PROTECT(returningFrame, {
	result->pushRemappable();
	returningFrame->allocatePseudoContext();
	result= popRemappableOop();
      });
    }

  NativeMethod *nMeth= 0;
  GC_PROTECT(returningFrame, {
    result->pushRemappable();
    nMeth= NativeMethod::find(SelectorCannotReturn, 1, returningFrame->pseudoContext());
    result= popRemappableOop();
  });

  // a FAR better solution would be to compile a trivial ccall to g_cannotReturn
  // (which acts like fixed send glue with sel=#cannotReturn: nArgs=1) after
  // each return sequence (pcMap for vPC=retBytecode+1 has nPC of insn following
  // ccall NOT insn following return) and then cannotReturn could just push the
  // returning frame and result before returning back into the returning method
  // (same frame) at the insn after the failed return, thus avoiding...

  // ARRRRRRRRGGGGGGGGHHHHHHHHHHHHH!!!!!!!!!
  Frame *newFrame= returningFrame->calleeFrame();

  // newFrame->activateEmpty(/*rcvr*/returningFrame, nMeth, /*sender*/returningFrame)
  newFrame->setSenderFrame(returningFrame);
  newFrame->pc= nMeth->activatedEntry;
  newFrame->stackIndex(0);
  newFrame->receiver= returningFrame->pseudoContext();
  newFrame->setNativeMethod(nMeth);
  newFrame->pcx= 0;

  newFrame->push(result);	// 1 argument
  for (size_t i= 0; i < nMeth->temporaryCount; ++i)
    newFrame->push(nilObj);

  // this will (obviously) ALL have to CHANGE when stack grows downwards, frames
  // become variably-sized, and argument copying is eliminated!

  return newFrame;
}


Frame *c_LocalRetTop(Frame *frame)
{
  TRACE(("LocalRetTop"));

  if (frame->senderFrame()->isBaseFrame())
    {
      oop result= frame->pop();
      return cannotReturn(frame, result);
    }

  oop result= frame->stackValue(0);

  frame->senderFrame()->push(result);

# if 0 // cxFault gets this
  if (frame->hasPseudoContext())
    {
      assert(frame->senderFrame()->pc == t_cxFault);
      GC_PROTECT(frame, {
	frame->stabiliseForReturn();
      });
    }
  else
    {
      assert(frame->senderFrame()->pc != t_cxFault);
    }
# endif

  PRINTF(("resume at %p in frame %p\n", frame->senderFrame()->pc, frame->senderFrame()));
  PRINTLN(frame->senderFrame()->nativeMethod());

  GC_PROTECT(frame->senderFrame(), {
    quickCheckForInterrupts();
  });

  return frame->senderFrame();
}


Frame *c_RemoteRetTop(Frame *frame)
{
  TRACE(("RemoteRetTop"));

# ifndef BLOCK_CLOSURES
  assert(frame->hasPseudoContext());
  assert(frame->pseudoContext()->isPseudoBlockContext());
# endif

  oop home= frame->receiver;

  // Note: this is INSUFFICIENT, but consistent with the Interpreter
  if ((frame->senderFrame()->isBaseFrame())
      || (!home->isPseudoMethodContext())
      || (home->asPseudoContext()->frame()->senderFrame()->isBaseFrame()))
    {
      oop result= frame->pop();
      return cannotReturn(frame, result);
    }
  Frame *homeFrame= home->asPseudoContext()->frame();
  {
    for (Frame *f= frame; f != homeFrame; f= f->senderFrame())
      if (f->isBaseFrame())
	{
	  oop result= frame->pop();
	  return cannotReturn(frame, result);
	}
  }

  frame->stackValue(0)->pushRemappable();		// ANSWER

  {
    for (Frame *f= frame; f != homeFrame; f= f->senderFrame())
      if (f->isBaseFrame())
	fatal("broken sender chain encountered in non-local return");
  }
  {
    for (Frame *f= frame; f != homeFrame; f= f->senderFrame()) {
      PRINTF(("pop frame %p\n", f));
      if (f->hasPseudoContext())
	GC_PROTECT(f, {
	  f->stabiliseForReturn();
	});
    }
  }

  assert(homeFrame->hasPseudoContext());

# if 0 // no longer needed: cxFault triggers stabilisation
  GC_PROTECT(homeFrame, {
    homeFrame->stabiliseForReturn();
  });
# endif

  Frame *newFrame= homeFrame->senderFrame();

  assert(newFrame != 0);

  newFrame->push(popRemappableOop());

  PRINTF(("resume at %p in frame %p\n", newFrame->pc, newFrame));
  PRINTLN(newFrame->nativeMethod());

  GC_PROTECT(newFrame, {
    quickCheckForInterrupts();
  });

  return newFrame;
}


#ifdef NEW_PRIMITIVES
# define tryFloatArith(OP, RCV, ARG)	\
    successFlag= true;			\
    primitiveArguments[0]= (RCV);	\
    primitiveArguments[1]= (ARG);	\
    stackPointer= primitiveArguments+1;	\
    GC_PROTECT(frame, {			\
      OP(RCV, ARG);			\
    });					\
    if (successFlag)			\
      {					\
	frame->popThenPush(2, primitiveArguments[0]);	\
	return 0;			\
      }
#else
# define tryFloatArith(OP, RCV, ARG)	\
    successFlag= true;			\
    stackPointer= frame->stackp;	\
    GC_PROTECT(frame, {			\
      OP(RCV, ARG);			\
    });					\
    if (successFlag)			\
      {					\
	frame->stackp= stackPointer;	\
	return 0;			\
      }
#endif

#ifdef NEW_PRIMITIVES
# define tryFloatCompare(OP, RCV, ARG, T, F)	\
    successFlag= true;				\
    bool ans= OP(RCV, ARG);			\
    GC_PROTECT(frame, {				\
      OP(RCV, ARG);				\
    });						\
    if (successFlag)				\
      {						\
	frame->popThenPush(2, (ans ? T : F));	\
	return 0;				\
      }
#else
# define tryFloatCompare(OP, RCV, ARG, T, F)	\
    successFlag= true;				\
    bool ans= OP(RCV, ARG);			\
    stackPointer= frame->stackp;		\
    GC_PROTECT(frame, {				\
      OP(RCV, ARG);				\
    });						\
    if (successFlag)				\
      {						\
	frame->popThenPush(2, (ans ? T : F));	\
	return 0;				\
      }
#endif


#if 0
  // x INEQUALITY y  <=/=>  ((x*2)+1) INEQUALIITY ((y*2)+1)
  // i.e. the laws of algebra are not working today
# define INT(X)	(((int)(X)) >> 1)
#else
  // x INEQUALITY y  <===>  ((x*2)+1) INEQUALIITY ((y*2)+1)
  // i.e. we can compare SmallInteger magnitudes without removing tags
# define INT(X)	((int)(X))
#endif

extern "C" { void primitiveFloatAddtoArg(oop, oop); }

insn *c_Add(Frame *frame)
{
  TRACE(("Add"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      int result= ((int)rcv >> 1) + ((int)arg >> 1);
      if (Object::isIntegerValue(result))
	{
	  frame->popThenPush(2, Object::integer(result));
	  return 0;
	}
    }
  else
    {
      tryFloatArith(primitiveFloatAddtoArg, rcv, arg);
    }

  return c_SendSpecial(frame, ((SelectorAddIndex << 8) | 1));
}


extern "C" { void primitiveFloatSubtractfromArg(oop, oop); }

insn *c_Subtract(Frame *frame)
{
  TRACE(("Subtract"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      int result= ((int)rcv >> 1) - ((int)arg >> 1);
      if (Object::isIntegerValue(result))
	{
	  frame->popThenPush(2, Object::integer(result));
	  return 0;
	}
    }
  else
    {
      tryFloatArith(primitiveFloatSubtractfromArg, rcv, arg);
    }

  return c_SendSpecial(frame, ((SelectorSubtractIndex << 8) | 1));
}


extern "C" { bool primitiveFloatLessthanArg(oop, oop); }

insn *c_LessThan(Frame *frame)
{
  TRACE(("LessThan"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      frame->popThenPush(2, (INT(rcv) < INT(arg)) ? trueObj : falseObj);
      return 0;
    }

  tryFloatCompare(primitiveFloatLessthanArg, rcv, arg, trueObj, falseObj);

  return c_SendSpecial(frame, ((SelectorLessThanIndex << 8) | 1));
}


extern "C" { bool primitiveFloatGreaterthanArg(oop, oop); }

insn *c_GreaterThan(Frame *frame)
{
  TRACE(("GreaterThan"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      frame->popThenPush(2, (INT(rcv) > INT(arg)) ? trueObj : falseObj);
      return 0;
    }

  tryFloatCompare(primitiveFloatGreaterthanArg, rcv, arg, trueObj, falseObj);

  return c_SendSpecial(frame, ((SelectorGreaterThanIndex << 8) | 1));
}


insn *c_LessOrEqual(Frame *frame)
{
  TRACE(("LessOrEqual"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      frame->popThenPush(2, (INT(rcv) <= INT(arg)) ? trueObj : falseObj);
      return 0;
    }

  tryFloatCompare(primitiveFloatGreaterthanArg, rcv, arg, falseObj, trueObj);

  return c_SendSpecial(frame, ((SelectorLessOrEqualIndex << 8) | 1));
}


insn *c_GreaterOrEqual(Frame *frame)
{
  TRACE(("GreaterOrEqual"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      frame->popThenPush(2, (INT(rcv) >= INT(arg)) ? trueObj : falseObj);
      return 0;
    }

  tryFloatCompare(primitiveFloatLessthanArg, rcv, arg, falseObj, trueObj);

  return c_SendSpecial(frame, ((SelectorGreaterOrEqualIndex << 8) | 1));
}


extern "C" { bool primitiveFloatEqualtoArg(oop, oop); }

insn *c_Equal(Frame *frame)
{
  TRACE(("Equal"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);
  if ((int)rcv & (int)arg & 1)
    {
      frame->popThenPush(2, (rcv == arg) ? trueObj : falseObj);
      return 0;
    }

  tryFloatCompare(primitiveFloatEqualtoArg, rcv, arg, trueObj, falseObj);

  return c_SendSpecial(frame, ((SelectorEqualIndex << 8) | 1));
}


insn *c_NotEqual(Frame *frame)
{
  TRACE(("NotEqual"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);
  if ((int)rcv & (int)arg & 1)
    {
      frame->popThenPush(2, (rcv != arg) ? trueObj : falseObj);
      return 0;
    }

  tryFloatCompare(primitiveFloatEqualtoArg, rcv, arg, falseObj, trueObj);

  return c_SendSpecial(frame, ((SelectorNotEqualIndex << 8) | 1));
}


extern "C" { void primitiveFloatMultiplybyArg(oop, oop); }

insn *c_Multiply(Frame *frame)
{
  TRACE(("Multiply"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      int argInt= arg->integerValue();
      int rcvInt= rcv->integerValue();
      int result= rcvInt * argInt;
      if ((argInt == 0) || (rcvInt == result / argInt))
	if (Object::isIntegerValue(result))
	  {
	    frame->popThenPush(2, Object::integer(result));
	    return 0;
	  }
    }
  else
    {
      tryFloatArith(primitiveFloatMultiplybyArg, rcv, arg);
    }

  return c_SendSpecial(frame, ((SelectorMultiplyIndex << 8) | 1));
}


extern "C" { void primitiveFloatDividebyArg(oop, oop); }

insn *c_Divide(Frame *frame)
{
  TRACE(("Divide"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);

  if ((int)rcv & (int)arg & 1)
    {
      int argInt= arg->integerValue();
      int rcvInt= rcv->integerValue();
      if ((argInt != 0) && ((rcvInt % argInt) == 0))
	{
	  int result= rcvInt / argInt;
	  if (Object::isIntegerValue(result))
	    {
	      frame->popThenPush(2, Object::integer(result));
	      return 0;
	    }
	}
    }
  else
    {
      tryFloatArith(primitiveFloatDividebyArg, rcv, arg);
    }

  return c_SendSpecial(frame, ((SelectorDivideIndex << 8) | 1));
}


extern "C" { int doPrimitiveModby(oop, oop); }

insn *c_Mod(Frame *frame)
{
  TRACE(("Mod"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);
  successFlag= true;
  int result= doPrimitiveModby(rcv, arg);
  if (successFlag)
    {
      frame->popThenPush(2, Object::integer(result));
      return 0;
    }

  return c_SendSpecial(frame, ((SelectorModIndex << 8) | 1));
}


insn *c_MakePoint(Frame *frame)
{
  successFlag= true;
#ifdef NEW_PRIMITIVES
  primitiveArguments[0]= frame->stackValue(1);
  primitiveArguments[1]= frame->stackValue(0);
  stackPointer= primitiveArguments + 1;
#else
  stackPointer= frame->stackp;
#endif
  GC_PROTECT(frame, {
    primitiveMakePoint();
  });
  if (successFlag)
    {
#    ifdef NEW_PRIMITIVES
      frame->popThenPush(2, primitiveArguments[0]);
#    else
      frame->stackp= stackPointer;
#    endif
      return 0;
    }

  return c_SendSpecial(frame, ((SelectorMakePointIndex << 8) | 1));
}


insn *c_BitShift(Frame *frame)
{
  TRACE(("BitShift"));

  successFlag= true;
#ifdef NEW_PRIMITIVES
  primitiveArguments[0]= frame->stackValue(1);
  primitiveArguments[1]= frame->stackValue(0);
  stackPointer= primitiveArguments + 1;
#else
  stackPointer= frame->stackp;
#endif
  GC_PROTECT(frame, {
    primitiveBitShift();
  });
  if (successFlag)
    {
#    ifdef NEW_PRIMITIVES
      frame->popThenPush(2, primitiveArguments[0]);
#    else
      frame->stackp= stackPointer;
#    endif
      return 0;
    }

  return c_SendSpecial(frame, ((SelectorBitShiftIndex << 8) | 1));
}


extern "C" { int doPrimitiveDivby(oop, oop); }

insn *c_Div(Frame *frame)
{
  TRACE(("Div"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);
  successFlag= true;
  int result= doPrimitiveDivby(rcv, arg);
  if (successFlag)
    {
      frame->popThenPush(2, Object::integer(result));
      return 0;
    }
  return c_SendSpecial(frame, ((SelectorDivIndex << 8) | 1));
}


insn *c_BitAnd(Frame *frame)
{
  TRACE(("BitAnd"));

  successFlag= true;
#ifdef NEW_PRIMITIVES
  primitiveArguments[0]= frame->stackValue(1);
  primitiveArguments[1]= frame->stackValue(0);
  stackPointer= primitiveArguments + 1;
#else
  stackPointer= frame->stackp;
#endif
  GC_PROTECT(frame, {
    primitiveBitAnd();
  });
  if (successFlag)
    {
#    ifdef NEW_PRIMITIVES
      frame->popThenPush(2, primitiveArguments[0]);
#    else
      frame->stackp= stackPointer;
#    endif
      return 0;
    }

  return c_SendSpecial(frame, ((SelectorBitAndIndex << 8) | 1));
}


insn *c_BitOr(Frame *frame)
{
  TRACE(("BitOr"));

  successFlag= true;
#ifdef NEW_PRIMITIVES
  primitiveArguments[0]= frame->stackValue(1);
  primitiveArguments[1]= frame->stackValue(0);
  stackPointer= primitiveArguments + 1;
#else
  stackPointer= frame->stackp;
#endif
  GC_PROTECT(frame, {
    primitiveBitOr();
  });
  if (successFlag)
    {
#    ifdef NEW_PRIMITIVES
      frame->popThenPush(2, primitiveArguments[0]);
#    else
      frame->stackp= stackPointer;
#    endif
      return 0;
    }

  return c_SendSpecial(frame, ((SelectorBitOrIndex << 8) | 1));
}


void c_Equivalent(Frame *frame)
{
  TRACE(("Equivalent"));

  oop arg= frame->stackValue(0);
  oop rcv= frame->stackValue(1);
  frame->popThenPush(2, rcv == arg ? trueObj : falseObj);
}


void c_Class(Frame *frame)
{
  TRACE(("Class"));
  oop top= frame->stackValue(0);
  oop cls= top->fetchClass();
  if (cls == ClassPseudoContext)
    {
      if (top->isPseudoBlockContext())
	{
	  cls= ClassBlockContext;
	}
      else
	{
	  assert(top->isPseudoMethodContext());
	  cls= ClassMethodContext;
	}
    }
  frame->popThenPush(1, cls);
}


insn *c_BlockCopy(Frame *frame)
{
  oop nargs= frame->pop();
# ifndef NDEBUG
  oop activeCtx=
# endif
  frame->pop();		// WE DON'T USE THIS!

  TRACE(("BlockCopy %d", nargs->integerValue()));

  assert(activeCtx->isPseudoContext());

  //		LdThisContext	  1 byte
  //		LdLit		1/2 bytes
  //		BlockCopy	  1 byte
  // pc ----->	Jmp Dest	  2 bytes
  //		blockBody...
  int initialPC= frame->nativeMethod()->n2vPC(frame->pc) + 2;

  assert(frame->isMethodFrame());

  BlockContext *closure= 0;

  GC_PROTECT(frame, {
    frame->allocatePseudoMethodContext();
    closure= new BlockContext;
  });

  assert(closure->sender == nilObj);
  closure->pc=	    Object::integer(initialPC);
  closure->stackp=  Object::integer(0);
  closure->nargs=   nargs;
  closure->startpc= Object::integer(initialPC);
  closure->home=    frame->pseudoContext()->asMethodContext();

  frame->push(closure);

  assert(closure->home->isMethodContext() || closure->home->isPseudoMethodContext());

  PRINTF((" ==> "));  PRINTLN(closure);

  return 0;
}


insn *c_bBlockCopy(Frame *frame)
{
  oop nargs= frame->pop();
//oop activeCtx=
  frame->pop();		// we don't need this any more!

  TRACE(("bBlockCopy %d", nargs->integerValue()));

  // since we're running in a block, the receiver is the home context
  assert(frame->receiver->isMethodContext() || frame->receiver->isPseudoMethodContext());

  //		LdThisContext	  1 byte
  //		LdLit		1/2 bytes
  //		BlockCopy	  1 byte
  // pc ----->	Jmp Dest	  2 bytes
  //		blockBody...
  int initialPC= frame->nativeMethod()->n2vPC(frame->pc) + 2;

  BlockContext *closure= 0;

  GC_PROTECT(frame, {
    closure= new BlockContext;
  });

  assert(closure->sender == nilObj);
  closure->pc=	    Object::integer(initialPC);
  closure->stackp=  Object::integer(0);
  closure->nargs=   nargs;
  closure->startpc= Object::integer(initialPC);
  closure->home=    (MethodContext *)frame->receiver;	// UNSAFE CAST

  assert(closure->home->isMethodContext() || closure->home->isPseudoMethodContext());

  frame->push(closure);

  PRINTF((" ==> "));  PRINTLN(closure);

  return 0;
}


// glue version of c_Lambda that can be called from compiled code

BlockContext *g_c_Lambda(Frame *frame, oop startPC, oop nArgs)
{
  BlockContext *closure= 0;

  assert(frame->isMethodFrame());

# ifdef FAST_LAMBDA
  GC_PROTECT(frame, {
    // can't cache result because GC in BlkCtx::new trashes it
    if (!frame->hasPseudoContext())
      frame->allocatePseudoMethodContext();
    closure= BlockContext::allocateEmpty();
  });
# else
  GC_PROTECT(frame, {
    // can't cache result because GC in BlkCtx::new trashes it
    if (!frame->hasPseudoContext())
      frame->allocatePseudoMethodContext();
    closure= new BlockContext;
  });
# endif

  assert(startPC->isInteger());
  assert(nArgs->isInteger());
  // MUST fill in all fixed fields and set stackp to 0
  closure->sender=  nilObj->asContext();
  closure->pc=	    startPC;
  closure->stackp=  Object::integer(0);
  closure->nargs=   nArgs;
  closure->startpc= startPC;
  closure->home=    frame->pseudoContext()->asMethodContext();

# if defined(USE_CLOSURE_CACHE)
  closure->tempAt(ClosureCacheIndex)= (oop)frame->nativeMethod()->v2nBlockPC(startPC->integerValue());
# endif

  assert(closure->isBlockContext());
  assert(closure->okayFields());
  assert(closure->home->isMethodContext() || closure->home->isPseudoMethodContext());

  return closure;
}


void c_Lambda(Frame *frame, int startArgs)
{
  int startPC= startArgs >> 8;
  int nArgs= startArgs & 0xff;

  TRACE(("Lambda %d %d", startPC, nArgs));

  BlockContext *closure=
    g_c_Lambda(frame, Object::integer(startPC), Object::integer(nArgs));

  assert(closure->sender == nilObj);
  frame->push(closure);

  PRINTF((" [] ==> "));  PRINTLN(closure);
}


// glue version of c_bLambda that can be called from compiled code

BlockContext *g_c_bLambda(Frame *frame, oop startPC, oop nArgs)
{
  // since we're running in a block, the receiver MUST be the home context
  assert(frame->receiver->isPseudoMethodContext() || frame->receiver->isMethodContext());

  BlockContext *closure= 0;

  assert(frame->isBlockFrame());

# ifdef FAST_LAMBDA
  GC_PROTECT(frame, {
    closure= BlockContext::allocateEmpty();
  });
# else
  GC_PROTECT(frame, {
    closure= new BlockContext;
  });
# endif

  assert(startPC->isInteger());
  assert(nArgs->isInteger());

  closure->sender = nilObj->asContext();
  closure->pc=	    startPC;
  closure->stackp=  Object::integer(0);
  closure->nargs=   nArgs;
  closure->startpc= startPC;
  closure->home=    frame->receiver->asMethodContext();

# if defined(USE_CLOSURE_CACHE)
  closure->tempAt(ClosureCacheIndex)= (oop)frame->nativeMethod()->v2nBlockPC(startPC->integerValue());
# endif

  assert(closure->home->isMethodContext() || closure->home->isPseudoMethodContext());

  return closure;
}

void c_bLambda(Frame *frame, int startArgs)
{
  int startPC= startArgs >> 8;
  int nArgs= startArgs & 0xff;

  TRACE(("bLambda %d %d", startPC, nArgs));

  BlockContext *closure=
    g_c_bLambda(frame, Object::integer(startPC), Object::integer(nArgs));

  assert(closure->sender == nilObj);
  frame->push(closure);

  PRINTF((" [][] ==> "));  PRINTLN(closure);
}
