/*
  Expression Evaluator Library (NS-EEL) v2
  Copyright (C) 2004-2013 Cockos Incorporated
  Copyright (C) 1999-2003 Nullsoft, Inc.
  
  nseel-compiler.c

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/

#include "ns-eel-int.h"

#include "../denormal.h"

#include <string.h>
#include <math.h>
#include <stdio.h>
#include <ctype.h>

#include "../wdlcstring.h"

#if !defined(EEL_TARGET_PORTABLE) && !defined(_WIN32)
#include <sys/mman.h>
#include <stdint.h>
#include <unistd.h>
#endif

#ifdef __APPLE__
#include <libkern/OSCacheControl.h>
#endif

#define NSEEL_VARS_MALLOC_CHUNKSIZE 8

//#define LOG_OPT
//#define EEL_PRINT_FAILS
//#define EEL_VALIDATE_WORKTABLE_USE


#ifdef EEL_PRINT_FAILS
  #define RET_MINUS1_FAIL(x) { wdl_log("%s\n",x); return -1; }
#else
#define RET_MINUS1_FAIL(x) return -1;
#endif

#ifdef EEL_DUMP_OPS
FILE *g_eel_dump_fp, *g_eel_dump_fp2;
#endif

#ifdef EEL_VALIDATE_WORKTABLE_USE
  #define MIN_COMPUTABLE_SIZE 0
  #define COMPUTABLE_EXTRA_SPACE 64 // safety buffer, if EEL_VALIDATE_WORKTABLE_USE set, used for magic-value-checking
#else
  #define MIN_COMPUTABLE_SIZE 32 // always use at least this big of a temp storage table (and reset the temp ptr when it goes past this boundary)
  #define COMPUTABLE_EXTRA_SPACE 16 // safety buffer, if EEL_VALIDATE_WORKTABLE_USE set, used for magic-value-checking
#endif


/*
  P1 is rightmost parameter
  P2 is second rightmost, if any
  P3 is third rightmost, if any
  registers on x86 are  (RAX etc on x86-64)
    P1(ret) EAX
    P2 EDI
    P3 ECX
    WTP RSI
    x86_64: r12 is a pointer to ram_state->blocks
    x86_64: r13 is a pointer to closenessfactor

  registers on PPC are:
    P1(ret) r3
    P2 r14 
    P3 r15
    WTP r16 (r17 has the original value)
    r13 is a pointer to ram_state->blocks

    ppc uses f31 and f30 and others for certain constants

  */


#ifdef EEL_TARGET_PORTABLE

#define EEL_DOESNT_NEED_EXEC_PERMS

#ifdef EEL_PORTABLE_TAILCALL
#include "glue_port_new.h"
#else
#include "glue_port.h"
#endif

#elif defined(__ppc__)

#include "glue_ppc.h"

#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)

#include "glue_aarch64.h"

#elif defined(__arm__) || (defined (_M_ARM) && _M_ARM  == 7)

#include "glue_arm.h"

#elif defined(_WIN64) || defined(__LP64__)

#include "glue_x86_64_sse.h"

#else

#include "glue_x86.h"

#endif

#ifndef GLUE_INVSQRT_NEEDREPL
#define GLUE_INVSQRT_NEEDREPL 0
#endif


// used by //#eel-no-optimize:xxx, in ctx->optimizeDisableFlags
#define OPTFLAG_NO_OPTIMIZE 1
#define OPTFLAG_NO_FPSTACK 2
#define OPTFLAG_NO_INLINEFUNC 4
#define OPTFLAG_FULL_DENORMAL_CHECKS 8 // if set, denormals/NaN are always filtered on assign
#define OPTFLAG_NO_DENORMAL_CHECKS 16 // if set and FULL not set, denormals/NaN are never filtered on assign


#define DENORMAL_CLEARING_THRESHOLD 1.0e-50 // when adding/subtracting a constant, assume if it's greater than this, it will clear denormal (the actual value is probably 10^-290...)


#define MAX_SUB_NAMESPACES 32
typedef struct
{
  const char *namespacePathToThis;
  const char *subParmInfo[MAX_SUB_NAMESPACES];
} namespaceInformation;




static int nseel_evallib_stats[5]; // source bytes, static code bytes, call code bytes, data bytes, segments
int *NSEEL_getstats()
{
  return nseel_evallib_stats;
}

static int findLineNumber(const char *exp, int byteoffs)
{
  int lc=0;
  while (byteoffs-->0 && *exp) if (*exp++ =='\n') lc++;
  return lc;
}


static int nseel_vms_referencing_globallist_cnt;
nseel_globalVarItem *nseel_globalreg_list;
static EEL_F *get_global_var(compileContext *ctx, const char *gv, int addIfNotPresent);

static void *__newBlock_align(llBlock **start,int size, int align, int code_page_size);

#define OPCODE_IS_TRIVIAL(x) ((x)->opcodeType <= OPCODETYPE_VARPTRPTR)
enum {
  OPCODETYPE_DIRECTVALUE=0,
  OPCODETYPE_DIRECTVALUE_TEMPSTRING, // like directvalue, but will generate a new tempstring value on generate
  OPCODETYPE_VALUE_FROM_NAMESPACENAME, // this.* or namespace.* are encoded this way
  OPCODETYPE_VARPTR,
  OPCODETYPE_VARPTRPTR,
  OPCODETYPE_FUNC1,
  OPCODETYPE_FUNC2,
  OPCODETYPE_FUNC3,
  OPCODETYPE_FUNCX,

  OPCODETYPE_MOREPARAMS,

  OPCODETYPE_INVALID,
};

struct opcodeRec
{
 int opcodeType; 
 int fntype;
 void *fn;
 
 union {
   struct opcodeRec *parms[3];
   struct {
     double directValue;
     EEL_F *valuePtr; // if direct value, valuePtr can be cached
   } dv;
 } parms;
  
 int namespaceidx;
 
 // OPCODETYPE_VALUE_FROM_NAMESPACENAME (relname is either empty or blah)
 // OPCODETYPE_VARPTR if it represents a global variable, will be nonempty
 // OPCODETYPE_FUNC* with fntype=FUNCTYPE_EELFUNC
 const char *relname;
};





static opcodeRec *newOpCode(compileContext *ctx, const char *str, int opType)
{
  const size_t strszfull = str ? strlen(str) : 0;
  const size_t str_sz = wdl_min(NSEEL_MAX_VARIABLE_NAMELEN, strszfull);

  opcodeRec *rec = (opcodeRec*)__newBlock_align(ctx->isSharedFunctions ? &ctx->blocks_head_data : &ctx->tmpblocks,
                         (int) (sizeof(opcodeRec) + (str_sz>0 ? str_sz+1 : 0)),
                         8, 0);
  if (rec)
  {
    memset(rec,0,sizeof(*rec));
    rec->opcodeType = opType;

    if (str_sz > 0) 
    {
      char *p = (char *)(rec+1);
      memcpy(p,str,str_sz);
      p[str_sz]=0;

      rec->relname = p;
    }
    else
    {
      rec->relname = "";
    }
  }

  return rec;
}

#ifndef EEL_DOESNT_NEED_EXEC_PERMS
static int eel_get_page_size(void)
{
  static int pagesize;
  if (!pagesize)
  {
#ifndef _WIN32
    const int ps = (int)sysconf(_SC_PAGESIZE);
    pagesize = wdl_max(ps, 4096);
#else
    SYSTEM_INFO inf = { 0 };
    GetSystemInfo(&inf);
    pagesize = wdl_max(inf.dwPageSize, 4096);
#endif
  }
  return pagesize;
}
#endif

#define newCodeBlock(x,a) __newBlock_align(&ctx->blocks_head_code,x,a, 1)
#define newDataBlock(x,a) __newBlock_align(&ctx->blocks_head_data,x,a,0)
#define newCtxDataBlock(x,a) __newBlock_align(&ctx->ctx_pblocks,x,a,0)
#define newTmpBlock(ctx, size) __newBlock_align(&(ctx)->tmpblocks, size, 8,0)

static char *eel_get_llblock_buffer(llBlock *llb) { return (char *) (llb+1); }

#ifndef EEL_DOESNT_NEED_EXEC_PERMS
static void eel_set_blocks_allow_execute(llBlock *llb, int exec)
{
  while (llb)
  {
    const size_t sz = sizeof(*llb) + llb->sizealloc;
    #ifdef _WIN32
      DWORD ov;
      VirtualProtect(llb,sz,exec ? (PAGE_EXECUTE_READ) : (PAGE_READWRITE),&ov);
      FlushInstructionCache(GetCurrentProcess(),llb,sz);
    #else
      mprotect(llb,sz,exec ? (PROT_READ|PROT_EXEC) : (PROT_READ|PROT_WRITE));
      #ifdef __APPLE__
      if (exec) sys_icache_invalidate(llb,sz);
      #endif
    #endif
    WDL_ASSERT((((INT_PTR)llb) & (eel_get_page_size()-1)) == 0);
    WDL_ASSERT((sz & (eel_get_page_size()-1)) == 0);
    llb = llb->next;
  }
}
#endif

static void freeBlocks(llBlock **start, int is_code);

static int __growbuf_resize(eel_growbuf *buf, int newsize)
{
  if (newsize<0)
  {
    free(buf->ptr);
    buf->ptr=NULL;
    buf->alloc=buf->size=0;
    return 0;
  }

  if (newsize > buf->alloc)
  {
    const int newalloc = newsize + 4096 + newsize/2;
    void *newptr = realloc(buf->ptr,newalloc);
    if (!newptr)
    {
      newptr = malloc(newalloc);
      if (!newptr) return 1;
      if (buf->ptr && buf->size) memcpy(newptr,buf->ptr,buf->size);
      free(buf->ptr);
      buf->ptr=newptr;
    }
    else
      buf->ptr = newptr;

    buf->alloc=newalloc;
  }
  buf->size = newsize;
  return 0;
}


#ifndef DECL_ASMFUNC
#define DECL_ASMFUNC(x) void nseel_asm_##x(void); 


void _asm_megabuf(void);
void _asm_gmegabuf(void);

#endif


  DECL_ASMFUNC(booltofp)
  DECL_ASMFUNC(fptobool)
  DECL_ASMFUNC(fptobool_rev)
  DECL_ASMFUNC(sin)
  DECL_ASMFUNC(cos)
  DECL_ASMFUNC(tan)
  DECL_ASMFUNC(1pdd)
  DECL_ASMFUNC(2pdd)
  DECL_ASMFUNC(2pdds)
  DECL_ASMFUNC(1pp)
  DECL_ASMFUNC(2pp)
  DECL_ASMFUNC(sqr)
  DECL_ASMFUNC(sqrt)
  DECL_ASMFUNC(log)
  DECL_ASMFUNC(log10)
  DECL_ASMFUNC(abs)
  DECL_ASMFUNC(min)
  DECL_ASMFUNC(max)
  DECL_ASMFUNC(min_fp)
  DECL_ASMFUNC(max_fp)
  DECL_ASMFUNC(sig)
  DECL_ASMFUNC(sign)
  DECL_ASMFUNC(band)
  DECL_ASMFUNC(bor)
  DECL_ASMFUNC(bnot)
  DECL_ASMFUNC(if)
  DECL_ASMFUNC(fcall)
  DECL_ASMFUNC(repeat)
  DECL_ASMFUNC(repeatwhile)
  DECL_ASMFUNC(equal)
  DECL_ASMFUNC(equal_exact)
  DECL_ASMFUNC(notequal_exact)
  DECL_ASMFUNC(notequal)
  DECL_ASMFUNC(below)
  DECL_ASMFUNC(above)
  DECL_ASMFUNC(beloweq)
  DECL_ASMFUNC(aboveeq)
  DECL_ASMFUNC(assign)
  DECL_ASMFUNC(assign_fromfp)
  DECL_ASMFUNC(assign_fast)
  DECL_ASMFUNC(assign_fast_fromfp)
  DECL_ASMFUNC(add)
  DECL_ASMFUNC(sub)
  DECL_ASMFUNC(add_op)
  DECL_ASMFUNC(sub_op)
  DECL_ASMFUNC(add_op_fast)
  DECL_ASMFUNC(sub_op_fast)
  DECL_ASMFUNC(mul)
  DECL_ASMFUNC(div)
  DECL_ASMFUNC(mul_op)
  DECL_ASMFUNC(div_op)
  DECL_ASMFUNC(mul_op_fast)
  DECL_ASMFUNC(div_op_fast)
  DECL_ASMFUNC(mod)
  DECL_ASMFUNC(shl)
  DECL_ASMFUNC(shr)
  DECL_ASMFUNC(mod_op)
  DECL_ASMFUNC(or)
  DECL_ASMFUNC(or0)
  DECL_ASMFUNC(xor)
  DECL_ASMFUNC(xor_op)
  DECL_ASMFUNC(and)
  DECL_ASMFUNC(or_op)
  DECL_ASMFUNC(and_op)
  DECL_ASMFUNC(uminus)
  DECL_ASMFUNC(invsqrt)
  DECL_ASMFUNC(dbg_getstackptr)

  DECL_ASMFUNC(stack_push)
  DECL_ASMFUNC(stack_pop)
  DECL_ASMFUNC(stack_pop_fast) // just returns value, doesn't mod param
  DECL_ASMFUNC(stack_peek)
  DECL_ASMFUNC(stack_peek_int)
  DECL_ASMFUNC(stack_peek_top)
  DECL_ASMFUNC(stack_exch)

static void *NSEEL_PProc_GRAM(void *data, int data_size, compileContext *ctx)
{
  if (data_size>0) data=EEL_GLUE_set_immediate(data, (INT_PTR)ctx->gram_blocks);
  return data;
}

static void *NSEEL_PProc_Stack(void *data, int data_size, compileContext *ctx)
{
  codeHandleType *ch=ctx->tmpCodeHandle;

  if (data_size>0) 
  {
    UINT_PTR m1=(UINT_PTR)(NSEEL_STACK_SIZE * sizeof(EEL_F) - 1);
    UINT_PTR stackptr = ((UINT_PTR) (&ch->stack));

    ch->want_stack=1;
    if (!ch->stack) ch->stack = newDataBlock(NSEEL_STACK_SIZE*sizeof(EEL_F),NSEEL_STACK_SIZE*sizeof(EEL_F)); // stack functions need this alignment

    data=EEL_GLUE_set_immediate(data, stackptr);
    data=EEL_GLUE_set_immediate(data, m1); // and
    data=EEL_GLUE_set_immediate(data, ((UINT_PTR)ch->stack&~m1)); //or
  }
  return data;
}

static void *NSEEL_PProc_Stack_PeekInt(void *data, int data_size, compileContext *ctx, INT_PTR offs)
{
  codeHandleType *ch=ctx->tmpCodeHandle;

  if (data_size>0) 
  {
    UINT_PTR m1=(UINT_PTR)(NSEEL_STACK_SIZE * sizeof(EEL_F) - 1);
    UINT_PTR stackptr = ((UINT_PTR) (&ch->stack));

    ch->want_stack=1;
    if (!ch->stack) ch->stack = newDataBlock(NSEEL_STACK_SIZE*sizeof(EEL_F),NSEEL_STACK_SIZE*sizeof(EEL_F)); // stack functions need this alignment

    data=EEL_GLUE_set_immediate(data, stackptr);
    data=EEL_GLUE_set_immediate(data, offs);
    data=EEL_GLUE_set_immediate(data, m1); // and
    data=EEL_GLUE_set_immediate(data, ((UINT_PTR)ch->stack&~m1)); //or
  }
  return data;
}
static void *NSEEL_PProc_Stack_PeekTop(void *data, int data_size, compileContext *ctx)
{
  codeHandleType *ch=ctx->tmpCodeHandle;

  if (data_size>0) 
  {
    UINT_PTR stackptr = ((UINT_PTR) (&ch->stack));

    ch->want_stack=1;
    if (!ch->stack) ch->stack = newDataBlock(NSEEL_STACK_SIZE*sizeof(EEL_F),NSEEL_STACK_SIZE*sizeof(EEL_F)); // stack functions need this alignment

    data=EEL_GLUE_set_immediate(data, stackptr);
  }
  return data;
}

#if defined(_MSC_VER) && _MSC_VER >= 1400
static double eel__floor(double a) { return floor(a); }
static double eel__ceil(double a) { return ceil(a); }
#define floor eel__floor
#define ceil eel__ceil
#endif


#ifdef NSEEL_EEL1_COMPAT_MODE
static double eel1band(double a, double b)
{
  return (fabs(a)>NSEEL_CLOSEFACTOR && fabs(b) > NSEEL_CLOSEFACTOR) ? 1.0 : 0.0;
}
static double eel1bor(double a, double b)
{
  return (fabs(a)>NSEEL_CLOSEFACTOR || fabs(b) > NSEEL_CLOSEFACTOR) ? 1.0 : 0.0;
}

static double eel1sigmoid(double x, double constraint)
{
  double t = (1+exp(-x * (constraint)));
  return fabs(t)>NSEEL_CLOSEFACTOR ? 1.0/t : 0;
}

#endif



#define FUNCTIONTYPE_PARAMETERCOUNTMASK 0xff

#define BIF_NPARAMS_MASK       0x7ffff00
#define BIF_RETURNSONSTACK     0x0000100
#define BIF_LASTPARMONSTACK    0x0000200
#define BIF_RETURNSBOOL        0x0000400
#define BIF_LASTPARM_ASBOOL    0x0000800
//                             0x00?0000 -- taken by FP stack flags
#define BIF_TAKES_VARPARM      0x0400000
#define BIF_TAKES_VARPARM_EX   0x0C00000 // this is like varparm but check count exactly
#define BIF_WONTMAKEDENORMAL   0x0100000
#define BIF_CLEARDENORMAL      0x0200000

#if GLUE_HAS_FPREG2 > 0 && GLUE_MAX_FPSTACK_SIZE > 0
#error GLUE_HAS_FPREG2 and GLUE_MAX_FPSTACK_SIZE are exclusive
#endif

#if GLUE_MAX_SPILL_REGS > 0 && GLUE_HAS_FPREG2 <= 0
#error GLUE_MAX_SPILL_REGS requires GLUE_HAS_FPREG2
#endif

#if GLUE_MAX_FPSTACK_SIZE > 0 || GLUE_HAS_FPREG2 > 0
  #define BIF_SECONDLASTPARMST 0x0001000 // use with BIF_LASTPARMONSTACK only (last two parameters get passed on fp stack)
  #define BIF_LAZYPARMORDERING 0x0002000 // allow optimizer to avoid fxch when using BIF_TWOPARMSONFPSTACK_LAZY etc
#else
  #define BIF_SECONDLASTPARMST 0
  #define BIF_LAZYPARMORDERING 0
#endif

#if GLUE_MAX_FPSTACK_SIZE > 0
  #define BIF_REVERSEFPORDER   0x0004000 // force a fxch (reverse order of last two parameters on fp stack, used by comparison functions)
  #ifndef BIF_FPSTACKUSE
    #define BIF_FPSTACKUSE(x) (((x)>=0&&(x)<8) ? ((7-(x))<<16):0)
  #endif
  #ifndef BIF_GETFPSTACKUSE
    #define BIF_GETFPSTACKUSE(x) (7 - (((x)>>16)&7))
  #endif
#else
  #define BIF_REVERSEFPORDER   0
  #define BIF_FPSTACKUSE(x) 0
  #define BIF_GETFPSTACKUSE(x) 0
#endif

#define BIF_TWOPARMSONFPSTACK (BIF_SECONDLASTPARMST|BIF_LASTPARMONSTACK)
#define BIF_TWOPARMSONFPSTACK_LAZY (BIF_LAZYPARMORDERING|BIF_SECONDLASTPARMST|BIF_LASTPARMONSTACK)


#ifndef GLUE_HAS_NATIVE_TRIGSQRTLOG
static double sqrt_fabs(double a) { return sqrt(fabs(a)); }
#endif


EEL_F NSEEL_CGEN_CALL nseel_int_rand(EEL_F f);

#define FNPTR_HAS_CONDITIONAL_EXEC(op)  \
  (op->fntype == FN_LOGICAL_AND || \
   op->fntype == FN_LOGICAL_OR ||  \
   op->fntype == FN_IF_ELSE || \
   op->fntype == FN_WHILE || \
   op->fntype == FN_LOOP)

static functionType fnTable1[] = {
#ifndef GLUE_HAS_NATIVE_TRIGSQRTLOG
   { "sin",   nseel_asm_1pdd,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL, {&sin} },
   { "cos",    nseel_asm_1pdd,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&cos} },
   { "tan",    nseel_asm_1pdd,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&tan}  },
   { "sqrt",   nseel_asm_1pdd,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL, {&sqrt_fabs}, },
   { "log",    nseel_asm_1pdd,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&log} },
   { "log10",  nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&log10} },
#else
   { "sin",   nseel_asm_sin,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL|BIF_FPSTACKUSE(1) },
   { "cos",    nseel_asm_cos,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL|BIF_FPSTACKUSE(1) },
   { "tan",    nseel_asm_tan,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1) },
   { "sqrt",   nseel_asm_sqrt,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1)|BIF_WONTMAKEDENORMAL },
   { "log",    nseel_asm_log,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(3), },
   { "log10",  nseel_asm_log10, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(3), },
#endif


   { "asin",   nseel_asm_1pdd,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&asin}, },
   { "acos",   nseel_asm_1pdd,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&acos}, },
   { "atan",   nseel_asm_1pdd,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&atan}, },
   { "atan2",  nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK, {&atan2}, },
   { "exp",    nseel_asm_1pdd,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK, {&exp}, },
   { "abs",    nseel_asm_abs,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(0)|BIF_WONTMAKEDENORMAL },
   { "sqr",    nseel_asm_sqr,   1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1) },
   { "min",    nseel_asm_min,   2|NSEEL_NPARAMS_FLAG_CONST|BIF_FPSTACKUSE(3)|BIF_WONTMAKEDENORMAL },
   { "max",    nseel_asm_max,   2|NSEEL_NPARAMS_FLAG_CONST|BIF_FPSTACKUSE(3)|BIF_WONTMAKEDENORMAL },
   { "sign",   nseel_asm_sign,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL, },
   { "rand",   nseel_asm_1pdd,  1|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&nseel_int_rand}, },

   { "floor",  nseel_asm_1pdd, 1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&floor} },
   { "ceil",   nseel_asm_1pdd,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL, {&ceil} },

   { "invsqrt",   nseel_asm_invsqrt,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(3), {GLUE_INVSQRT_NEEDREPL} },

   { "__dbg_getstackptr",   nseel_asm_dbg_getstackptr,  1|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1),  },

#ifdef NSEEL_EEL1_COMPAT_MODE
  { "sigmoid", nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK, {&eel1sigmoid}, },

  // these differ from _and/_or, they always evaluate both...
  { "band",  nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_CLEARDENORMAL , {&eel1band}, },
  { "bor",  nseel_asm_2pdd, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_CLEARDENORMAL , {&eel1bor}, },

  {"exec2", NULL, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_WONTMAKEDENORMAL},
  {"exec3", NULL, 3|NSEEL_NPARAMS_FLAG_CONST|BIF_WONTMAKEDENORMAL},
#endif // end EEL1 compat


  {"freembuf",_asm_generic1parm,1,{&__NSEEL_RAM_MemFree},NSEEL_PProc_RAM},
  {"memcpy",_asm_generic3parm,  3,{&__NSEEL_RAM_MemCpy},NSEEL_PProc_RAM},
  {"memset",_asm_generic3parm,  3,{&__NSEEL_RAM_MemSet},NSEEL_PProc_RAM},
  {"__memtop",_asm_generic1parm,1,{&__NSEEL_RAM_MemTop},NSEEL_PProc_RAM},
  {"mem_set_values",_asm_generic2parm_retd,2|BIF_TAKES_VARPARM|BIF_RETURNSONSTACK,{&__NSEEL_RAM_Mem_SetValues},NSEEL_PProc_RAM},
  {"mem_get_values",_asm_generic2parm_retd,2|BIF_TAKES_VARPARM|BIF_RETURNSONSTACK,{&__NSEEL_RAM_Mem_GetValues},NSEEL_PProc_RAM},
  {"mem_multiply_sum",_asm_generic3parm_retd, 3|BIF_RETURNSONSTACK,{&__NSEEL_RAM_MemSumProducts},NSEEL_PProc_RAM},
  {"mem_insert_shuffle",_asm_generic3parm_retd, 3|BIF_RETURNSONSTACK, {&__NSEEL_RAM_MemInsertShuffle},NSEEL_PProc_RAM},

  {"stack_push",nseel_asm_stack_push,1|BIF_FPSTACKUSE(0),{0,},NSEEL_PProc_Stack},
  {"stack_pop",nseel_asm_stack_pop,  1|BIF_FPSTACKUSE(1),{0,},NSEEL_PProc_Stack},
  {"stack_peek",nseel_asm_stack_peek,1|NSEEL_NPARAMS_FLAG_CONST|BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(0),{0,},NSEEL_PProc_Stack},
  {"stack_exch",nseel_asm_stack_exch,1|BIF_FPSTACKUSE(1), {0,},NSEEL_PProc_Stack_PeekTop},
};
static functionType fn_min2 = { "min2",    nseel_asm_min_fp, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_WONTMAKEDENORMAL };
static functionType fn_max2 = { "max2",    nseel_asm_max_fp, 2|NSEEL_NPARAMS_FLAG_CONST|BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_WONTMAKEDENORMAL };
static functionType fn_or0  = { "or0",     nseel_asm_or0,    1|NSEEL_NPARAMS_FLAG_CONST|BIF_LASTPARMONSTACK|BIF_RETURNSONSTACK|BIF_CLEARDENORMAL };

static eel_function_table default_user_funcs;

static int functable_lowerbound(functionType *list, int list_sz, const char *name, int *ismatch)
{
  int a = 0, c = list_sz;
  while (a != c)
  {
    const int b = (a+c)/2;
    const int cmp = stricmp(name,list[b].name);
    if (cmp > 0) a = b+1;
    else if (cmp < 0) c = b;
    else
    {
      *ismatch = 1;
      return b;
    }
  }
  *ismatch = 0;
  return a;
}

static int funcTypeCmp(const void *a, const void *b) { return stricmp(((functionType*)a)->name,((functionType*)b)->name); }

functionType *nseel_getFunctionByName(compileContext *ctx, const char *name, int *mchk)
{
  eel_function_table *tab = ctx && ctx->registered_func_tab ? ctx->registered_func_tab : &default_user_funcs;
  static char sorted;
  const int fn1size = (int) (sizeof(fnTable1)/sizeof(fnTable1[0]));
  int idx,match;
  if (!sorted)
  {
    NSEEL_HOSTSTUB_EnterMutex();
    if (!sorted) qsort(fnTable1,fn1size,sizeof(fnTable1[0]),funcTypeCmp);
    sorted=1;
    NSEEL_HOSTSTUB_LeaveMutex();
  }
  idx=functable_lowerbound(fnTable1,fn1size,name,&match);
  if (match)
  {
    if (mchk) *mchk = 0;
    return fnTable1+idx;
  }

  if ((!ctx || !(ctx->current_compile_flags&NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS)) && tab->list)
  {
    idx=functable_lowerbound(tab->list,tab->list_size,name,&match);
    if (match) 
    {
      if (mchk)
      {
        while (idx>0 && !stricmp(tab->list[idx-1].name,name)) idx--;
        *mchk = tab->list_size - 1 - idx;
      }
      return tab->list + idx;
    }
  }

  return NULL;
}

functionType *nseel_enumFunctions(compileContext *ctx, int idx)
{
  eel_function_table *tab = ctx && ctx->registered_func_tab ? ctx->registered_func_tab : &default_user_funcs;
  const int fn1size = (int) (sizeof(fnTable1)/sizeof(fnTable1[0]));
  if (idx >= 0 && idx < fn1size) return fnTable1 + idx;
  if ((!ctx || !(ctx->current_compile_flags&NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS)) && tab->list)
  {
    idx -= fn1size;
    if (idx>=0 && idx < tab->list_size)
      return tab->list + idx;
  }

  return NULL;
}

int NSEEL_init() // returns 0 on success
{
  NSEEL_quit();
  return 0;
}

void NSEEL_quit()
{
  free(default_user_funcs.list);
  default_user_funcs.list = NULL;
  default_user_funcs.list_size = 0;
}

void NSEEL_addfunc_varparm_ex(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination)
{
  NSEEL_addfunctionex2(name,min_np|(want_exact?BIF_TAKES_VARPARM_EX:BIF_TAKES_VARPARM),(char *)_asm_generic2parm_retd,0,pproc,fptr,NULL,destination);
}

void NSEEL_addfunc_varparm_ctxptr(const char *name, int min_np, int want_exact, void *ctxptr, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, INT_PTR, EEL_F **), eel_function_table *destination)
{
  NSEEL_addfunctionex2(name,min_np|(want_exact?BIF_TAKES_VARPARM_EX:BIF_TAKES_VARPARM),(char *)_asm_generic2parm_retd,0,NULL,ctxptr,fptr,destination);
}

void NSEEL_addfunc_varparm_ctxptr2(const char *name, int min_np, int want_exact, NSEEL_PPPROC pproc, void *ctx, EEL_F (NSEEL_CGEN_CALL *fptr)(void *, void *, INT_PTR, EEL_F **), eel_function_table *destination)
{
  NSEEL_addfunctionex2(name,min_np|(want_exact?BIF_TAKES_VARPARM_EX:BIF_TAKES_VARPARM),(char *)_asm_generic2xparm_retd,0,pproc,ctx,fptr,destination);
}

void NSEEL_addfunc_ret_type(const char *name, int np, int ret_type,  NSEEL_PPPROC pproc, void *fptr, eel_function_table *destination) // ret_type=-1 for bool, 1 for value, 0 for ptr
{
  char *stub=NULL;
  int stubsz=0;
#define DOSTUB(np) { \
    stub = (ret_type == 1 ? (char*)_asm_generic##np##parm_retd : (char*)_asm_generic##np##parm); \
  }

  WDL_ASSERT(np >= 1 && np <= 3); // use np=1 if you want "zero" parameters

  if (np == 1) DOSTUB(1)
  else if (np == 2) DOSTUB(2)
  else if (np == 3) DOSTUB(3)
#undef DOSTUB

  if (stub) NSEEL_addfunctionex2(name,np|(ret_type == -1 ? BIF_RETURNSBOOL:0), stub, stubsz, pproc,fptr,NULL,destination);
}

void NSEEL_addfunctionex2(const char *name, int nparms, char *code_startaddr, int code_len /* ignored*/,
    NSEEL_PPPROC pproc, void *fptr, void *fptr2, eel_function_table *destination)
{
  const int list_size_chunk = 128;
  functionType *r;
  if (!destination) destination = &default_user_funcs;

  if (!destination->list || !(destination->list_size & (list_size_chunk-1)))
  {
    void *nv = realloc(destination->list, (destination->list_size + list_size_chunk)*sizeof(functionType));
    if (!nv) return;
    destination->list = (functionType *)nv;
  }
  if (destination->list)
  {
    int match,idx;

    idx=functable_lowerbound(destination->list,destination->list_size,name,&match);

    r = destination->list + idx;
    if (idx < destination->list_size)
      memmove(r + 1, r, (destination->list_size - idx) * sizeof(functionType));
    destination->list_size++;

    memset(r, 0, sizeof(functionType));

    if (!(nparms & BIF_RETURNSBOOL)) 
    {
      if (code_startaddr == (void *)&_asm_generic1parm_retd || 
          code_startaddr == (void *)&_asm_generic2parm_retd ||
          code_startaddr == (void *)&_asm_generic2xparm_retd ||
          code_startaddr == (void *)&_asm_generic3parm_retd)
      {
        nparms |= BIF_RETURNSONSTACK;
      }
    }
    r->nParams = nparms;
    r->name = name;
    r->afunc = code_startaddr;
    r->pProc = pproc;
    r->replptrs[0] = fptr;
    r->replptrs[1] = fptr2;
  }
}


//---------------------------------------------------------------------------------------------------------------
static void freeBlocks(llBlock **start, int is_code)
{
  llBlock *s=*start;
  *start=0;
  while (s)
  {
    llBlock *llB = s->next;
#ifndef EEL_DOESNT_NEED_EXEC_PERMS
    if (is_code)
    {
      #ifdef _WIN32
        VirtualFree(s, 0, MEM_RELEASE);
      #else
        munmap(s,sizeof(*s) + s->sizealloc);
      #endif
    }
    else
#endif
    {
      free(s);
    }
    s=llB;
  }
}


//---------------------------------------------------------------------------------------------------------------
static void *__newBlock_align(llBlock **start, int size, int align, int is_for_code)
{
  llBlock *llb = *start;
  int alloc_amt, align_pos, scan_cnt=8;
  if (WDL_NOT_NORMALLY(align < 1)) align = 1;

  while (llb && --scan_cnt > 0)
  {
    const int sizeused = llb->sizeused;
    if (sizeused + size <= llb->sizealloc)
    {
      align_pos = (int) (((INT_PTR)eel_get_llblock_buffer(llb) + sizeused)&(align-1));
      if (align_pos) align_pos = align - align_pos;

      if (sizeused + size + align_pos <= llb->sizealloc)
      {
        llb->sizeused += size + align_pos;
        return eel_get_llblock_buffer(llb) + sizeused + align_pos;
      }
    }
    llb = llb->next;
  }

#ifndef EEL_DOESNT_NEED_EXEC_PERMS
  if (is_for_code)
  {
    const int code_page_size = eel_get_page_size();
    alloc_amt = (sizeof(*llb) + size + code_page_size - 1) & ~(code_page_size-1);
    #ifdef _WIN32
      #ifdef _M_ARM64EC
      {
        MEM_EXTENDED_PARAMETER ext;
        memset(&ext,0,sizeof(ext));
        ext.Type = MemExtendedParameterAttributeFlags;
        ext.ULong64 = MEM_EXTENDED_PARAMETER_EC_CODE;
        llb = (llBlock *)VirtualAlloc2(NULL,NULL,alloc_amt,MEM_COMMIT,PAGE_EXECUTE_READ,&ext,1);
        if (WDL_NORMALLY(llb))
        {
          DWORD ov;
          VirtualProtect(llb, alloc_amt, PAGE_READWRITE, &ov);
        }
      }
      #else
        llb = (llBlock *)VirtualAlloc(NULL,alloc_amt,MEM_COMMIT,PAGE_READWRITE);
      #endif
      if (llb == NULL) return NULL;
    #else
      #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
      llb = (llBlock *)mmap(NULL,alloc_amt, PROT_READ|PROT_WRITE,MAP_ANON|MAP_PRIVATE,-1,0);
      #else
      llb = (llBlock *)mmap(NULL,alloc_amt, PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
      #endif
      if (llb == MAP_FAILED) return NULL;
    #endif
    alloc_amt -= sizeof(*llb);
    align_pos = 0;

    WDL_ASSERT(((INT_PTR)llb & (code_page_size - 1))==0);
    WDL_ASSERT(((INT_PTR)(eel_get_llblock_buffer(llb) + alloc_amt) & (code_page_size - 1))==0);
  }
  else
#endif
  {
    // data block, allocate in larger chunks
    alloc_amt = (size + align - 1 + 31)&~31;
    if (alloc_amt < 65536-64) alloc_amt = 65536-64;

    llb = (llBlock *)malloc(sizeof(*llb) + alloc_amt);
    if (!llb) return NULL;
    align_pos = (int) (((INT_PTR)eel_get_llblock_buffer(llb))&(align-1));
    if (align_pos) align_pos = align - align_pos;
  }

  WDL_ASSERT(size+align_pos <= alloc_amt);
  llb->sizeused = size + align_pos;
  llb->sizealloc = alloc_amt;
  llb->next = *start;
  *start = llb;
  return eel_get_llblock_buffer(llb) + align_pos;
}


//---------------------------------------------------------------------------------------------------------------
opcodeRec *nseel_createCompiledValue(compileContext *ctx, EEL_F value)
{
  opcodeRec *r=newOpCode(ctx,NULL,OPCODETYPE_DIRECTVALUE);
  if (r)
  {
    r->parms.dv.directValue = value; 
  }
  return r;
}

opcodeRec *nseel_createCompiledValuePtr(compileContext *ctx, EEL_F *addrValue, const char *namestr)
{
  opcodeRec *r=newOpCode(ctx,namestr,OPCODETYPE_VARPTR);
  if (!r) return 0;

  r->parms.dv.valuePtr=addrValue;

  return r;
}

static int validate_varname_for_function(compileContext *ctx, const char *name)
{
  if (!ctx->function_curName || !ctx->function_globalFlag) return 1;

  if (ctx->function_localTable_Size[2] > 0 && ctx->function_localTable_Names[2])
  {
    char * const * const namelist = ctx->function_localTable_Names[2];
    const int namelist_sz = ctx->function_localTable_Size[2];
    int i;
    const size_t name_len = strlen(name);

    for (i=0;i<namelist_sz;i++) 
    {
      const char *nmchk=namelist[i];
      const size_t l = strlen(nmchk);
      if (l > 1 && nmchk[l-1] == '*')
      {
        if (name_len >= l && !strnicmp(nmchk,name,l-1) && name[l-1]=='.')  return 1;
      }
      else
      {
        if (name_len == l && !stricmp(nmchk,name)) return 1;
      }
    }
  }

  return 0;
}

opcodeRec *nseel_resolve_named_symbol(compileContext *ctx, opcodeRec *rec, int parmcnt, int *errOut)
{
  const int isFunctionMode = parmcnt >= 0;
  int rel_prefix_len=0;
  int rel_prefix_idx=-2;
  int i;    
  char match_parmcnt[4]={-1,-1,-1,-1}; // [3] is guess
  unsigned char match_parmcnt_pos=0;
  char *sname = (char *)rec->relname;
  int is_string_prefix = parmcnt < 0 && sname[0] == '#';
  const char *prevent_function_calls = NULL;

  if (errOut) *errOut = 0;

  if (sname) sname += is_string_prefix;

  if (rec->opcodeType != OPCODETYPE_VARPTR || !sname || !sname[0]) return NULL;

  if (!isFunctionMode && !is_string_prefix && !strnicmp(sname,"reg",3) && isdigit_safe(sname[3]) && isdigit_safe(sname[4]) && !sname[5])
  {
    EEL_F *a=get_global_var(ctx,sname,1);
    if (a) 
    {
      rec->parms.dv.valuePtr = a;
      sname[0]=0; // for dump_ops compat really, but this shouldn't be needed anyway
    }
    return rec;
  }

  if (ctx->function_curName)
  {
    if (!strnicmp(sname,"this.",5))
    {
      rel_prefix_len=5;
      rel_prefix_idx=-1;
    } 
    else if (!stricmp(sname,"this"))
    {
      rel_prefix_len=4;
      rel_prefix_idx=-1;
    } 
  
    // scan for parameters/local variables before user functions   
    if (rel_prefix_idx < -1 &&
        ctx->function_localTable_Size[0] > 0 &&
        ctx->function_localTable_Names[0] && 
        ctx->function_localTable_ValuePtrs)
    {
      char * const * const namelist = ctx->function_localTable_Names[0];
      const int namelist_sz = ctx->function_localTable_Size[0];
      for (i=0; i < namelist_sz; i++)
      {
        const char *p = namelist[i];
        if (p)
        {
          if (!isFunctionMode && !is_string_prefix && !strnicmp(p,sname,NSEEL_MAX_VARIABLE_NAMELEN))
          {
            rec->opcodeType = OPCODETYPE_VARPTRPTR;
            rec->parms.dv.valuePtr=(EEL_F *)(ctx->function_localTable_ValuePtrs+i);
            rec->parms.dv.directValue=0.0;
            return rec;
          }
          else 
          {
            const size_t plen = strlen(p);
            if (plen > 1 && p[plen-1] == '*' && !strnicmp(p,sname,plen-1) && ((sname[plen-1] == '.'&&sname[plen]) || !sname[plen-1]))
            {
              rel_prefix_len=(int) (sname[plen-1] ? plen : plen-1);
              rel_prefix_idx=i;
              break;
            }
          }
        }
      }
    }
    // if instance name set, translate sname or sname.* into "this.sname.*"
    if (rel_prefix_idx < -1 &&
        ctx->function_localTable_Size[1] > 0 && 
        ctx->function_localTable_Names[1])
    {
      char * const * const namelist = ctx->function_localTable_Names[1];
      const int namelist_sz = ctx->function_localTable_Size[1];
      const char *full_sname = rec->relname; // include # in checks
      for (i=0; i < namelist_sz; i++)
      {
        const char *p = namelist[i];
        if (p && *p)
        {
          const size_t tl = strlen(p);     
          if (!strnicmp(p,full_sname,tl) && (full_sname[tl] == 0 || full_sname[tl] == '.'))
          {
            rel_prefix_len=0; // treat as though this. prefixes is present
            rel_prefix_idx=-1;
            break;
          }
        }
      }
    }
    if (rel_prefix_idx >= -1) 
    {
      ctx->function_usesNamespaces=1;
    }
  } // ctx->function_curName

  if (!isFunctionMode)
  {
    // instance variables
    if (rel_prefix_idx >= -1) 
    {
      rec->opcodeType = OPCODETYPE_VALUE_FROM_NAMESPACENAME;
      rec->namespaceidx = rel_prefix_idx;
      if (rel_prefix_len > 0) 
      {
        if (is_string_prefix) sname[-1] = '#';
        memmove(sname, sname+rel_prefix_len, strlen(sname + rel_prefix_len) + 1);
      }
    }
    else 
    {
      // no namespace index, so it must be a global
      if (!validate_varname_for_function(ctx,rec->relname)) 
      {
        if (errOut) *errOut = 1;
        if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string));
        snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"global '%s' inaccessible",rec->relname);
        return NULL;
      }
    }
  
    return rec;
  }

  if (ctx->func_check)
    prevent_function_calls = ctx->func_check(sname,ctx->func_check_user);
 
  ////////// function mode
  // first off, while() and loop() are special and can't be overridden
  //
  if (parmcnt == 1 && !stricmp("while",sname) && !prevent_function_calls)
  {
    rec->opcodeType = OPCODETYPE_FUNC1;
    rec->fntype = FN_WHILE;
    return rec;
  }
  if (parmcnt == 2 && !stricmp("loop",sname) && !prevent_function_calls)
  {
    rec->opcodeType = OPCODETYPE_FUNC2;
    rec->fntype = FN_LOOP;
    return rec;
  }
  
  //
  // resolve user function names before builtin functions -- this allows the user to override default functions
  if (!(ctx->current_compile_flags & NSEEL_CODE_COMPILE_FLAG_ONLY_BUILTIN_FUNCTIONS))
  {
    _codeHandleFunctionRec *best=NULL;
    size_t bestlen=0;
    const char * const ourcall = sname+rel_prefix_len;
    const size_t ourcall_len = strlen(ourcall);
    int pass;
    for (pass=0;pass<2;pass++)
    {
      _codeHandleFunctionRec *fr = pass ? ctx->functions_common : ctx->functions_local;
      // sname is [namespace.[ns.]]function, find best match of function that matches the right end   
      while (fr)
      {
        int this_np = fr->num_params;
        const char *thisfunc = fr->fname;
        const size_t thisfunc_len = strlen(thisfunc);
        if (this_np < 1) this_np=1;
        if (thisfunc_len == ourcall_len && !stricmp(thisfunc,ourcall))
        {
          if (this_np == parmcnt)
          {
            bestlen = thisfunc_len;
            best = fr;
            break; // found exact match, finished
          }
          else
          {
            if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = fr->num_params;
          }
        }

        if (thisfunc_len > bestlen && thisfunc_len < ourcall_len && ourcall[ourcall_len - thisfunc_len - 1] == '.' && !stricmp(thisfunc,ourcall + ourcall_len - thisfunc_len))
        {
          if (this_np == parmcnt) 
          {
            bestlen = thisfunc_len;
            best = fr;
          }
          else
            if (match_parmcnt[3]<0) match_parmcnt[3]=fr->num_params;
        }
        fr=fr->next;
      }
      if (fr) break; // found exact match, finished
    }
    
    if (best)
    {
      switch (parmcnt)
      {
        case 0:
        case 1: rec->opcodeType = OPCODETYPE_FUNC1; break;
        case 2: rec->opcodeType = OPCODETYPE_FUNC2; break;
        case 3: rec->opcodeType = OPCODETYPE_FUNC3; break;
        default: rec->opcodeType = OPCODETYPE_FUNCX; break;
      }
      if (ourcall != rec->relname) memmove((char *)rec->relname, ourcall, strlen(ourcall)+1);

      if (ctx->function_curName && rel_prefix_idx<0)
      {
        // if no namespace specified, and this.commonprefix.func() called, remove common prefixes and set prefixidx to be this
        const char *p=ctx->function_curName;
        if (*p) p++;
        while (*p && *p != '.')  p++;
        if (*p && p[1]) // we have a dot!
        {
          while (p[1]) p++; // go to last char of string, which doesn't allow possible trailing dot to be checked

          while (--p > ctx->function_curName) // do not check possible leading dot
          {            
            if (*p == '.')
            {
              const size_t cmplen = p+1-ctx->function_curName;
              if (!strnicmp(rec->relname,ctx->function_curName,cmplen) && rec->relname[cmplen])
              {
                const char *src=rec->relname + cmplen;
                memmove((char *)rec->relname, src, strlen(src)+1);
                rel_prefix_idx=-1; 
                ctx->function_usesNamespaces=1;
                break;
              }
            }
          }
        }
      }

      if (ctx->function_curName && rel_prefix_idx < -1 && 
          strchr(rec->relname,'.') && !validate_varname_for_function(ctx,rec->relname))
      {
        if (errOut) *errOut = 1;
        if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string));
        snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"namespaced function '%s' inaccessible",rec->relname);
        return NULL;
      }

      rec->namespaceidx = rel_prefix_idx;
      rec->fntype = FUNCTYPE_EELFUNC;
      rec->fn = best;
      return rec;
    }    
  }

  if (prevent_function_calls)
  {
    if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string));
    snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"'%.30s': %s",sname, prevent_function_calls);
    if (errOut) *errOut = 0;
    return NULL;
  }

#ifdef NSEEL_EEL1_COMPAT_MODE
    if (!stricmp(sname,"assign")) 
    {
      if (parmcnt == 2)
      {
        rec->opcodeType = OPCODETYPE_FUNC2;
        rec->fntype = FN_ASSIGN;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2;
    }
    else if (!stricmp(sname,"if")) 
    {
      if (parmcnt == 3)
      {
        rec->opcodeType = OPCODETYPE_FUNC3;
        rec->fntype = FN_IF_ELSE;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 3;
    }
    else if (!stricmp(sname,"equal")) 
    {
      if (parmcnt == 2)
      {
        rec->opcodeType = OPCODETYPE_FUNC2;
        rec->fntype = FN_EQ;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2;
    }
    else if (!stricmp(sname,"below")) 
    {
      if (parmcnt == 2)
      {
        rec->opcodeType = OPCODETYPE_FUNC2;
        rec->fntype = FN_LT;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2;
    }
    else if (!stricmp(sname,"above")) 
    {
      if (parmcnt == 2)
      {
        rec->opcodeType = OPCODETYPE_FUNC2;
        rec->fntype = FN_GT;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2;
    }
    else if (!stricmp(sname,"bnot")) 
    {
      if (parmcnt == 1)
      {
        rec->opcodeType = OPCODETYPE_FUNC1;
        rec->fntype = FN_NOT;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 1;
    }
    else if (!stricmp(sname,"megabuf")) 
    {
      if (parmcnt == 1)
      {
        rec->opcodeType = OPCODETYPE_FUNC1;
        rec->fntype = FN_MEMORY;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 1;
    }
    else if (!stricmp(sname,"gmegabuf")) 
    {
      if (parmcnt == 1)
      {
        rec->opcodeType = OPCODETYPE_FUNC1;
        rec->fntype = FN_GMEMORY;
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 1;
    }
    else
#endif
  // convert legacy pow() to FN_POW
  if (!stricmp("pow",sname))
  {
    if (parmcnt == 2)
    {
      rec->opcodeType = OPCODETYPE_FUNC2;
      rec->fntype = FN_POW;
      return rec;
    }
    if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = 2;
  }
  else if (!stricmp("__denormal_likely",sname) || !stricmp("__denormal_unlikely",sname))
  {
    if (parmcnt == 1)
    {
      rec->opcodeType = OPCODETYPE_FUNC1;
      rec->fntype = !stricmp("__denormal_likely",sname) ? FN_DENORMAL_LIKELY : FN_DENORMAL_UNLIKELY;
      return rec;
    }
  }
    
  {
    int chkamt=0;
    functionType *f=nseel_getFunctionByName(ctx,sname,&chkamt);
    if (f) while (chkamt-->=0)
    {
      const int pc_needed=(f->nParams&FUNCTIONTYPE_PARAMETERCOUNTMASK);
      if ((f->nParams&BIF_TAKES_VARPARM_EX)==BIF_TAKES_VARPARM ? (parmcnt >= pc_needed) : (parmcnt == pc_needed))
      {
        rec->fntype = FUNCTYPE_FUNCTIONTYPEREC;
        rec->fn = (void *)f;
        switch (parmcnt)
        {
          case 0:
          case 1: rec->opcodeType = OPCODETYPE_FUNC1; break;
          case 2: rec->opcodeType = OPCODETYPE_FUNC2; break;
          case 3: rec->opcodeType = OPCODETYPE_FUNC3; break;
          default: rec->opcodeType = OPCODETYPE_FUNCX; break;
        }
        return rec;
      }
      if (match_parmcnt_pos < 3) match_parmcnt[match_parmcnt_pos++] = (f->nParams&FUNCTIONTYPE_PARAMETERCOUNTMASK);
      f++;
      if (chkamt < 0 || stricmp(f->name,sname)) break;
    }
  }
  if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string));
  if (match_parmcnt[3] >= 0)
  {
    if (match_parmcnt_pos<3) match_parmcnt[match_parmcnt_pos] = match_parmcnt[3];
    match_parmcnt_pos++;
  }

  if (!match_parmcnt_pos)
    snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"'%.30s' undefined",sname);
  else
  {
    int x;
    snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"'%.30s' needs ",sname);
    for (x = 0; x < match_parmcnt_pos; x++)
      snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"%s%d",x==0?"" : x == match_parmcnt_pos-1?" or ":",",match_parmcnt[x]);
    lstrcatn(ctx->last_error_string," parms",sizeof(ctx->last_error_string));
  }
  if (errOut) *errOut = match_parmcnt_pos > 0 ? parmcnt<match_parmcnt[0]?2:(match_parmcnt[0] < 2 ? 4:1) : 0;
  return NULL;
}

opcodeRec *nseel_setCompiledFunctionCallParameters(compileContext *ctx, opcodeRec *fn, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3, opcodeRec *postCode, int *errOut)
{
  opcodeRec *r;
  int np=0,x;
  if (!fn || fn->opcodeType != OPCODETYPE_VARPTR || !fn->relname || !fn->relname[0]) 
  {
    return NULL;
  }
  fn->parms.parms[0] = code1;
  fn->parms.parms[1] = code2;
  fn->parms.parms[2] = code3;

  for (x=0;x<3;x++)
  {
    opcodeRec *prni=fn->parms.parms[x];
    while (prni && np < NSEEL_MAX_EELFUNC_PARAMETERS)
    {
      const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS;
      np++;
      if (!isMP) break;
      prni = prni->parms.parms[1];
    }
  }
  r = nseel_resolve_named_symbol(ctx, fn, np<1 ? 1 : np ,errOut);
  if (postCode && r)
  {
    if (code1 && r->opcodeType == OPCODETYPE_FUNC1 && r->fntype == FN_WHILE)
    {
      // change while(x) (postcode) to be 
      // while ((x) ? (postcode;1) : 0);
      
      r->parms.parms[0] = 
        nseel_createIfElse(ctx,r->parms.parms[0],
                               nseel_createSimpleCompiledFunction(ctx,FN_JOIN_STATEMENTS,2,postCode,nseel_createCompiledValue(ctx,1.0f)),
                               NULL); // NULL defaults to 0.0
        
    }
    else
    {
      snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"syntax error following function");
      *errOut = -1;
      return NULL;
    }
  }
  return r;
}


struct eelStringSegmentRec *nseel_createStringSegmentRec(compileContext *ctx, const char *str, int len)
{
  struct eelStringSegmentRec *r = newTmpBlock(ctx,sizeof(struct eelStringSegmentRec));
  if (r)
  {
    r->_next=0;
    r->str_start=str;
    r->str_len = len;
  }
  return r;
}

opcodeRec *nseel_eelMakeOpcodeFromStringSegments(compileContext *ctx, struct eelStringSegmentRec *rec)
{
  if (ctx && ctx->onString)
  {
    return nseel_createCompiledValue(ctx, ctx->onString(ctx->caller_this,rec));
  }

  return NULL;
}

opcodeRec *nseel_createMoreParametersOpcode(compileContext *ctx, opcodeRec *code1, opcodeRec *code2)
{
  opcodeRec *r=code1 && code2 ? newOpCode(ctx,NULL,OPCODETYPE_MOREPARAMS) : NULL;
  if (r)
  {
    r->parms.parms[0] = code1;
    r->parms.parms[1] = code2;
  }
  return r;
}


opcodeRec *nseel_createIfElse(compileContext *ctx, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3)
{
  opcodeRec *r=code1 ? newOpCode(ctx,NULL,OPCODETYPE_FUNC3) : NULL;
  if (r)
  {
    if (!code2) code2 = nseel_createCompiledValue(ctx,0.0);
    if (!code3) code3 = nseel_createCompiledValue(ctx,0.0);
    if (!code2||!code3) return NULL;

    r->fntype = FN_IF_ELSE;
    r->parms.parms[0] = code1;
    r->parms.parms[1] = code2;
    r->parms.parms[2] = code3;
  }
  return r;
}


opcodeRec *nseel_createMemoryAccess(compileContext *ctx, opcodeRec *code1, opcodeRec *code2)
{
  if (code1 && code1->opcodeType == OPCODETYPE_VARPTR && !stricmp(code1->relname,"gmem"))
  {
    return nseel_createSimpleCompiledFunction(ctx, FN_GMEMORY,1,code2?code2:nseel_createCompiledValue(ctx,0.0),0);
  }
  if (code2 && (code2->opcodeType != OPCODETYPE_DIRECTVALUE || code2->parms.dv.directValue != 0.0))
  {
    code1 = nseel_createSimpleCompiledFunction(ctx,FN_ADD,2,code1,code2);
  }
  return nseel_createSimpleCompiledFunction(ctx, FN_MEMORY,1,code1,0);
}

opcodeRec *nseel_createSimpleCompiledFunction(compileContext *ctx, int fn, int np, opcodeRec *code1, opcodeRec *code2)
{
  opcodeRec *r=code1 && (np<2 || code2) ? newOpCode(ctx,NULL,np>=2 ? OPCODETYPE_FUNC2:OPCODETYPE_FUNC1) : NULL;
  if (r)
  {
    r->fntype = fn;
    r->parms.parms[0] = code1;
    r->parms.parms[1] = code2;
    if (fn == FN_JOIN_STATEMENTS)
    {
      r->fn = r; // for joins, fn is temporarily used for tail pointers
      if (code1 && code1->opcodeType == OPCODETYPE_FUNC2 && code1->fntype == fn)
      {
        opcodeRec *t = (opcodeRec *)code1->fn;
        // keep joins in the form of dosomething->morestuff. 
        // in this instance, code1 is previous stuff to do, code2 is new stuff to do
        r->parms.parms[0] = t->parms.parms[1];

        code1->fn = (t->parms.parms[1] = r);
        return code1;
      }
    }
  }
  return r;  
}


// these are bitmasks; on request you can tell what is supported, and compileOpcodes will return one of them
#define RETURNVALUE_IGNORE 0 // ignore return value
#define RETURNVALUE_NORMAL 1 // pointer
#define RETURNVALUE_FPSTACK 2
#define RETURNVALUE_BOOL 4 // P1 is nonzero if true
#define RETURNVALUE_BOOL_REVERSED 8 // P1 is zero if true
#define RETURNVALUE_CACHEABLE 16 // only to be used when (at least) RETURNVALUE_NORMAL is set
#if GLUE_HAS_FPREG2 > 0
#define RETURNVALUE_FPREG2 32 // only usable for compileOpcodes() on a trivial opcode
#endif



static int compileOpcodes(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTable, const namespaceInformation *namespacePathToThis, 
                          int supportedReturnValues, int *rvType, int *fpStackUsage, int *canHaveDenormalOutput);


static unsigned char *compileCodeBlockWithRet(compileContext *ctx, opcodeRec *rec, int *computTableSize, const namespaceInformation *namespacePathToThis, 
                                              int supportedReturnValues, int *rvType, int *fpStackUse, int *canHaveDenormalOutput);

_codeHandleFunctionRec *eel_createFunctionNamespacedInstance(compileContext *ctx, _codeHandleFunctionRec *fr, const char *nameptr)
{
  size_t n;
  _codeHandleFunctionRec *subfr = 
    fr->isCommonFunction ? 
      ctx->isSharedFunctions ? newDataBlock(sizeof(_codeHandleFunctionRec),8) : 
      newCtxDataBlock(sizeof(_codeHandleFunctionRec),8) :  // if common function, but derived version is in non-common context, set ownership to VM rather than us
    newTmpBlock(ctx,sizeof(_codeHandleFunctionRec));

  if (!subfr) return 0;
  // fr points to functionname()'s rec, nameptr to blah.functionname()

  *subfr = *fr;
  n = strlen(nameptr);
  if (n > sizeof(subfr->fname)-1) n=sizeof(subfr->fname)-1;
  memcpy(subfr->fname,nameptr,n);
  subfr->fname[n]=0;

  subfr->next = NULL;
  subfr->startptr=0; // make sure this code gets recompiled (with correct member ptrs) for this instance!
  subfr->startptr_size=-1;

  // subfr->derivedCopies already points to the right place
  fr->derivedCopies = subfr; 
  
  return subfr;

}
static void combineNamespaceFields(char *nm, const namespaceInformation *namespaceInfo, const char *relname, int thisctx) // nm must be NSEEL_MAX_VARIABLE_NAMELEN+1 bytes
{
  const char *prefix = namespaceInfo ? 
                          thisctx<0 ? (thisctx == -1 ? namespaceInfo->namespacePathToThis : NULL) :  (thisctx < MAX_SUB_NAMESPACES ? namespaceInfo->subParmInfo[thisctx] : NULL)
                        : NULL;
  int lfp = 0, lrn=relname ? (int)strlen(relname) : 0;
  if (prefix) while (prefix[lfp] && prefix[lfp] != ':' && lfp < NSEEL_MAX_VARIABLE_NAMELEN) lfp++;
  if (!relname) relname = "";

  while (*relname == '.') // if relname begins with ., then remove a chunk of context from prefix
  {
    relname++;
    while (lfp>0 && prefix[lfp-1] != '.') lfp--;
    if (lfp>0) lfp--;       
  }

  if (lfp > NSEEL_MAX_VARIABLE_NAMELEN-3) lfp=NSEEL_MAX_VARIABLE_NAMELEN-3;
  if (lfp>0) memcpy(nm,prefix,lfp);

  if (lrn > NSEEL_MAX_VARIABLE_NAMELEN - lfp - (lfp>0)) lrn=NSEEL_MAX_VARIABLE_NAMELEN - lfp - (lfp>0);
  if (lrn > 0)
  {
    if (lfp>0) nm[lfp++] = '.';
    memcpy(nm+lfp,relname,lrn);
    lfp+=lrn;
  }
  nm[lfp++]=0;
}


//---------------------------------------------------------------------------------------------------------------
static void *nseel_getBuiltinFunctionAddress(compileContext *ctx, 
      int fntype, void *fn, 
      NSEEL_PPPROC *pProc, void ***replList, 
      int *abiInfo, int preferredReturnValues, const EEL_F *hasConstParm1, const EEL_F *hasConstParm2)
{
  const EEL_F *firstConstParm = hasConstParm1 ? hasConstParm1 : hasConstParm2;
  static void *pow_replptrs[4]={&pow,};      

  switch (fntype)
  {
#define RF(x) return (void*)nseel_asm_##x

    case FN_MUL_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(mul_op);
    case FN_DIV_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(div_op);
    case FN_OR_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(or_op);
    case FN_XOR_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(xor_op);
    case FN_AND_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(and_op);
    case FN_MOD_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(mod_op);
    case FN_ADD_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(add_op);
    case FN_SUB_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(sub_op);
    case FN_POW_OP:
      *abiInfo=BIF_LASTPARMONSTACK|BIF_CLEARDENORMAL;
      *replList = pow_replptrs;
    RF(2pdds);
    case FN_POW: 
      *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK;//BIF_FPSTACKUSE(2) might be safe, need to look at pow()'s implementation, but safer bet is to disallow fp stack caching for this expression
      *replList = pow_replptrs;
    RF(2pdd);
    case FN_ADD: 
       *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2);
        // for x +- non-denormal-constant,  we can set BIF_CLEARDENORMAL
       if (firstConstParm && fabs(*firstConstParm) > DENORMAL_CLEARING_THRESHOLD) *abiInfo |= BIF_CLEARDENORMAL;
    RF(add);
    case FN_SUB: 
       *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2);
        // for x +- non-denormal-constant,  we can set BIF_CLEARDENORMAL
       if (firstConstParm && fabs(*firstConstParm) > DENORMAL_CLEARING_THRESHOLD) *abiInfo |= BIF_CLEARDENORMAL;
    RF(sub);
    case FN_MULTIPLY: 
        *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2); 
         // for x*constant-greater-than-eq-1, we can set BIF_WONTMAKEDENORMAL
        if (firstConstParm && fabs(*firstConstParm) >= 1.0) *abiInfo |= BIF_WONTMAKEDENORMAL;
    RF(mul);
    case FN_DIVIDE: 
        *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2); 
        // for x/constant-less-than-eq-1, we can set BIF_WONTMAKEDENORMAL
        if (firstConstParm && fabs(*firstConstParm) <= 1.0) *abiInfo |= BIF_WONTMAKEDENORMAL;
    RF(div);
    case FN_MOD:
      *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL;
    RF(mod);
    case FN_ASSIGN:
      *abiInfo = BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL;
    RF(assign);
    case FN_AND: *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; RF(and);
    case FN_OR: *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL; RF(or);
    case FN_XOR:
      *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK_LAZY|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(xor);
    case FN_SHR:
      *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(shr);
    case FN_SHL:
      *abiInfo = BIF_RETURNSONSTACK|BIF_TWOPARMSONFPSTACK|BIF_FPSTACKUSE(2)|BIF_CLEARDENORMAL;
    RF(shl);
    case FN_NOTNOT: *abiInfo = BIF_LASTPARM_ASBOOL|BIF_RETURNSBOOL|BIF_FPSTACKUSE(1); break;
    case FN_UMINUS: *abiInfo = BIF_RETURNSONSTACK|BIF_LASTPARMONSTACK|BIF_WONTMAKEDENORMAL; RF(uminus);
    case FN_NOT: *abiInfo = BIF_LASTPARM_ASBOOL|BIF_RETURNSBOOL|BIF_FPSTACKUSE(1); RF(bnot);

    case FN_EQ:
      *abiInfo = BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2);
    RF(equal);
    case FN_EQ_EXACT:
      *abiInfo=BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2);
    RF(equal_exact);
    case FN_NE:
      *abiInfo=BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2);
    RF(notequal);
    case FN_NE_EXACT:
      *abiInfo=BIF_TWOPARMSONFPSTACK_LAZY|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2);
    RF(notequal_exact);
    case FN_LOGICAL_AND:
      *abiInfo = BIF_RETURNSBOOL;
    RF(band);
    case FN_LOGICAL_OR:
      *abiInfo = BIF_RETURNSBOOL;
    RF(bor);

    case FN_GT:
      *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2);
    RF(above);
    case FN_GTE:
      *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_REVERSEFPORDER|BIF_FPSTACKUSE(2);
#if BIF_REVERSEFPORDER > 0
    RF(beloweq);
#else
    RF(aboveeq);
#endif
    case FN_LT:
      *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_REVERSEFPORDER|BIF_FPSTACKUSE(2);
#if BIF_REVERSEFPORDER > 0
    RF(above);
#else
    RF(below);
#endif
    case FN_LTE:
      *abiInfo = BIF_TWOPARMSONFPSTACK|BIF_RETURNSBOOL|BIF_FPSTACKUSE(2);
    RF(beloweq);

#undef RF
#define RF(x) return (void*)_asm_##x

    case FN_MEMORY:
      {
        static void *replptrs[4]={&__NSEEL_RAMAlloc,};      
        *replList = replptrs;
        *abiInfo = BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL;
        #ifdef GLUE_MEM_NEEDS_PPROC
          *pProc = NSEEL_PProc_RAM;
        #endif
        RF(megabuf);
      }
    break;
    case FN_GMEMORY:
      {
        static void *replptrs[4]={&__NSEEL_RAMAllocGMEM,};      
        *replList = replptrs;
        *abiInfo=BIF_LASTPARMONSTACK|BIF_FPSTACKUSE(1)|BIF_CLEARDENORMAL;
        *pProc=NSEEL_PProc_GRAM;
        RF(gmegabuf);
      }
    break;
#undef RF

    case FUNCTYPE_FUNCTIONTYPEREC:
      if (fn)
      {
        functionType *p=(functionType *)fn;

        // if prefers fpstack or bool, or ignoring value, then use fp-stack versions
        if ((preferredReturnValues&(RETURNVALUE_BOOL|RETURNVALUE_FPSTACK)) || !preferredReturnValues)
        {
          if (p->afunc == (void*)nseel_asm_min) p = &fn_min2;
          else if (p->afunc == (void*)nseel_asm_max) p = &fn_max2;
        }

        *replList=p->replptrs;
        *pProc=p->pProc;
        *abiInfo = p->nParams & BIF_NPARAMS_MASK;
        if (firstConstParm)
        {
          const char *name=p->name;
          if (!strcmp(name,"min") && *firstConstParm < -1.0e-10) *abiInfo |= BIF_CLEARDENORMAL;
          else if (!strcmp(name,"max") && *firstConstParm > 1.0e-10) *abiInfo |= BIF_CLEARDENORMAL;
        }
        return p->afunc; 
      }
    break;
  }
  
  return NULL;
}



static void *nseel_getEELFunctionAddress(compileContext *ctx, 
      opcodeRec *op,
      int *customFuncParmSize, int *customFuncLocalStorageSize,
      EEL_F ***customFuncLocalStorage, int *computTableTop, 
      void **endP, int *isRaw, int wantCodeGenerated,
      const namespaceInformation *namespacePathToThis, int *rvMode, int *fpStackUse, int *canHaveDenormalOutput,
      opcodeRec **ordered_parmptrs, int num_ordered_parmptrs      
      ) // if wantCodeGenerated is false, can return bogus pointers in raw mode
{
  _codeHandleFunctionRec *fn = (_codeHandleFunctionRec*)op->fn;

  namespaceInformation local_namespace={NULL};
  char prefix_buf[NSEEL_MAX_VARIABLE_NAMELEN+1], nm[NSEEL_MAX_FUNCSIG_NAME+1];
  if (!fn) return NULL;

  // op->relname ptr is [whatever.]funcname
  if (fn->parameterAsNamespaceMask || fn->usesNamespaces)
  {
    if (wantCodeGenerated)
    {
      char *p = prefix_buf;
      combineNamespaceFields(nm,namespacePathToThis,op->relname,op->namespaceidx);
      lstrcpyn_safe(prefix_buf,nm,sizeof(prefix_buf));
      local_namespace.namespacePathToThis = prefix_buf;
      // nm is full path of function, prefix_buf will be the path not including function name (unless function name only)
      while (*p) p++;
      while (p >= prefix_buf && *p != '.') p--;
      if (p > prefix_buf) *p=0;
    }
    if (fn->parameterAsNamespaceMask)
    {
      int x;
      for(x=0;x<MAX_SUB_NAMESPACES && x < fn->num_params;x++)
      {
        if (fn->parameterAsNamespaceMask & (((unsigned int)1)<<x))
        {
          if (wantCodeGenerated)
          {
            const char *rn=NULL;
            char tmp[NSEEL_MAX_VARIABLE_NAMELEN+1];
            if (x < num_ordered_parmptrs && ordered_parmptrs[x]) 
            {
              if (ordered_parmptrs[x]->opcodeType == OPCODETYPE_VARPTR) 
              {
                rn=ordered_parmptrs[x]->relname;
              }
              else if (ordered_parmptrs[x]->opcodeType == OPCODETYPE_VALUE_FROM_NAMESPACENAME)
              {
                const char *p=ordered_parmptrs[x]->relname;
                if (*p == '#') p++;
                combineNamespaceFields(tmp,namespacePathToThis,p,ordered_parmptrs[x]->namespaceidx);
                rn = tmp;
              }
            }
          
            if (!rn) 
            {
              // todo: figure out how to give correct line number/offset (ugh)
              snprintf(ctx->last_error_string,sizeof(ctx->last_error_string),"parameter %d to %.120s() must be namespace",x+1,fn->fname);
              return NULL;
            }

            lstrcatn(nm,":",sizeof(nm));

            local_namespace.subParmInfo[x] = nm+strlen(nm);
            lstrcatn(nm,rn,sizeof(nm));
          }         
          ordered_parmptrs[x] = NULL; // prevent caller from bothering generating parameters
        }
      }
    }
    if (wantCodeGenerated)
    {
      _codeHandleFunctionRec *fr = fn;
      // find namespace-adjusted function (if generating code, otherwise assume size is the same)
      fn = 0; // if this gets re-set, it will be the new function
      while (fr && !fn)
      {
        if (!stricmp(fr->fname,nm)) fn = fr;
        fr=fr->derivedCopies;
      }
      if (!fn) // generate copy of function
      {
        fn = eel_createFunctionNamespacedInstance(ctx,(_codeHandleFunctionRec*)op->fn,nm);
      }
    }
  }
  if (!fn) return NULL;

  if (!fn->startptr && fn->opcodes && fn->startptr_size != 0)
  {
    int sz = fn->startptr_size;

    if (sz < 0) 
    {
      fn->tmpspace_req=0;
      fn->rvMode = RETURNVALUE_IGNORE;
      fn->canHaveDenormalOutput=0;

      sz = compileOpcodes(ctx,fn->opcodes,NULL,128*1024*1024,&fn->tmpspace_req,
          wantCodeGenerated ? &local_namespace : NULL,RETURNVALUE_FPSTACK,
          &fn->rvMode,&fn->fpStackUsage,&fn->canHaveDenormalOutput);
      if (sz<0) return NULL;

      fn->startptr_base_size = fn->startptr_size = sz;
    }

    if (!wantCodeGenerated)
    {
      // don't compile anything for now, just give stats
      if (computTableTop) *computTableTop += fn->tmpspace_req;
      *customFuncParmSize = fn->num_params;
      *customFuncLocalStorage = fn->localstorage;
      *customFuncLocalStorageSize = fn->localstorage_size;
      *rvMode = fn->rvMode;
      *fpStackUse = fn->fpStackUsage;
      if (canHaveDenormalOutput) *canHaveDenormalOutput=fn->canHaveDenormalOutput;

      if (fn->startptr_base_size <= NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE &&
          !(ctx->optimizeDisableFlags&OPTFLAG_NO_INLINEFUNC))
      {
        *isRaw = 1;
        *endP = ((char *)1) + fn->startptr_base_size;
        return (char *)1;
      }
      return (void*)nseel_asm_fcall;
    }

    if (fn->startptr_base_size <= NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE &&
        !(ctx->optimizeDisableFlags&OPTFLAG_NO_INLINEFUNC))
    {
      void *p=newTmpBlock(ctx,sz);
      fn->tmpspace_req=0;
      if (p)
      {
        fn->canHaveDenormalOutput=0;
        if (fn->isCommonFunction) ctx->isGeneratingCommonFunction++;
        sz=compileOpcodes(ctx,fn->opcodes,(unsigned char*)p,sz,&fn->tmpspace_req,&local_namespace,RETURNVALUE_FPSTACK,&fn->rvMode,&fn->fpStackUsage,&fn->canHaveDenormalOutput);
        if (fn->isCommonFunction) ctx->isGeneratingCommonFunction--;
        // recompile function with native context pointers
        if (sz>0)
        {
          fn->startptr_size=sz;
          fn->startptr=p;
        }
      }
    }
    else
    {
      unsigned char *codeCall;
      fn->tmpspace_req=0;
      fn->fpStackUsage=0;
      fn->canHaveDenormalOutput=0;
      if (fn->isCommonFunction) ctx->isGeneratingCommonFunction++;
      codeCall=compileCodeBlockWithRet(ctx,fn->opcodes,&fn->tmpspace_req,&local_namespace,RETURNVALUE_FPSTACK,&fn->rvMode,&fn->fpStackUsage,&fn->canHaveDenormalOutput);
      if (fn->isCommonFunction) ctx->isGeneratingCommonFunction--;
      if (codeCall)
      {
        void *f=GLUE_realAddress(nseel_asm_fcall,&sz);
        fn->startptr = newTmpBlock(ctx,sz);
        if (fn->startptr)
        {
          memcpy(fn->startptr,f,sz);
          EEL_GLUE_set_immediate(fn->startptr,(INT_PTR)codeCall);
          fn->startptr_size = sz;
        }
      }
    }
  }

  if (fn->startptr)
  {
    if (computTableTop) *computTableTop += fn->tmpspace_req;
    *customFuncParmSize = fn->num_params;
    *customFuncLocalStorage = fn->localstorage;
    *customFuncLocalStorageSize = fn->localstorage_size;
    *rvMode = fn->rvMode;
    *fpStackUse = fn->fpStackUsage;
    if (canHaveDenormalOutput) *canHaveDenormalOutput= fn->canHaveDenormalOutput;
    *endP = (char*)fn->startptr + fn->startptr_size;
    if (!wantCodeGenerated &&
        fn->startptr_base_size <= NSEEL_MAX_FUNCTION_SIZE_FOR_INLINE &&
        !(ctx->optimizeDisableFlags&OPTFLAG_NO_INLINEFUNC))
    {
      // report the correct maximum base length for the calculation pass
      *endP = (char*)fn->startptr + fn->startptr_base_size;
    }
    *isRaw=1;
    return fn->startptr;
  }
  
  return 0;
}



// returns true if does something (other than calculating and throwing away a value)
static char optimizeOpcodes(compileContext *ctx, opcodeRec *op, int needsResult)
{
  opcodeRec *lastJoinOp=NULL;
  char retv, retv_parm[3], joined_retv=0;
  while (op && op->opcodeType == OPCODETYPE_FUNC2 && op->fntype == FN_JOIN_STATEMENTS)
  {
    if (!optimizeOpcodes(ctx,op->parms.parms[0], 0) || OPCODE_IS_TRIVIAL(op->parms.parms[0]))
    {
      // direct value, can skip ourselves
      memcpy(op,op->parms.parms[1],sizeof(*op));
    }
    else
    {
      joined_retv |= 1;
      lastJoinOp = op;
      op = op->parms.parms[1];
    }
  }
goto start_over;

#define RESTART_DIRECTVALUE(X) { op->parms.dv.directValue = (X); goto start_over_directvalue; }
start_over_directvalue:
  op->opcodeType = OPCODETYPE_DIRECTVALUE;
  op->parms.dv.valuePtr=NULL;
  
start_over: // when an opcode changed substantially in optimization, goto here to reprocess it

  retv = retv_parm[0]=retv_parm[1]=retv_parm[2]=0;

  if (!op || // should never really happen
      OPCODE_IS_TRIVIAL(op) || // should happen often (vars)
      op->opcodeType < 0 || op->opcodeType >= OPCODETYPE_INVALID // should never happen (assert would be appropriate heh)
      ) return joined_retv;
  
  if (!needsResult)
  {
    if (op->fntype == FUNCTYPE_EELFUNC) 
    {
      needsResult=1; // assume eel functions are non-const for now
    }
    else if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC)
    {
      functionType  *pfn = (functionType *)op->fn;
      if (!pfn || !(pfn->nParams&NSEEL_NPARAMS_FLAG_CONST)) needsResult=1;
    }
    else if (op->fntype >= FN_NONCONST_BEGIN && op->fntype < FUNCTYPE_SIMPLEMAX)
    {
      needsResult=1;
    }
  }

  if (op->opcodeType>=OPCODETYPE_FUNC2) retv_parm[1] = optimizeOpcodes(ctx,op->parms.parms[1], needsResult);
  if (op->opcodeType>=OPCODETYPE_FUNC3) retv_parm[2] = optimizeOpcodes(ctx,op->parms.parms[2], needsResult);

  retv_parm[0] = optimizeOpcodes(ctx,op->parms.parms[0], needsResult || 
      (FNPTR_HAS_CONDITIONAL_EXEC(op) && (retv_parm[1] || retv_parm[2] || op->opcodeType <= OPCODETYPE_FUNC1)) );

  if (op->opcodeType != OPCODETYPE_MOREPARAMS)
  {
    if (op->fntype >= 0 && op->fntype < FUNCTYPE_SIMPLEMAX)
    {
      if (op->opcodeType == OPCODETYPE_FUNC1) // within FUNCTYPE_SIMPLE
      {
        if (op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE)
        {
          switch (op->fntype)
          {
            case FN_NOTNOT: RESTART_DIRECTVALUE(fabs(op->parms.parms[0]->parms.dv.directValue)>=NSEEL_CLOSEFACTOR ? 1.0 : 0.0);
            case FN_NOT:    RESTART_DIRECTVALUE(fabs(op->parms.parms[0]->parms.dv.directValue)>=NSEEL_CLOSEFACTOR ? 0.0 : 1.0);
            case FN_UMINUS: RESTART_DIRECTVALUE(- op->parms.parms[0]->parms.dv.directValue);
          }
        }
        else if (op->fntype == FN_NOT || op->fntype == FN_NOTNOT)
        {
          if (op->parms.parms[0]->opcodeType == OPCODETYPE_FUNC1)
          {
            switch (op->parms.parms[0]->fntype)
            {
              case FN_UMINUS:
              case FN_NOTNOT: // ignore any NOTNOTs UMINUS or UPLUS, they would have no effect anyway
                op->parms.parms[0] = op->parms.parms[0]->parms.parms[0];
              goto start_over;

              case FN_NOT:
                op->fntype = op->fntype==FN_NOT ? FN_NOTNOT : FN_NOT; // switch between FN_NOT and FN_NOTNOT
                op->parms.parms[0] = op->parms.parms[0]->parms.parms[0];
              goto start_over;
            }
          }
          else if (op->parms.parms[0]->opcodeType == OPCODETYPE_FUNC2)
          {
            int repl_type = -1;
            switch (op->parms.parms[0]->fntype)
            {
              case FN_EQ: repl_type = FN_NE; break;
              case FN_NE: repl_type = FN_EQ; break;
              case FN_EQ_EXACT: repl_type = FN_NE_EXACT; break;
              case FN_NE_EXACT: repl_type = FN_EQ_EXACT; break;
              case FN_LT:  repl_type = FN_GTE; break;
              case FN_LTE: repl_type = FN_GT; break;
              case FN_GT:  repl_type = FN_LTE; break;
              case FN_GTE: repl_type = FN_LT; break;
            }
            if (repl_type != -1)
            {
              const int oldtype = op->fntype;
              memcpy(op,op->parms.parms[0],sizeof(*op));
              if (oldtype == FN_NOT) op->fntype = repl_type;
              goto start_over;
            }
          }
        }
      }
      else if (op->opcodeType == OPCODETYPE_FUNC2)  // within FUNCTYPE_SIMPLE
      {
        const int dv0 = op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE;
        const int dv1 = op->parms.parms[1]->opcodeType == OPCODETYPE_DIRECTVALUE;
        if (dv0 && dv1)
        {
          int reval = -1;
          switch (op->fntype)
          {
            case FN_MOD:
              {
                EEL_F ret = 0.0;
                int a = (int) fabs(op->parms.parms[1]->parms.dv.directValue);
                if (a) 
                {
#ifdef GLUE_MOD_IS_64
                  ret = ((WDL_INT64) fabs(op->parms.parms[0]->parms.dv.directValue)) % a;
#else
                  ret = ((int) fabs(op->parms.parms[0]->parms.dv.directValue)) % a;
#endif
                  if (WDL_NOT_NORMALLY(ret<0)) ret = -ret;
                }
                RESTART_DIRECTVALUE(ret);
              }
            break;
            case FN_SHL:      RESTART_DIRECTVALUE(((int)op->parms.parms[0]->parms.dv.directValue) << ((int)op->parms.parms[1]->parms.dv.directValue));
            case FN_SHR:      RESTART_DIRECTVALUE(((int)op->parms.parms[0]->parms.dv.directValue) >> ((int)op->parms.parms[1]->parms.dv.directValue));
            case FN_POW:      RESTART_DIRECTVALUE(pow(op->parms.parms[0]->parms.dv.directValue, op->parms.parms[1]->parms.dv.directValue));
            case FN_DIVIDE:   RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue / op->parms.parms[1]->parms.dv.directValue);
            case FN_MULTIPLY: RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue * op->parms.parms[1]->parms.dv.directValue);

            case FN_ADD:      RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue + op->parms.parms[1]->parms.dv.directValue);
            case FN_SUB:      RESTART_DIRECTVALUE(op->parms.parms[0]->parms.dv.directValue - op->parms.parms[1]->parms.dv.directValue);
            case FN_AND:      RESTART_DIRECTVALUE((double) (((WDL_INT64)op->parms.parms[0]->parms.dv.directValue) & ((WDL_INT64)op->parms.parms[1]->parms.dv.directValue)));
            case FN_OR:       RESTART_DIRECTVALUE((double) (((WDL_INT64)op->parms.parms[0]->parms.dv.directValue) | ((WDL_INT64)op->parms.parms[1]->parms.dv.directValue)));
            case FN_XOR:      RESTART_DIRECTVALUE((double) (((WDL_INT64)op->parms.parms[0]->parms.dv.directValue) ^ ((WDL_INT64)op->parms.parms[1]->parms.dv.directValue)));

            case FN_EQ:       reval = fabs(op->parms.parms[0]->parms.dv.directValue - op->parms.parms[1]->parms.dv.directValue) < NSEEL_CLOSEFACTOR; break;
            case FN_NE:       reval = fabs(op->parms.parms[0]->parms.dv.directValue - op->parms.parms[1]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; break;
            case FN_EQ_EXACT: reval = op->parms.parms[0]->parms.dv.directValue == op->parms.parms[1]->parms.dv.directValue; break;
            case FN_NE_EXACT: reval = op->parms.parms[0]->parms.dv.directValue != op->parms.parms[1]->parms.dv.directValue; break;
            case FN_LT:       reval = op->parms.parms[0]->parms.dv.directValue < op->parms.parms[1]->parms.dv.directValue; break;
            case FN_LTE:      reval = op->parms.parms[0]->parms.dv.directValue <= op->parms.parms[1]->parms.dv.directValue; break;
            case FN_GT:       reval = op->parms.parms[0]->parms.dv.directValue > op->parms.parms[1]->parms.dv.directValue; break;
            case FN_GTE:      reval = op->parms.parms[0]->parms.dv.directValue >= op->parms.parms[1]->parms.dv.directValue; break;
            case FN_LOGICAL_AND: reval = fabs(op->parms.parms[0]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR && fabs(op->parms.parms[1]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; break;
            case FN_LOGICAL_OR:  reval = fabs(op->parms.parms[0]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR || fabs(op->parms.parms[1]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR; break;
          }
          
          if (reval >= 0) RESTART_DIRECTVALUE((EEL_F) reval);
        }
        else if (dv0 || dv1)
        {
          double dvalue = op->parms.parms[!dv0]->parms.dv.directValue;
          switch (op->fntype)
          {
            case FN_OR:
            case FN_XOR:
              if (!(WDL_INT64)dvalue)
              {
                // replace with or0
                op->opcodeType = OPCODETYPE_FUNC1;
                op->fntype = FUNCTYPE_FUNCTIONTYPEREC;
                op->fn = &fn_or0;
                if (dv0) op->parms.parms[0] = op->parms.parms[1];
                goto start_over;
              }
            break;
            case FN_SUB:
              if (dv0) 
              {
                if (dvalue == 0.0)
                {
                  op->opcodeType = OPCODETYPE_FUNC1;
                  op->fntype = FN_UMINUS;
                  op->parms.parms[0] = op->parms.parms[1];
                  goto start_over;
                }
                break;
              }
              WDL_FALLTHROUGH; // fall through, if dv1 we can remove +0.0

            case FN_ADD:
              if (dvalue == 0.0) 
              {
                memcpy(op,op->parms.parms[!!dv0],sizeof(*op));
                goto start_over;
              }
            break;
            case FN_AND:
              if ((WDL_INT64)dvalue) break;
              dvalue = 0.0; // treat x&0 as x*0, which optimizes to 0
            
              WDL_FALLTHROUGH; // fall through
            case FN_MULTIPLY:
              if (dvalue == 0.0) // remove multiply by 0.0 (using 0.0 direct value as replacement), unless the nonzero side did something
              {
                if (!retv_parm[!!dv0]) 
                {
                  memcpy(op,op->parms.parms[!dv0],sizeof(*op)); // set to 0 if other action wouldn't do anything
                  goto start_over;
                }
                else
                {
                  // this is 0.0 * oldexpressionthatmustbeprocessed or oldexpressionthatmustbeprocessed*0.0
                  op->fntype = FN_JOIN_STATEMENTS;

                  if (dv0) // 0.0*oldexpression, reverse the order so that 0 is returned
                  {
                    // set to (oldexpression;0)
                    opcodeRec *tmp = op->parms.parms[1];
                    op->parms.parms[1] = op->parms.parms[0];
                    op->parms.parms[0] = tmp;
                  }
                  goto start_over;
                }
              }
              else if (dvalue == 1.0) // remove multiply by 1.0 (using non-1.0 value as replacement)
              {
                memcpy(op,op->parms.parms[!!dv0],sizeof(*op));
                goto start_over;
              }
            break;
            case FN_POW:
              if (dv1)
              {
                // x^0 = 1
                if (fabs(dvalue) < 1e-30)
                {
                  RESTART_DIRECTVALUE(1.0);
                }
                // x^1 = x
                if (fabs(dvalue-1.0) < 1e-30)
                {
                  memcpy(op,op->parms.parms[0],sizeof(*op));
                  goto start_over;
                }
              }
              else if (dv0)
              {
                // pow(constant, x) = exp((x) * ln(constant)), if constant>0
                // opcodeRec *parm0 = op->parms.parms[0];
                if (dvalue > 0.0)
                {
                  static functionType *expcpy;
                  if (!expcpy)
                  {
                    expcpy = nseel_getFunctionByName(NULL,"exp",NULL);
                    if (WDL_NOT_NORMALLY(!expcpy)) break;
                  }

                  // 1^x = 1
                  if (fabs(dvalue-1.0) < 1e-30)
                  {
                    RESTART_DIRECTVALUE(1.0);
                  }

                  dvalue=log(dvalue);

                  if (fabs(dvalue-1.0) < 1e-9)
                  {
                    // caller wanted e^x
                    op->parms.parms[0]=op->parms.parms[1];
                  }
                  else
                  {
                    // it would be nice to replace 10^x with exp(log(10)*x) or 2^x with exp(log(2),x), but 
                    // doing so breaks rounding. we could maybe only allow 10^x, which is used for dB conversion,
                    // but for now we should just force the programmer do it exp(log(10)*x) themselves.
                    break;

                    /* 
                    parm0->opcodeType = OPCODETYPE_FUNC2;
                    parm0->fntype = FN_MULTIPLY;
                    parm0->parms.parms[0] = nseel_createCompiledValue(ctx,dvalue);
                    parm0->parms.parms[1] = op->parms.parms[1];
                    */
                  }

                  op->opcodeType = OPCODETYPE_FUNC1;
                  op->fntype = FUNCTYPE_FUNCTIONTYPEREC;
                  op->fn = expcpy;
                  goto start_over;
                }
              }
            break;
            case FN_MOD:
              if (dv1)
              {
                const int a = (int) dvalue;
                if (!a) 
                {
                  RESTART_DIRECTVALUE(0.0);
                }
              }
            break;
            case FN_DIVIDE:
              if (dv1)
              {
                if (dvalue == 1.0)  // remove divide by 1.0  (using non-1.0 value as replacement)
                {
                  memcpy(op,op->parms.parms[!!dv0],sizeof(*op));
                  goto start_over;
                }
                else
                {
                  // change to a multiply
                  if (dvalue == 0.0)
                  {
                    op->fntype = FN_MULTIPLY;
                    goto start_over;
                  }
                  else
                  {
                    double d = 1.0/dvalue;
                    WDL_UINT64 w;
                    memcpy(&w,&d,sizeof(d));
                    // allow conversion to multiply if reciprocal is exact
                    // we could also just look to see if the last few digits of the mantissa were 0, which would probably be good
                    // enough, but if the user really wants it they should do * (1/x) instead to force precalculation of reciprocal.
                    if (!(w & WDL_UINT64_CONST(0xfffffffffffff)))
                    {
                      op->fntype = FN_MULTIPLY;
                      op->parms.parms[1]->parms.dv.directValue = d;
                      op->parms.parms[1]->parms.dv.valuePtr=NULL;
                      goto start_over;
                    }
                  }
                }
              }
              else if (dvalue == 0.0)
              {
                if (!retv_parm[!!dv0])
                {
                  // if 0/x set to always 0.
                  // this is 0.0 / (oldexpression that can be eliminated)
                  memcpy(op,op->parms.parms[!dv0],sizeof(*op)); // set to 0 if other action wouldn't do anything
                }
                else
                {
                  opcodeRec *tmp;
                  // this is 0.0 / oldexpressionthatmustbeprocessed
                  op->fntype = FN_JOIN_STATEMENTS;
                  tmp = op->parms.parms[1];
                  op->parms.parms[1] = op->parms.parms[0];
                  op->parms.parms[0] = tmp;
                  // set to (oldexpression;0)
                }
                goto start_over;
              }
            break;
            case FN_EQ:
              if (dvalue == 0.0)
              {
                // convert x == 0.0 to !x
                op->opcodeType=OPCODETYPE_FUNC1;
                op->fntype = FN_NOT;
                if (dv0) op->parms.parms[0]=op->parms.parms[1];
                goto start_over;
              }
            break;
            case FN_NE:
              if (dvalue == 0.0)
              {
                // convert x != 0.0 to !!
                op->opcodeType=OPCODETYPE_FUNC1;
                op->fntype = FN_NOTNOT;
                if (dv0) op->parms.parms[0]=op->parms.parms[1];
                goto start_over;
              }
            break;
            case FN_LOGICAL_AND:
              if (dv0)
              {
                // dvalue && expr
                if (fabs(dvalue) < NSEEL_CLOSEFACTOR)
                {
                  // 0 && expr, replace with 0
                  RESTART_DIRECTVALUE(0.0);
                }
                else
                {
                  // 1 && expr, replace with 0 != expr
                  op->fntype = FN_NE;
                  op->parms.parms[0]->parms.dv.valuePtr=NULL;
                  op->parms.parms[0]->parms.dv.directValue = 0.0;
                }
              }
              else
              {
                // expr && dvalue
                if (fabs(dvalue) < NSEEL_CLOSEFACTOR)
                {
                  // expr && 0
                  if (!retv_parm[0]) 
                  {
                    // expr has no consequence, drop it
                    RESTART_DIRECTVALUE(0.0);
                  }
                  else
                  {
                    // replace with (expr; 0)
                    op->fntype = FN_JOIN_STATEMENTS;
                    op->parms.parms[1]->parms.dv.valuePtr=NULL;
                    op->parms.parms[1]->parms.dv.directValue = 0.0;
                  }
                }
                else
                {
                  // expr && 1, replace with expr != 0
                  op->fntype = FN_NE;
                  op->parms.parms[1]->parms.dv.valuePtr=NULL;
                  op->parms.parms[1]->parms.dv.directValue = 0.0;
                }
              }
            goto start_over;
            case FN_LOGICAL_OR:
              if (dv0)
              {
                // dvalue || expr
                if (fabs(dvalue) >= NSEEL_CLOSEFACTOR)
                {
                  // 1 || expr, replace with 1
                  RESTART_DIRECTVALUE(1.0);
                }
                else
                {
                  // 0 || expr, replace with 0 != expr
                  op->fntype = FN_NE;
                  op->parms.parms[0]->parms.dv.valuePtr=NULL;
                  op->parms.parms[0]->parms.dv.directValue = 0.0;
                }
              }
              else
              {
                // expr || dvalue
                if (fabs(dvalue) >= NSEEL_CLOSEFACTOR)
                {
                  // expr || 1
                  if (!retv_parm[0]) 
                  {
                    // expr has no consequence, drop it and return 1
                    RESTART_DIRECTVALUE(1.0);
                  }
                  else
                  {
                    // replace with (expr; 1)
                    op->fntype = FN_JOIN_STATEMENTS;
                    op->parms.parms[1]->parms.dv.valuePtr=NULL;
                    op->parms.parms[1]->parms.dv.directValue = 1.0;
                  }
                }
                else
                {
                  // expr || 0, replace with expr != 0
                  op->fntype = FN_NE;
                  op->parms.parms[1]->parms.dv.valuePtr=NULL;
                  op->parms.parms[1]->parms.dv.directValue = 0.0;
                }
              }
            goto start_over;
          }
        } // dv0 || dv1

        // general optimization of two parameters
        switch (op->fntype)
        {
          case FN_MULTIPLY:
          {
            opcodeRec *first_parm = op->parms.parms[0],*second_parm = op->parms.parms[1];

            if (second_parm->opcodeType == first_parm->opcodeType) 
            {
              switch(first_parm->opcodeType)
              {
                case OPCODETYPE_VALUE_FROM_NAMESPACENAME:
                  if (first_parm->namespaceidx != second_parm->namespaceidx) break;
                  WDL_FALLTHROUGH; // fall through
                case OPCODETYPE_VARPTR:
                  if (first_parm->relname && second_parm->relname && !stricmp(second_parm->relname,first_parm->relname)) second_parm=NULL;
                break;
                case OPCODETYPE_VARPTRPTR:
                  if (first_parm->parms.dv.valuePtr && first_parm->parms.dv.valuePtr==second_parm->parms.dv.valuePtr) second_parm=NULL;
                break;

              }
              if (!second_parm) // switch from x*x to sqr(x)
              {
                static functionType *sqrcpy;
                if (!sqrcpy) sqrcpy = nseel_getFunctionByName(NULL,"sqr",NULL);
                if (WDL_NORMALLY(sqrcpy))
                {
                  op->opcodeType = OPCODETYPE_FUNC1;
                  op->fntype = FUNCTYPE_FUNCTIONTYPEREC;
                  op->fn = sqrcpy;
                  goto start_over;
                }
              }
            }
          }
          break;
          case FN_POW:
            {
              opcodeRec *first_parm = op->parms.parms[0];
              if (first_parm->opcodeType == op->opcodeType && first_parm->fntype == FN_POW)
              {
                // since first_parm is a pow too, we can multiply the exponents.

                // set our base to be the base of the inner pow
                op->parms.parms[0] = first_parm->parms.parms[0];

                // make the old extra pow be a multiply of the exponents
                first_parm->fntype = FN_MULTIPLY;
                first_parm->parms.parms[0] = op->parms.parms[1];

                // put that as the exponent
                op->parms.parms[1] = first_parm;

                goto start_over;
              }
            }
          break;
          case FN_LOGICAL_AND:
          case FN_LOGICAL_OR:
            if (op->parms.parms[0]->fntype == FN_NOTNOT)
            {
              // remove notnot, unnecessary for input to &&/|| operators
              op->parms.parms[0] = op->parms.parms[0]->parms.parms[0];
              goto start_over;
            }
            if (op->parms.parms[1]->fntype == FN_NOTNOT)
            {
              // remove notnot, unnecessary for input to &&/|| operators
              op->parms.parms[1] = op->parms.parms[1]->parms.parms[0];
              goto start_over;
            }        
          break;
        }
      }
      else if (op->opcodeType==OPCODETYPE_FUNC3)  // within FUNCTYPE_SIMPLE
      {
        if (op->fntype == FN_IF_ELSE)
        {
          if (op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE)
          {
            int s = fabs(op->parms.parms[0]->parms.dv.directValue) >= NSEEL_CLOSEFACTOR;
            memcpy(op,op->parms.parms[s ? 1 : 2],sizeof(opcodeRec));
            goto start_over;
          }
          if (op->parms.parms[0]->opcodeType == OPCODETYPE_FUNC1)
          {
            if (op->parms.parms[0]->fntype == FN_NOTNOT)
            {
              // remove notnot, unnecessary for input to ? operator
              op->parms.parms[0] = op->parms.parms[0]->parms.parms[0];
              goto start_over;
            }
          }
        }
      }
      if (op->fntype >= FN_NONCONST_BEGIN && op->fntype < FUNCTYPE_SIMPLEMAX) retv|=1;

      // FUNCTYPE_SIMPLE
    }   
    else if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC && op->fn)
    {

      /*
      probably worth doing reduction on:
      _divop (constant change to multiply)
      _and
      _or
      abs

      maybe:
      min
      max


      also, optimize should (recursively or maybe iteratively?) search transitive functions (mul/div) for more constant reduction possibilities


      */


      functionType  *pfn = (functionType *)op->fn;

      if (!(pfn->nParams&NSEEL_NPARAMS_FLAG_CONST)) retv|=1;

      if (op->opcodeType==OPCODETYPE_FUNC1) // within FUNCTYPE_FUNCTIONTYPEREC
      {
        if (op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE)
        {
          int suc=1;
          EEL_F v = op->parms.parms[0]->parms.dv.directValue;
  #define DOF(x) if (!strcmp(pfn->name,#x)) v = x(v); else
  #define DOF2(x,y) if (!strcmp(pfn->name,#x)) v = x(y); else
          DOF(sin)
          DOF(cos)
          DOF(tan)
          DOF(asin)
          DOF(acos)
          DOF(atan)
          DOF2(sqrt, fabs(v))
          DOF(exp)
          DOF(log)
          DOF(log10)
          /* else */ suc=0;
  #undef DOF
  #undef DOF2
          if (suc)
          {
            RESTART_DIRECTVALUE(v);
          }


        }
      }
      else if (op->opcodeType==OPCODETYPE_FUNC2)  // within FUNCTYPE_FUNCTIONTYPEREC
      {
        const int dv0=op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE;
        const int dv1=op->parms.parms[1]->opcodeType == OPCODETYPE_DIRECTVALUE;
        if (dv0 && dv1)
        {
          if (!strcmp(pfn->name,"atan2")) 
          {
            RESTART_DIRECTVALUE(atan2(op->parms.parms[0]->parms.dv.directValue, op->parms.parms[1]->parms.dv.directValue));
          }
        }
      }
      // FUNCTYPE_FUNCTIONTYPEREC
    }
    else
    {
      // unknown or eel func, assume non-const
      retv |= 1;
    }
  }

  // if we need results, or our function has effects itself, then finish
  if (retv || needsResult)
  {
    return retv || joined_retv || retv_parm[0] || retv_parm[1] || retv_parm[2];
  }

  // we don't need results here, and our function is const, which means we can remove it
  {
    int cnt=0, idx1=0, idx2=0, x;
    for (x=0;x<3;x++) if (retv_parm[x]) { if (!cnt++) idx1=x; else idx2=x; }

    if (!cnt) // none of the parameters do anything, remove this opcode
    {
      if (lastJoinOp)
      {
        // replace previous join with its first linked opcode, removing this opcode completely
        memcpy(lastJoinOp,lastJoinOp->parms.parms[0],sizeof(*lastJoinOp));
      }
      else if (op->opcodeType != OPCODETYPE_DIRECTVALUE)
      {
        // allow caller to easily detect this as trivial and remove it
        op->opcodeType = OPCODETYPE_DIRECTVALUE;
        op->parms.dv.valuePtr=NULL;
        op->parms.dv.directValue=0.0;
      }
      // return joined_retv below
    }
    else
    {
      // if parameters are non-const, and we're a conditional, preserve our function
      if (FNPTR_HAS_CONDITIONAL_EXEC(op)) return 1;

      // otherwise, condense into either the non-const statement, or a join
      if (cnt==1)
      {
        memcpy(op,op->parms.parms[idx1],sizeof(*op));
      }
      else if (cnt == 2)
      {
        op->opcodeType = OPCODETYPE_FUNC2;
        op->fntype = FN_JOIN_STATEMENTS;
        op->fn = op;
        op->parms.parms[0] = op->parms.parms[idx1];
        op->parms.parms[1] = op->parms.parms[idx2];
        op->parms.parms[2] = NULL;
      }
      else
      {
        // todo need to create a new opcodeRec here, for now just leave as is 
        // (non-conditional const 3 parameter functions are rare anyway)
      }
      return 1;
    }
  }
  return joined_retv;
}


static int generateValueToReg(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int whichReg, const namespaceInformation *functionPrefix, int allowCache)
{
  EEL_F *b=NULL;
  if (op->opcodeType==OPCODETYPE_VALUE_FROM_NAMESPACENAME)
  {
    char nm[NSEEL_MAX_VARIABLE_NAMELEN+1];
    const char *p = op->relname;
    combineNamespaceFields(nm,functionPrefix,p+(*p == '#'),op->namespaceidx);
    if (!nm[0]) return -1;
    if (*p == '#') 
    {
      if (ctx->isGeneratingCommonFunction)
        b = newCtxDataBlock(sizeof(EEL_F),sizeof(EEL_F));
      else
        b = newDataBlock(sizeof(EEL_F),sizeof(EEL_F));

      if (!b) RET_MINUS1_FAIL("error creating storage for str")

      if (!ctx->onNamedString) return -1; // should never happen, will not generate OPCODETYPE_VALUE_FROM_NAMESPACENAME with # prefix if !onNamedString

      *b = ctx->onNamedString(ctx->caller_this,nm);
    }
    else
    {
      b = nseel_int_register_var(ctx,nm,0,NULL);
      if (!b) RET_MINUS1_FAIL("error registering var")
    }
  }
  else
  {
    if (op->opcodeType != OPCODETYPE_DIRECTVALUE) allowCache=0;

    if (op->opcodeType==OPCODETYPE_DIRECTVALUE_TEMPSTRING && ctx->onNamedString)
    {
      op->parms.dv.directValue = ctx->onNamedString(ctx->caller_this,"");
      op->parms.dv.valuePtr = NULL;
    }

    b=op->parms.dv.valuePtr;
    if (!b && op->opcodeType == OPCODETYPE_VARPTR && op->relname && op->relname[0]) 
    {
      op->parms.dv.valuePtr = b = nseel_int_register_var(ctx,op->relname,0,NULL);
    }

    if (b && op->opcodeType == OPCODETYPE_VARPTRPTR) b = *(EEL_F **)b;
    if (!b && allowCache)
    {
      int n=50; // only scan last X items
      opcodeRec *r = ctx->directValueCache;
      while (r && n--)
      {
        if (r->parms.dv.directValue == op->parms.dv.directValue && (b=r->parms.dv.valuePtr)) break;
        r=(opcodeRec*)r->fn;
      }
    }
    if (!b)
    {
      ctx->l_stats[3]++;
      if (ctx->isGeneratingCommonFunction)
        b = newCtxDataBlock(sizeof(EEL_F),sizeof(EEL_F));
      else
        b = newDataBlock(sizeof(EEL_F),sizeof(EEL_F));

      if (!b) RET_MINUS1_FAIL("error allocating data block")

      if (op->opcodeType != OPCODETYPE_VARPTRPTR) op->parms.dv.valuePtr = b;
      *b = denormal_filter_double2(op->parms.dv.directValue);

      if (allowCache)
      {
        op->fn = ctx->directValueCache;
        ctx->directValueCache = op;
      }
    }
  }

  GLUE_MOV_PX_DIRECTVALUE_GEN(bufOut,(INT_PTR)b,whichReg);
  return GLUE_MOV_PX_DIRECTVALUE_SIZE;
}


unsigned char *compileCodeBlockWithRet(compileContext *ctx, opcodeRec *rec, int *computTableSize, const namespaceInformation *namespacePathToThis, 
                                       int supportedReturnValues, int *rvType, int *fpStackUsage, int *canHaveDenormalOutput)
{
  unsigned char *p, *newblock2;
  // generate code call
  int funcsz=compileOpcodes(ctx,rec,NULL,1024*1024*128,NULL,namespacePathToThis,supportedReturnValues, rvType,fpStackUsage, NULL);
  if (funcsz<0) return NULL;
 
  p = newblock2 = newCodeBlock(funcsz+ sizeof(GLUE_RET)+GLUE_FUNC_ENTER_SIZE+GLUE_FUNC_LEAVE_SIZE,32);
  if (!newblock2) return NULL;
  #if GLUE_FUNC_ENTER_SIZE > 0
    memcpy(p,&GLUE_FUNC_ENTER,GLUE_FUNC_ENTER_SIZE); 
    p += GLUE_FUNC_ENTER_SIZE;
  #endif
  *fpStackUsage=0;
  funcsz=compileOpcodes(ctx,rec,p, funcsz, computTableSize,namespacePathToThis,supportedReturnValues, rvType,fpStackUsage, canHaveDenormalOutput);         
  if (funcsz<0) return NULL;
  p+=funcsz;

  #if GLUE_FUNC_LEAVE_SIZE > 0
    memcpy(p,&GLUE_FUNC_LEAVE,GLUE_FUNC_LEAVE_SIZE); 
    p+=GLUE_FUNC_LEAVE_SIZE;
  #endif
  memcpy(p,&GLUE_RET,sizeof(GLUE_RET)); p+=sizeof(GLUE_RET);
#if defined(__arm__) || defined(__aarch64__)
  __clear_cache(newblock2,p);
#endif
  
  ctx->l_stats[2]+=funcsz+2;
  return newblock2;
}      

static int compileNativeFunctionCall(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, 
                                     int *rvMode, int *fpStackUsage, int preferredReturnValues, int *canHaveDenormalOutput)
{
  // builtin function generation
  int func_size=0;
  int cfunc_abiinfo=0;
  int local_fpstack_use=0; // how many items we have pushed onto the fp stack
  int parm_size=0;
  int restore_stack_amt=0;
#ifdef GLUE_HAS_FUSE
  int fuse_flags = 0; // 1 = last parm was trivial (likely mov rax, movsd xmm0 ,[rax])
#endif
#if GLUE_MAX_SPILL_REGS > 0
  int spill_reg = -1;
#endif

  NSEEL_PPPROC preProc=0;
  void **repl=NULL;

  int n_params= 1 + op->opcodeType - OPCODETYPE_FUNC1;

  const int parm0_dv = op->parms.parms[0]->opcodeType == OPCODETYPE_DIRECTVALUE;
  const int parm1_dv = n_params > 1 && op->parms.parms[1]->opcodeType == OPCODETYPE_DIRECTVALUE;

  void *func = nseel_getBuiltinFunctionAddress(ctx, op->fntype, op->fn, &preProc,&repl,
      &cfunc_abiinfo,preferredReturnValues,
       parm0_dv ? &op->parms.parms[0]->parms.dv.directValue : NULL,
       parm1_dv ? &op->parms.parms[1]->parms.dv.directValue : NULL
       );

  *fpStackUsage=BIF_GETFPSTACKUSE(cfunc_abiinfo);
  *rvMode = RETURNVALUE_NORMAL;

  if (cfunc_abiinfo & BIF_TAKES_VARPARM)
  {
#if defined(__arm__) || (defined (_M_ARM) && _M_ARM  == 7)
    const int max_params=16384; // arm uses up to two instructions, should be good for at leaast 64k (16384*4)
#elif defined(__ppc__)
    const int max_params=4096; // 32kb max offset addressing for stack, so 4096*4 = 16384, should be safe
#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)
    const int max_params=32768; 
#else
    const int max_params=32768; // sanity check, the stack is free to grow on x86/x86-64
#endif
    int x;
    // this mode is less efficient in that it creates a list of pointers on the stack to pass to the function
    // but it is more flexible and works for >3 parameters.
    if (op->opcodeType == OPCODETYPE_FUNCX)
    {
      n_params=0;
      for (x=0;x<3;x++)
      {
        opcodeRec *prni=op->parms.parms[x];
        while (prni)
        {
          const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS;
          n_params++;
          if (!isMP||n_params>=max_params) break;
          prni = prni->parms.parms[1];
        }
      }
    }

    restore_stack_amt = (sizeof(void *) * n_params + 15)&~15;

    if (restore_stack_amt)
    {
      int offs = restore_stack_amt;
      while (offs > 0)
      {
        int amt = offs;
        if (amt > 4096) amt=4096;

        if (bufOut_len < parm_size+GLUE_MOVE_STACK_SIZE) RET_MINUS1_FAIL("insufficient size for varparm")
        if (bufOut) GLUE_MOVE_STACK(bufOut+parm_size, - amt);
        parm_size += GLUE_MOVE_STACK_SIZE;
        offs -= amt;

        if (offs>0) // make sure this page is in memory
        {
          if (bufOut_len < parm_size+GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(0)) 
            RET_MINUS1_FAIL("insufficient size for varparm stackchk")
          if (bufOut) GLUE_STORE_P1_TO_STACK_AT_OFFS(bufOut+parm_size,0);
          parm_size += GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(0);
        }
      }
    }

    if (op->opcodeType == OPCODETYPE_FUNCX)
    {     
      n_params=0;
      for (x=0;x<3;x++)
      {
        opcodeRec *prni=op->parms.parms[x];
        while (prni)
        {
          const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS;
          opcodeRec *r = isMP ? prni->parms.parms[0] : prni;
          if (r)
          {
            int canHaveDenorm=0;
            int rvt=RETURNVALUE_NORMAL;
            int subfpstackuse=0, use_offs;
                
            int lsz = compileOpcodes(ctx,r,bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, rvt,&rvt, &subfpstackuse, &canHaveDenorm);
            if (canHaveDenorm && canHaveDenormalOutput) *canHaveDenormalOutput = 1;

            if (lsz<0) RET_MINUS1_FAIL("call coc for varparmX failed")
            if (rvt != RETURNVALUE_NORMAL) RET_MINUS1_FAIL("call coc for varparmX gave bad type back");

            parm_size += lsz;            
            use_offs = n_params*(int) sizeof(void *);

            if (bufOut_len < parm_size+GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs)) 
              RET_MINUS1_FAIL("call coc for varparmX size");
            if (bufOut) GLUE_STORE_P1_TO_STACK_AT_OFFS(bufOut + parm_size, use_offs);
            parm_size+=GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs);

            if (subfpstackuse+local_fpstack_use > *fpStackUsage) *fpStackUsage = subfpstackuse+local_fpstack_use;
          }
          else RET_MINUS1_FAIL("zero parameter varparmX")

          n_params++;

          if (!isMP||n_params>=max_params) break;
          prni = prni->parms.parms[1];
        }
      }
    }
    else for (x=0;x<n_params;x++)
    {
      opcodeRec *r = op->parms.parms[x];
      if (r)
      {
        int canHaveDenorm=0;
        int subfpstackuse=0;
        int rvt=RETURNVALUE_NORMAL;
        int use_offs;
               
        int lsz = compileOpcodes(ctx,r,bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, rvt,&rvt, &subfpstackuse, &canHaveDenorm);
        if (canHaveDenorm && canHaveDenormalOutput) *canHaveDenormalOutput = 1;

        if (lsz<0) RET_MINUS1_FAIL("call coc for varparm123 failed")
        if (rvt != RETURNVALUE_NORMAL) RET_MINUS1_FAIL("call coc for varparm123 gave bad type back");

        parm_size += lsz;

        use_offs = x*(int)sizeof(void *);
        if (bufOut_len < parm_size+GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs)) 
          RET_MINUS1_FAIL("call coc for varparm123 size");
        if (bufOut) GLUE_STORE_P1_TO_STACK_AT_OFFS(bufOut + parm_size, use_offs);
        parm_size+=GLUE_STORE_P1_TO_STACK_AT_OFFS_SIZE(use_offs);

        if (subfpstackuse+local_fpstack_use > *fpStackUsage) *fpStackUsage = subfpstackuse+local_fpstack_use;
      }
      else RET_MINUS1_FAIL("zero parameter for varparm123");
    }

    if (bufOut_len < parm_size+GLUE_MOV_PX_DIRECTVALUE_SIZE+GLUE_MOVE_PX_STACKPTR_SIZE) RET_MINUS1_FAIL("insufficient size for varparm p1")
    if (bufOut) GLUE_MOV_PX_DIRECTVALUE_GEN(bufOut+parm_size, (INT_PTR)n_params,1);
    parm_size+=GLUE_MOV_PX_DIRECTVALUE_SIZE;
    if (bufOut) GLUE_MOVE_PX_STACKPTR_GEN(bufOut+parm_size, 0);
    parm_size+=GLUE_MOVE_PX_STACKPTR_SIZE;
    
  }
  else // not varparm
  {
    int pn;
  #if GLUE_MAX_FPSTACK_SIZE > 0
    int need_fxch=0;
  #endif
    int last_nt_parm=-1, last_nt_parm_type=-1;
    
    if (op->opcodeType == OPCODETYPE_FUNCX)
    {
      // this is not yet supported (calling conventions will need to be sorted, among other things)
      RET_MINUS1_FAIL("funcx for native functions requires BIF_TAKES_VARPARM or BIF_TAKES_VARPARM_EX")
    }

    if (parm0_dv) 
    {
      if (func == nseel_asm_stack_pop)
      {
        func = GLUE_realAddress(nseel_asm_stack_pop_fast,&func_size);
        if (!func || bufOut_len < func_size) RET_MINUS1_FAIL(func?"failed on popfast size":"failed on popfast addr")

        if (bufOut) 
        {
          memcpy(bufOut,func,func_size);
          NSEEL_PProc_Stack(bufOut,func_size,ctx);
        }
        return func_size;            
      }
      else if (func == nseel_asm_stack_peek)
      {
        int f = (int) op->parms.parms[0]->parms.dv.directValue;
        if (!f)
        {
          func = GLUE_realAddress(nseel_asm_stack_peek_top,&func_size);
          if (!func || bufOut_len < func_size) RET_MINUS1_FAIL(func?"failed on peek size":"failed on peek addr")

          if (bufOut) 
          {
            memcpy(bufOut,func,func_size);
            NSEEL_PProc_Stack_PeekTop(bufOut,func_size,ctx);
          }
          return func_size;
        }
        else
        {
          func = GLUE_realAddress(nseel_asm_stack_peek_int,&func_size);
          if (!func || bufOut_len < func_size) RET_MINUS1_FAIL(func?"failed on peekint size":"failed on peekint addr")

          if (bufOut)
          {
            memcpy(bufOut,func,func_size);
            NSEEL_PProc_Stack_PeekInt(bufOut,func_size,ctx,f*sizeof(EEL_F));
          }
          return func_size;
        }
      }
    }
    // end of built-in function specific special casing


    // first pass, calculate any non-trivial parameters
    for (pn=0; pn < n_params; pn++)
    { 
      if (!OPCODE_IS_TRIVIAL(op->parms.parms[pn]))
      {
        int canHaveDenorm=0;
        int subfpstackuse=0;
        int lsz=0; 
        int rvt=RETURNVALUE_NORMAL;
        int may_need_fppush=-1;
        if (last_nt_parm>=0)
        {
          if (last_nt_parm_type==RETURNVALUE_FPSTACK)
          {          
            may_need_fppush= parm_size;
          }
          else
          {
            // push last result
            if (bufOut_len < parm_size + (int)sizeof(GLUE_PUSH_P1)) RET_MINUS1_FAIL("failed on size, pushp1")
            if (bufOut) memcpy(bufOut + parm_size, &GLUE_PUSH_P1, sizeof(GLUE_PUSH_P1));
            parm_size += sizeof(GLUE_PUSH_P1);
          }
        }         

        if (func == nseel_asm_bnot) rvt=RETURNVALUE_BOOL_REVERSED|RETURNVALUE_BOOL;
        else if (pn == n_params - 1)
        {
          if (cfunc_abiinfo&BIF_LASTPARMONSTACK) rvt=RETURNVALUE_FPSTACK;
          else if (cfunc_abiinfo&BIF_LASTPARM_ASBOOL) rvt=RETURNVALUE_BOOL;
          else if (func == nseel_asm_assign) rvt=RETURNVALUE_FPSTACK|RETURNVALUE_NORMAL;
        }
        else if (pn == n_params -2 && (cfunc_abiinfo&BIF_SECONDLASTPARMST))
        {
          rvt=RETURNVALUE_FPSTACK;
        }

        lsz = compileOpcodes(ctx,op->parms.parms[pn],bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, rvt,&rvt, &subfpstackuse, &canHaveDenorm);

        if (lsz<0) RET_MINUS1_FAIL("call coc failed")

        if (func == nseel_asm_bnot && rvt==RETURNVALUE_BOOL_REVERSED)
        {
          // remove bnot, compileOpcodes() used fptobool_rev
          func = NULL;
          rvt = RETURNVALUE_BOOL;
        }

        if (canHaveDenorm && canHaveDenormalOutput) *canHaveDenormalOutput = 1;

        parm_size += lsz;            

        if (may_need_fppush>=0)
        {
#if GLUE_MAX_SPILL_REGS > 0
          if (local_fpstack_use+subfpstackuse < GLUE_MAX_SPILL_REGS && pn == n_params - 1 && !(ctx->optimizeDisableFlags&OPTFLAG_NO_FPSTACK))
          {
            int spill_sz;
            spill_reg = local_fpstack_use+subfpstackuse;
            spill_sz = GLUE_SAVE_TO_SPILL_SIZE(spill_reg);
            if (bufOut_len < parm_size + spill_sz) RET_MINUS1_FAIL("failed on size, savetospilll")

            if (bufOut)
            {
              memmove(bufOut + may_need_fppush + spill_sz, bufOut + may_need_fppush, parm_size - may_need_fppush);
              GLUE_SAVE_TO_SPILL(bufOut + may_need_fppush, spill_reg);
            }
            parm_size += spill_sz;
            local_fpstack_use++;
          }
          else
#endif
          if (local_fpstack_use+subfpstackuse >= (GLUE_MAX_FPSTACK_SIZE-1) || (ctx->optimizeDisableFlags&OPTFLAG_NO_FPSTACK))
          {
            if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_FPSTACK_TOSTACK)) 
              RET_MINUS1_FAIL("failed on size, popfpstacktostack")

            if (bufOut) 
            {
              memmove(bufOut + may_need_fppush + sizeof(GLUE_POP_FPSTACK_TOSTACK), bufOut + may_need_fppush, parm_size - may_need_fppush);
              memcpy(bufOut + may_need_fppush, &GLUE_POP_FPSTACK_TOSTACK, sizeof(GLUE_POP_FPSTACK_TOSTACK));

            }
            parm_size += sizeof(GLUE_POP_FPSTACK_TOSTACK);
          }
          else
          {
            local_fpstack_use++;
          }
        }

        if (subfpstackuse+local_fpstack_use > *fpStackUsage) *fpStackUsage = subfpstackuse+local_fpstack_use;

        last_nt_parm = pn;
        last_nt_parm_type = rvt;

        if (pn == n_params - 1 && func == nseel_asm_assign)
        {
          if (!(ctx->optimizeDisableFlags & OPTFLAG_FULL_DENORMAL_CHECKS) && 
              (!canHaveDenorm || (ctx->optimizeDisableFlags & OPTFLAG_NO_DENORMAL_CHECKS)))
          {
            if (rvt == RETURNVALUE_FPSTACK)
            {
              cfunc_abiinfo |= BIF_LASTPARMONSTACK;
              func = nseel_asm_assign_fast_fromfp;
            }
            else
            {
              func = nseel_asm_assign_fast;
            }
          }
          else
          {
            if (rvt == RETURNVALUE_FPSTACK)
            {
              cfunc_abiinfo |= BIF_LASTPARMONSTACK;
              func = nseel_asm_assign_fromfp;
            }
          }
        
        }
      }
    }

    pn = last_nt_parm;
  
    if (pn >= 0) // if the last thing executed doesn't go to the last parameter, move it there
    {
      if ((cfunc_abiinfo&BIF_SECONDLASTPARMST) && pn == n_params-2)
      {
        // do nothing, things are in the right place
      }
      else if (pn != n_params-1)
      {
        // generate mov p1->pX
        if (bufOut_len < parm_size + GLUE_SET_PX_FROM_P1_SIZE) RET_MINUS1_FAIL("size, pxfromp1")
        if (bufOut) GLUE_SET_PX_FROM_P1(bufOut + parm_size,n_params - 1 - pn);
        parm_size += GLUE_SET_PX_FROM_P1_SIZE;
      }
    }

    // pop any pushed parameters
    while (--pn >= 0)
    { 
      if (!OPCODE_IS_TRIVIAL(op->parms.parms[pn]))
      {
        if ((cfunc_abiinfo&BIF_SECONDLASTPARMST) && pn == n_params-2)
        {
          if (!local_fpstack_use)
          {
            #if GLUE_HAS_FPREG2 > 0
              if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_STACK_TO_FPREG2)) RET_MINUS1_FAIL("size, popstacktofpstack2 2")
              if (bufOut) memcpy(bufOut+parm_size,GLUE_POP_STACK_TO_FPREG2,sizeof(GLUE_POP_STACK_TO_FPREG2));
              parm_size += sizeof(GLUE_POP_STACK_TO_FPREG2);
            #else
              if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_STACK_TO_FPSTACK)) RET_MINUS1_FAIL("size, popstacktofpstack 2")
              if (bufOut) memcpy(bufOut+parm_size,GLUE_POP_STACK_TO_FPSTACK,sizeof(GLUE_POP_STACK_TO_FPSTACK));
              parm_size += sizeof(GLUE_POP_STACK_TO_FPSTACK);
            #endif
            #if GLUE_MAX_FPSTACK_SIZE > 0
              need_fxch = 1;
            #endif
          }
          else
          {
#if GLUE_MAX_SPILL_REGS > 0
            if (spill_reg<0) RET_MINUS1_FAIL("spill reg not allocated");

            if (bufOut_len < parm_size + GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(spill_reg)) RET_MINUS1_FAIL("size, copy fps to fpreg2")
            if (bufOut) GLUE_RESTORE_SPILL_TO_FPREG2(bufOut+parm_size,spill_reg);
            parm_size += GLUE_RESTORE_SPILL_TO_FPREG2_SIZE(spill_reg);
#endif
            local_fpstack_use--;
          }
        }
        else
        {
          if (bufOut_len < parm_size + GLUE_POP_PX_SIZE) RET_MINUS1_FAIL("size, poppx")
          if (bufOut) GLUE_POP_PX(bufOut + parm_size,n_params - 1 - pn);
          parm_size += GLUE_POP_PX_SIZE;
        }
      }
    }

    // finally, set trivial pointers
    for (pn=0; pn < n_params; pn++)
    { 
      if (OPCODE_IS_TRIVIAL(op->parms.parms[pn]))
      {
        if (pn == n_params-2 && (cfunc_abiinfo&(BIF_SECONDLASTPARMST)))  // second to last parameter
        {
          #if GLUE_HAS_FPREG2 > 0
          const int req = RETURNVALUE_FPREG2;
          #else
          const int req = RETURNVALUE_FPSTACK;
          #endif
          int a = compileOpcodes(ctx,op->parms.parms[pn],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size,computTableSize,namespacePathToThis,
                                    req,NULL,NULL,canHaveDenormalOutput);
          if (a<0) RET_MINUS1_FAIL("coc call here 2")
          parm_size+=a;

          #if GLUE_MAX_FPSTACK_SIZE > 0
            need_fxch = 1;
          #endif
        }
        else if (pn == n_params-1)  // last parameter, but we should call compileOpcodes to get it in the right format (compileOpcodes can optimize that process if it needs to)
        {
          int rvt=0, a;
          int wantFpStack = func == nseel_asm_assign;
  #ifdef GLUE_PREFER_NONFP_DV_ASSIGNS // x86-64, and maybe others, prefer to avoid the fp stack for a simple copy
          if (wantFpStack &&
              (op->parms.parms[pn]->opcodeType != OPCODETYPE_DIRECTVALUE ||
              op->parms.parms[pn]->parms.dv.directValue != 0.0))
          {
            wantFpStack=-1; // cacheable but non-FP stack
          }
  #endif
  #if GLUE_HAS_FPREG2 > 0
          if (pn > 0 && (cfunc_abiinfo&(BIF_SECONDLASTPARMST) && !OPCODE_IS_TRIVIAL(op->parms.parms[pn-1])))
          {
            if (bufOut_len < parm_size+(int)sizeof(GLUE_COPY_FPSTACK_TO_FPREG2)) RET_MINUS1_FAIL("fptofpstack2tfp");
            if (bufOut) memcpy(bufOut+parm_size,GLUE_COPY_FPSTACK_TO_FPREG2,sizeof(GLUE_COPY_FPSTACK_TO_FPREG2));
            parm_size += sizeof(GLUE_COPY_FPSTACK_TO_FPREG2);
          }
  #endif

          a = compileOpcodes(ctx,op->parms.parms[pn],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size,computTableSize,namespacePathToThis,
            func == nseel_asm_bnot ? (RETURNVALUE_BOOL_REVERSED|RETURNVALUE_BOOL) :
              (cfunc_abiinfo & BIF_LASTPARMONSTACK) ? RETURNVALUE_FPSTACK : 
              (cfunc_abiinfo & BIF_LASTPARM_ASBOOL) ? RETURNVALUE_BOOL : 
              wantFpStack < 0 ? (RETURNVALUE_CACHEABLE|RETURNVALUE_NORMAL) : 
              wantFpStack ? (RETURNVALUE_FPSTACK|RETURNVALUE_NORMAL) : 
              RETURNVALUE_NORMAL,       
            &rvt, NULL,canHaveDenormalOutput);
           
          if (a<0) RET_MINUS1_FAIL("coc call here 3")
#ifdef GLUE_HAS_FUSE
          if (rvt == RETURNVALUE_FPSTACK) fuse_flags |= 1;
#endif

          if (func == nseel_asm_bnot && rvt == RETURNVALUE_BOOL_REVERSED)
          {
            // remove bnot, compileOpcodes() used fptobool_rev
            // case: !b ? 2 : 3, for example
            func = NULL;
            rvt = RETURNVALUE_BOOL;
          }

          parm_size+=a;
          #if GLUE_MAX_FPSTACK_SIZE > 0
            need_fxch = 0;
          #endif

          if (func == nseel_asm_assign)
          {
            if (rvt == RETURNVALUE_FPSTACK)
            {           
              if (!(ctx->optimizeDisableFlags & OPTFLAG_FULL_DENORMAL_CHECKS))
              {
                func = nseel_asm_assign_fast_fromfp;
              }
              else
              {
                func = nseel_asm_assign_fromfp;
              }
            }
            else if (!(ctx->optimizeDisableFlags & OPTFLAG_FULL_DENORMAL_CHECKS))
            {
               // assigning a value (from a variable or other non-computer), can use a fast assign (no denormal/result checking)
              func = nseel_asm_assign_fast;
            }
          }
        }
        else
        {
          if (bufOut_len < parm_size + GLUE_MOV_PX_DIRECTVALUE_SIZE) RET_MINUS1_FAIL("size, pxdvsz")
          if (bufOut) 
          {
            if (generateValueToReg(ctx,op->parms.parms[pn],bufOut + parm_size,n_params - 1 - pn,namespacePathToThis, 0/*nocaching, function gets pointer*/)<0) RET_MINUS1_FAIL("gvtr")
          }
          parm_size += GLUE_MOV_PX_DIRECTVALUE_SIZE;
        }
      }
    }

  #if GLUE_MAX_FPSTACK_SIZE > 0
    if ((cfunc_abiinfo&(BIF_SECONDLASTPARMST)) && !(cfunc_abiinfo&(BIF_LAZYPARMORDERING))&&
        ((!!need_fxch)^!!(cfunc_abiinfo&BIF_REVERSEFPORDER)) 
        )
    {
      // emit fxch
      if (bufOut_len < sizeof(GLUE_FXCH)) RET_MINUS1_FAIL("len,fxch")
      if (bufOut) 
      { 
        memcpy(bufOut+parm_size,GLUE_FXCH,sizeof(GLUE_FXCH));
      }
      parm_size+=sizeof(GLUE_FXCH);
    }
  #endif
  
    if (!*canHaveDenormalOutput)
    {
      // if add_op or sub_op, and constant non-denormal input, safe to omit denormal checks
      if (func == (void*)nseel_asm_add_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) >= DENORMAL_CLEARING_THRESHOLD)
      {
        func = nseel_asm_add_op_fast;
      }
      else if (func == (void*)nseel_asm_sub_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) >= DENORMAL_CLEARING_THRESHOLD)
      {
        func = nseel_asm_sub_op_fast;
      }
      // or if mul/div by a fixed value of >= or <= 1.0
      else if (func == (void *)nseel_asm_mul_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) >= 1.0)
      {
        func = nseel_asm_mul_op_fast;
      }
      else if (func == (void *)nseel_asm_div_op && parm1_dv && fabs(op->parms.parms[1]->parms.dv.directValue) <= 1.0)
      {
        func = nseel_asm_div_op_fast;
      }
    }
  } // not varparm

  if (cfunc_abiinfo & (BIF_CLEARDENORMAL | BIF_RETURNSBOOL) ) *canHaveDenormalOutput=0;
  else if (!(cfunc_abiinfo & BIF_WONTMAKEDENORMAL)) *canHaveDenormalOutput=1;

  if (func)
  {
    func = GLUE_realAddress(func,&func_size);
    if (!func) RET_MINUS1_FAIL("failrealladdrfunc")
  }
  else
  {
    func_size = 0;
  }
                   
  if (bufOut_len < parm_size + func_size) RET_MINUS1_FAIL("funcsz")

  if (bufOut)
  {
    unsigned char *p=bufOut + parm_size;
    memcpy(p, func, func_size);
#if GLUE_HAS_FUSE
    parm_size += GLUE_FUSE(ctx,p,parm_size,func_size,fuse_flags,
#if GLUE_MAX_SPILL_REGS > 0
        spill_reg
#else
        -1
#endif
    );
    p = bufOut + parm_size;
#endif
    if (preProc) p=preProc(p,func_size,ctx);
    if (repl)
    {
      if (repl[0]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[0]);
      if (repl[1]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[1]);
      if (repl[2]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[2]);
      if (repl[3]) p=EEL_GLUE_set_immediate(p,(INT_PTR)repl[3]);
    }
  }

  if (restore_stack_amt)
  {
    int rem = restore_stack_amt;
    while (rem > 0)
    {
      int amt = rem;
      if (amt > 4096) amt=4096;
      rem -= amt;

      if (bufOut_len < parm_size + func_size + GLUE_MOVE_STACK_SIZE) RET_MINUS1_FAIL("insufficient size for varparm")
      if (bufOut) GLUE_MOVE_STACK(bufOut + parm_size + func_size, amt);
      parm_size += GLUE_MOVE_STACK_SIZE;
    }
  }

  if (cfunc_abiinfo&BIF_RETURNSONSTACK) *rvMode = RETURNVALUE_FPSTACK;
  else if (cfunc_abiinfo&BIF_RETURNSBOOL) *rvMode=RETURNVALUE_BOOL;

  return parm_size + func_size;
}

static int compileEelFunctionCall(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, 
                                  int *rvMode, int *fpStackUse, int *canHaveDenormalOutput)
{
  int func_size=0, parm_size=0;
  int pn;
  int last_nt_parm=-1,last_nt_parm_mode=0;
  void *func_e=NULL;
  int n_params;
  opcodeRec *parmptrs[NSEEL_MAX_EELFUNC_PARAMETERS];
  int cfp_numparams=-1;
  int cfp_statesize=0;
  EEL_F **cfp_ptrs=NULL;
  int func_raw=0;
  int do_parms;
  int x;

  void *func;

  for (x=0; x < 3; x ++) parmptrs[x] = op->parms.parms[x];

  if (op->opcodeType == OPCODETYPE_FUNCX)
  {
    n_params=0;
    for (x=0;x<3;x++)
    {
      opcodeRec *prni=op->parms.parms[x];
      while (prni && n_params < NSEEL_MAX_EELFUNC_PARAMETERS)
      {
        const int isMP = prni->opcodeType == OPCODETYPE_MOREPARAMS;
        parmptrs[n_params++] = isMP ? prni->parms.parms[0] : prni;
        if (!isMP) break;
        prni = prni->parms.parms[1];
      }
    }
  }
  else 
  {
    n_params = 1 + op->opcodeType - OPCODETYPE_FUNC1;
  }
          
  *fpStackUse = 0;
  func = nseel_getEELFunctionAddress(ctx, op,
                                      &cfp_numparams,&cfp_statesize,&cfp_ptrs, 
                                      computTableSize, 
                                      &func_e, &func_raw,                                              
                                      !!bufOut,namespacePathToThis,rvMode,fpStackUse,canHaveDenormalOutput, parmptrs, n_params);

  if (func_raw) func_size = (int) ((char*)func_e  - (char*)func);
  else if (func) func = GLUE_realAddress(func,&func_size);
  
  if (!func) RET_MINUS1_FAIL("eelfuncaddr")

#if GLUE_MAX_FPSTACK_SIZE > 0
  *fpStackUse += 1;
#endif

  if (cfp_numparams>0 && n_params != cfp_numparams)
  {
    RET_MINUS1_FAIL("eelfuncnp")
  }

  // user defined function
  do_parms = cfp_numparams>0 && cfp_ptrs && cfp_statesize>0;

  // if function local/parameter state is zero, we need to allocate storage for it
  if (cfp_statesize>0 && cfp_ptrs && !cfp_ptrs[0])
  {
    EEL_F *pstate = newDataBlock(sizeof(EEL_F)*cfp_statesize,8);
    if (!pstate) RET_MINUS1_FAIL("eelfuncdb")

    for (pn=0;pn<cfp_statesize;pn++)
    {
      pstate[pn]=0;
      cfp_ptrs[pn] = pstate + pn;
    }
  }


  // first process parameters that are non-trivial
  for (pn=0; pn < n_params; pn++)
  { 
    int needDenorm=0;
    int lsz,sUse=0;                      
    
    if (!parmptrs[pn] || OPCODE_IS_TRIVIAL(parmptrs[pn])) continue; // skip and process after

    if (last_nt_parm >= 0 && do_parms)
    {
      if (last_nt_parm_mode == RETURNVALUE_FPSTACK)
      {
        if (bufOut_len < parm_size + (int)sizeof(GLUE_POP_FPSTACK_TOSTACK)) RET_MINUS1_FAIL("eelfunc_size popfpstacktostack")
        if (bufOut) memcpy(bufOut + parm_size,GLUE_POP_FPSTACK_TOSTACK,sizeof(GLUE_POP_FPSTACK_TOSTACK));
        parm_size+=sizeof(GLUE_POP_FPSTACK_TOSTACK);
      }
      else
      {
        if (bufOut_len < parm_size + (int)sizeof(GLUE_PUSH_P1PTR_AS_VALUE)) RET_MINUS1_FAIL("eelfunc_size pushp1ptrasval")
    
        // push
        if (bufOut) memcpy(bufOut + parm_size,&GLUE_PUSH_P1PTR_AS_VALUE,sizeof(GLUE_PUSH_P1PTR_AS_VALUE));
        parm_size+=sizeof(GLUE_PUSH_P1PTR_AS_VALUE);
      }
    }

    last_nt_parm_mode=0;
    lsz = compileOpcodes(ctx,parmptrs[pn],bufOut ? bufOut + parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis,
      do_parms ? (RETURNVALUE_FPSTACK|RETURNVALUE_NORMAL) : RETURNVALUE_IGNORE,&last_nt_parm_mode,&sUse, &needDenorm);

    // todo: if needDenorm, denorm convert when copying parameter

    if (lsz<0) RET_MINUS1_FAIL("eelfunc, coc fail")

    if (last_nt_parm_mode == RETURNVALUE_FPSTACK) sUse++;
    if (sUse > *fpStackUse) *fpStackUse=sUse;
    parm_size += lsz;

    last_nt_parm = pn;
  }
  // pop non-trivial results into place
  if (last_nt_parm >=0 && do_parms)
  {
    while (--pn >= 0)
    { 
      if (!parmptrs[pn] || OPCODE_IS_TRIVIAL(parmptrs[pn])) continue; // skip and process after
      if (pn == last_nt_parm)
      {
        if (last_nt_parm_mode == RETURNVALUE_FPSTACK)
        {
          // pop to memory directly
          const int cpsize = GLUE_POP_FPSTACK_TO_PTR(NULL,NULL);
          if (bufOut_len < parm_size + cpsize) RET_MINUS1_FAIL("eelfunc size popfpstacktoptr")

          if (bufOut) GLUE_POP_FPSTACK_TO_PTR((unsigned char *)bufOut + parm_size,cfp_ptrs[pn]);
          parm_size += cpsize;
        }
        else
        {
          // copy direct p1ptr to mem
          const int cpsize = GLUE_COPY_VALUE_AT_P1_TO_PTR(NULL,NULL);
          if (bufOut_len < parm_size + cpsize) RET_MINUS1_FAIL("eelfunc size copyvalueatp1toptr")

          if (bufOut) GLUE_COPY_VALUE_AT_P1_TO_PTR((unsigned char *)bufOut + parm_size,cfp_ptrs[pn]);
          parm_size += cpsize;
        }
      }
      else
      {
        const int popsize =  GLUE_POP_VALUE_TO_ADDR(NULL,NULL);
        if (bufOut_len < parm_size + popsize) RET_MINUS1_FAIL("eelfunc size pop value to addr")

        if (bufOut) GLUE_POP_VALUE_TO_ADDR((unsigned char *)bufOut + parm_size,cfp_ptrs[pn]);
        parm_size+=popsize;

      }
    }
  }

  // finally, set any trivial parameters
  if (do_parms)
  {
    const int cpsize = GLUE_MOV_PX_DIRECTVALUE_SIZE + GLUE_COPY_VALUE_AT_P1_TO_PTR(NULL,NULL);
    for (pn=0; pn < n_params; pn++)
    { 
      if (!parmptrs[pn] || !OPCODE_IS_TRIVIAL(parmptrs[pn])) continue; // set trivial values, we already set nontrivials

      if (bufOut_len < parm_size + cpsize) RET_MINUS1_FAIL("eelfunc size trivial set")

      if (bufOut) 
      {
        if (generateValueToReg(ctx,parmptrs[pn],bufOut + parm_size,0,namespacePathToThis, 1)<0) RET_MINUS1_FAIL("eelfunc gvr fail")
        GLUE_COPY_VALUE_AT_P1_TO_PTR(bufOut + parm_size + GLUE_MOV_PX_DIRECTVALUE_SIZE,cfp_ptrs[pn]);
      }
      parm_size += cpsize;

    }
  }

  if (bufOut_len < parm_size + func_size) RET_MINUS1_FAIL("eelfunc size combined")
  
  if (bufOut) memcpy(bufOut + parm_size, func, func_size);

  return parm_size + func_size;
  // end of EEL function generation
}

#ifdef DUMP_OPS_DURING_COMPILE
void dumpOp(compileContext *ctx, opcodeRec *op, int start);
#endif

#ifdef EEL_DUMP_OPS
void dumpOpcodeTree(compileContext *ctx, FILE *fp, opcodeRec *op, int indent_amt)
{
  const char *fname="";
  fprintf(fp,"%*sOP TYPE %d", indent_amt, "",
         op->opcodeType==OPCODETYPE_DIRECTVALUE_TEMPSTRING ? 10000 : // remap around OPCODETYPE_DIRECTVALUE_TEMPSTRING
         op->opcodeType > OPCODETYPE_DIRECTVALUE_TEMPSTRING ? op->opcodeType - 1 : 
         op->opcodeType);

  if ((op->opcodeType == OPCODETYPE_FUNC1 || 
      op->opcodeType == OPCODETYPE_FUNC2 || 
      op->opcodeType == OPCODETYPE_FUNC3 || 
      op->opcodeType == OPCODETYPE_FUNCX))
  {
    if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC)
    {
      functionType *fn_ptr = (functionType *)op->fn;
      fname = fn_ptr->name;
    }
    else if (op->fntype == FUNCTYPE_EELFUNC)
    {
      fname = op->relname;
    }
    if (!fname) fname ="";
  }

  switch (op->opcodeType)
  {
    case OPCODETYPE_DIRECTVALUE:
      fprintf(fp," DV=%f\r\n",op->parms.dv.directValue);
    break;
    case OPCODETYPE_VALUE_FROM_NAMESPACENAME: // this.* or namespace.* are encoded this way
      fprintf(fp," NSN=%s(%d)\r\n",op->relname?op->relname : "(null)",op->namespaceidx);
    break;
    case OPCODETYPE_VARPTR:
      {
        const char *nm = op->relname;
        if (!nm || !*nm)
        {
          int wb; 
          for (wb = 0; wb < ctx->varTable_numBlocks; wb ++)
          {
            char **plist=ctx->varTable_Names[wb];
            if (!plist) break;
  
            if (op->parms.dv.valuePtr >= ctx->varTable_Values[wb] && op->parms.dv.valuePtr < ctx->varTable_Values[wb] + NSEEL_VARS_PER_BLOCK)
            {
              nm = plist[op->parms.dv.valuePtr - ctx->varTable_Values[wb]];
              break;
            }
          }        
        }
        fprintf(fp," VP=%s\r\n", nm?nm : "(null)");
      }
    break;
    case OPCODETYPE_VARPTRPTR:
      fprintf(fp, " VPP?\r\n");
    break;
    case OPCODETYPE_FUNC1:
      if (op->fntype == FN_NOT)
        fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_not");
      else if (op->fntype == FN_NOTNOT)
        fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_notnot");
      else if (op->fntype == FN_MEMORY)
        fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_mem");
      else if (op->fntype == FN_GMEMORY)
        fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_gmem");
      else if (op->fntype == FN_WHILE)
        fprintf(fp," FUNC1 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "while");
      else
        fprintf(fp," FUNC1 %d %s {\r\n",op->fntype, fname);

      if (op->parms.parms[0])
        dumpOpcodeTree(ctx,fp,op->parms.parms[0],indent_amt+2);
      else
        fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,"");
      fprintf(fp,"%*s}\r\n", indent_amt, "");
    break;
    case OPCODETYPE_MOREPARAMS:
    case OPCODETYPE_FUNC2:
      if (op->opcodeType == OPCODETYPE_MOREPARAMS)
        fprintf(fp," MOREPARAMS {\r\n");
      else
      {
        if (op->fntype == FN_POW)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "pow");
        else if (op->fntype == FN_MOD)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_mod");
        else if (op->fntype == FN_XOR)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_xor");
        else if (op->fntype == FN_SHL)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_shl");
        else if (op->fntype == FN_SHR)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_shr");
        else if (op->fntype == FN_LT)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_below");
        else if (op->fntype == FN_GT)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_above");
        else if (op->fntype == FN_LTE)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_beleq");
        else if (op->fntype == FN_GTE)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_aboeq");
        else if (op->fntype == FN_EQ)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_equal");
        else if (op->fntype == FN_NE)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_noteq");
        else if (op->fntype == FN_EQ_EXACT)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_equal_exact");
        else if (op->fntype == FN_NE_EXACT)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_noteq_exact");
        else if (op->fntype == FN_LOGICAL_AND)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_and");
        else if (op->fntype == FN_LOGICAL_OR)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_or");
        else if (op->fntype == FN_ASSIGN)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_set");
        else if (op->fntype == FN_ADD_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_addop");
        else if (op->fntype == FN_SUB_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_subop");
        else if (op->fntype == FN_MUL_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_mulop");
        else if (op->fntype == FN_DIV_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_divop");
        else if (op->fntype == FN_OR_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_orop");
        else if (op->fntype == FN_AND_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_andop");
        else if (op->fntype == FN_XOR_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_xorop");
        else if (op->fntype == FN_MOD_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_modop");
        else if (op->fntype == FN_POW_OP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_powop");
        else if (op->fntype == FN_LOOP)
          fprintf(fp," FUNC2 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "loop");
        else
          fprintf(fp," FUNC2 %d %s {\r\n",op->fntype, fname);
      }
      if (op->parms.parms[0])
        dumpOpcodeTree(ctx,fp,op->parms.parms[0],indent_amt+2);
      else
        fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,"");

      if (op->parms.parms[1])
        dumpOpcodeTree(ctx,fp,op->parms.parms[1],indent_amt+2);
      else
        fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,"");
      fprintf(fp,"%*s}\r\n", indent_amt, "");
    break;
    case OPCODETYPE_FUNCX:
    case OPCODETYPE_FUNC3:
      if (op->opcodeType == OPCODETYPE_FUNCX)
        fprintf(fp," FUNCX %d %s {\r\n",op->fntype, fname);
      else if (op->fntype == FN_IF_ELSE)
        fprintf(fp," FUNC3 %d %s {\r\n",FUNCTYPE_FUNCTIONTYPEREC, "_if");
      else
        fprintf(fp," FUNC3 %d %s {\r\n",op->fntype, fname);
      if (op->parms.parms[0])
        dumpOpcodeTree(ctx,fp,op->parms.parms[0],indent_amt+2);
      else
        fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,"");

      if (op->parms.parms[1])
        dumpOpcodeTree(ctx,fp,op->parms.parms[1],indent_amt+2);
      else
        fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,"");

      if (op->parms.parms[2])
        dumpOpcodeTree(ctx,fp,op->parms.parms[2],indent_amt+2);
      else
        fprintf(fp,"%*sINVALID PARM\r\n",indent_amt+2,"");
      fprintf(fp,"%*s}\r\n", indent_amt, "");

    break;
  }
}

#endif

#ifdef GLUE_MAX_JMPSIZE
#define CHECK_SIZE_FORJMP(x,y) if ((x)<0 || (x)>=GLUE_MAX_JMPSIZE) goto y;
#define RET_MINUS1_FAIL_FALLBACK(err,j) goto j;
#else
#define CHECK_SIZE_FORJMP(x,y)
#define RET_MINUS1_FAIL_FALLBACK(err,j) RET_MINUS1_FAIL(err)
#endif
static int compileOpcodesInternal(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, int *calledRvType, int preferredReturnValues, int *fpStackUse, int *canHaveDenormalOutput)
{
  int rv_offset=0, denormal_force=-1;
  if (!op) RET_MINUS1_FAIL("coi !op")

  *fpStackUse=0;
  for (;;)
  {
    // special case: statement delimiting means we can process the left side into place, and iteratively do the second parameter without recursing
    // also we don't need to save/restore anything to the stack (which the normal 2 parameter function processing does)
    if (op->opcodeType == OPCODETYPE_FUNC2 && op->fntype == FN_JOIN_STATEMENTS)
    {
      int fUse1;
      int parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_IGNORE, NULL,&fUse1,NULL);
      if (parm_size < 0) RET_MINUS1_FAIL("coc join fail")
      op = op->parms.parms[1];
      if (!op) RET_MINUS1_FAIL("join got to null")

      if (fUse1>*fpStackUse) *fpStackUse=fUse1;
      if (bufOut) bufOut += parm_size;
      bufOut_len -= parm_size;
      rv_offset += parm_size;
#ifdef DUMP_OPS_DURING_COMPILE
      if (op->opcodeType != OPCODETYPE_FUNC2 || op->fntype != FN_JOIN_STATEMENTS) dumpOp(ctx,op,0);
#endif
      denormal_force=-1;
    }
    // special case: __denormal_likely(), __denormal_unlikely()
    else if (op->opcodeType == OPCODETYPE_FUNC1 && (op->fntype == FN_DENORMAL_LIKELY || op->fntype == FN_DENORMAL_UNLIKELY))
    {
      denormal_force = op->fntype == FN_DENORMAL_LIKELY;
      op = op->parms.parms[0];
    }
    else 
    {
      break;
    }
  }
  if (denormal_force >= 0 && canHaveDenormalOutput)
  {  
    *canHaveDenormalOutput = denormal_force;
    canHaveDenormalOutput = &denormal_force; // prevent it from being changed by functions below
  }

  // special case: BAND/BOR
  if (op->opcodeType == OPCODETYPE_FUNC2 && (op->fntype == FN_LOGICAL_AND || op->fntype == FN_LOGICAL_OR))
  {
    int fUse1=0;
    int parm_size;
#ifdef GLUE_MAX_JMPSIZE
    int parm_size_pre;
#endif
    int retType=RETURNVALUE_IGNORE;
    if (preferredReturnValues != RETURNVALUE_IGNORE) retType = RETURNVALUE_BOOL;

    *calledRvType = retType;
    
    parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_BOOL, NULL, &fUse1, NULL);
    if (parm_size < 0) RET_MINUS1_FAIL("loop band/bor coc fail")
    
    if (fUse1 > *fpStackUse) *fpStackUse=fUse1;


#ifdef GLUE_MAX_JMPSIZE
    parm_size_pre=parm_size;
#endif

    {
      int sz2, fUse2=0;
      const int testsz=op->fntype == FN_LOGICAL_OR ? sizeof(GLUE_JMP_IF_P1_NZ) : sizeof(GLUE_JMP_IF_P1_Z);
      if (bufOut_len < parm_size+testsz) RET_MINUS1_FAIL_FALLBACK("band/bor size fail",doNonInlinedAndOr_)

      if (bufOut) memcpy(bufOut+parm_size,op->fntype == FN_LOGICAL_OR ? GLUE_JMP_IF_P1_NZ : GLUE_JMP_IF_P1_Z,testsz); 
      parm_size += testsz;

      sz2 = compileOpcodes(ctx,op->parms.parms[1],bufOut?bufOut+parm_size:NULL,bufOut_len-parm_size, computTableSize, namespacePathToThis, retType, NULL,&fUse2, NULL);

      CHECK_SIZE_FORJMP(sz2,doNonInlinedAndOr_)
      if (sz2<0) RET_MINUS1_FAIL("band/bor coc fail")

      if (bufOut) GLUE_JMP_SET_OFFSET(bufOut + parm_size, sz2);
      parm_size+=sz2;

      if (fUse2 > *fpStackUse) *fpStackUse=fUse2;
      return rv_offset + parm_size;
    }
#ifdef GLUE_MAX_JMPSIZE
    if (0) 
    {
      void *stub;
      int stubsize;        
      unsigned char *newblock2, *p;
    
      // encode as function call
doNonInlinedAndOr_:
      parm_size = parm_size_pre;

      if (op->fntype == FN_LOGICAL_AND) 
      {
        stub = GLUE_realAddress(nseel_asm_band,&stubsize);
      }
      else 
      {
        stub = GLUE_realAddress(nseel_asm_bor,&stubsize);
      }
    
      if (bufOut_len < parm_size + stubsize) RET_MINUS1_FAIL("band/bor len fail")
    
      if (bufOut)
      {
        int fUse2=0;
        newblock2 = compileCodeBlockWithRet(ctx,op->parms.parms[1],computTableSize,namespacePathToThis, retType, NULL, &fUse2, NULL);
        if (!newblock2) RET_MINUS1_FAIL("band/bor ccbwr fail")

        if (fUse2 > *fpStackUse) *fpStackUse=fUse2;
    
        p = bufOut + parm_size;
        memcpy(p, stub, stubsize);
    
        p=EEL_GLUE_set_immediate(p,(INT_PTR)newblock2);
      }
      return rv_offset + parm_size + stubsize;
    }
#endif
  }  

  if (op->opcodeType == OPCODETYPE_FUNC3 && op->fntype == FN_IF_ELSE) // special case: IF
  {
    int fUse1=0;
#ifdef GLUE_MAX_JMPSIZE
    int parm_size_pre;
#endif
    int use_rv = RETURNVALUE_IGNORE;
    int rvMode=0;
    int parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_BOOL|RETURNVALUE_BOOL_REVERSED, &rvMode,&fUse1, NULL);
    if (parm_size < 0) RET_MINUS1_FAIL("if coc fail")
    if (fUse1 > *fpStackUse) *fpStackUse=fUse1;

    if (preferredReturnValues & RETURNVALUE_NORMAL) use_rv=RETURNVALUE_NORMAL;
    else if (preferredReturnValues & RETURNVALUE_FPSTACK) use_rv=RETURNVALUE_FPSTACK;
    else if (preferredReturnValues & RETURNVALUE_BOOL) use_rv=RETURNVALUE_BOOL;
    
    *calledRvType = use_rv;
#ifdef GLUE_MAX_JMPSIZE
    parm_size_pre = parm_size;
#endif

    {
      int csz,hasSecondHalf;
      if (rvMode & RETURNVALUE_BOOL_REVERSED)
      {
        if (bufOut_len < parm_size + (int)sizeof(GLUE_JMP_IF_P1_NZ)) RET_MINUS1_FAIL_FALLBACK("if size fail",doNonInlineIf_)
        if (bufOut) memcpy(bufOut+parm_size,GLUE_JMP_IF_P1_NZ,sizeof(GLUE_JMP_IF_P1_NZ));
        parm_size += sizeof(GLUE_JMP_IF_P1_NZ);
      }
      else
      {
        if (bufOut_len < parm_size + (int)sizeof(GLUE_JMP_IF_P1_Z)) RET_MINUS1_FAIL_FALLBACK("if size fail",doNonInlineIf_)
        if (bufOut) memcpy(bufOut+parm_size,GLUE_JMP_IF_P1_Z,sizeof(GLUE_JMP_IF_P1_Z));
        parm_size += sizeof(GLUE_JMP_IF_P1_Z);
      }
      csz=compileOpcodes(ctx,op->parms.parms[1],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, use_rv, NULL,&fUse1, canHaveDenormalOutput);
      if (fUse1 > *fpStackUse) *fpStackUse=fUse1;
      hasSecondHalf = preferredReturnValues || !OPCODE_IS_TRIVIAL(op->parms.parms[2]);

      CHECK_SIZE_FORJMP(csz,doNonInlineIf_)
      if (csz<0) RET_MINUS1_FAIL("if coc fial")

      if (bufOut) GLUE_JMP_SET_OFFSET(bufOut + parm_size, csz + (hasSecondHalf?sizeof(GLUE_JMP_NC):0));
      parm_size+=csz;

      if (hasSecondHalf)
      {
        if (bufOut_len < parm_size + (int)sizeof(GLUE_JMP_NC)) RET_MINUS1_FAIL_FALLBACK("if len fail",doNonInlineIf_)
        if (bufOut) memcpy(bufOut+parm_size,GLUE_JMP_NC,sizeof(GLUE_JMP_NC));
        parm_size+=sizeof(GLUE_JMP_NC);

        csz=compileOpcodes(ctx,op->parms.parms[2],bufOut ? bufOut+parm_size : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, use_rv, NULL, &fUse1, canHaveDenormalOutput);

        CHECK_SIZE_FORJMP(csz,doNonInlineIf_)
        if (csz<0) RET_MINUS1_FAIL("if coc 2 fail")

        // update jump address
        if (bufOut) GLUE_JMP_SET_OFFSET(bufOut + parm_size,csz); 
        parm_size+=csz;       
        if (fUse1 > *fpStackUse) *fpStackUse=fUse1;
      }
      return rv_offset + parm_size;
    }
#ifdef GLUE_MAX_JMPSIZE
    if (0)
    {
      unsigned char *newblock2,*newblock3,*ptr;
      void *stub;
      int stubsize;
doNonInlineIf_:
      parm_size = parm_size_pre;
      stub = GLUE_realAddress(nseel_asm_if,&stubsize);
    
      if (!stub || bufOut_len < parm_size + stubsize) RET_MINUS1_FAIL(stub ? "if sz fail" : "if addr fail")
    
      if (bufOut)
      {
        int fUse2=0;
        newblock2 = compileCodeBlockWithRet(ctx,op->parms.parms[1],computTableSize,namespacePathToThis, use_rv, NULL,&fUse2, canHaveDenormalOutput); 
        if (fUse2 > *fpStackUse) *fpStackUse=fUse2;
        newblock3 = compileCodeBlockWithRet(ctx,op->parms.parms[2],computTableSize,namespacePathToThis, use_rv, NULL,&fUse2, canHaveDenormalOutput);
        if (fUse2 > *fpStackUse) *fpStackUse=fUse2;
        if (!newblock2 || !newblock3) RET_MINUS1_FAIL("if subblock gen fail")
    
        ptr = bufOut + parm_size;
        memcpy(ptr, stub, stubsize);
         
        ptr=EEL_GLUE_set_immediate(ptr,(INT_PTR)newblock2);
        EEL_GLUE_set_immediate(ptr,(INT_PTR)newblock3);
      }
      return rv_offset + parm_size + stubsize;
    }
#endif
  }

  {
    // special case: while
    if (op->opcodeType == OPCODETYPE_FUNC1 && op->fntype == FN_WHILE)
    {
      *calledRvType = RETURNVALUE_BOOL;

#ifndef GLUE_INLINE_LOOPS
      // todo: PPC looping support when loop length is small enough
      {
        unsigned char *pwr=bufOut;
        unsigned char *newblock2;
        int stubsz;
        void *stubfunc = GLUE_realAddress(nseel_asm_repeatwhile,&stubsz);
        if (!stubfunc || bufOut_len < stubsz) RET_MINUS1_FAIL(stubfunc ? "repeatwhile size fail" :"repeatwhile addr fail")

        if (bufOut)
        {
          int fUse1 = 0;
          newblock2=compileCodeBlockWithRet(ctx,op->parms.parms[0],computTableSize,namespacePathToThis, RETURNVALUE_BOOL, NULL, &fUse1, NULL);
          if (!newblock2) RET_MINUS1_FAIL("repeatwhile ccbwr fail")
      
          if (fUse1 > *fpStackUse) *fpStackUse = fUse1;
          memcpy(pwr,stubfunc,stubsz);
          pwr=EEL_GLUE_set_immediate(pwr,(INT_PTR)newblock2); 
        }
      
        return rv_offset+stubsz;
      }
#else
      {
#ifndef GLUE_WHILE_END_NOJUMP
        unsigned char *jzoutpt;
#endif
        unsigned char *looppt;
        int parm_size=0,subsz, fUse1=0;
        if (bufOut_len < parm_size + (int)(GLUE_WHILE_SETUP_SIZE + sizeof(GLUE_WHILE_BEGIN))) RET_MINUS1_FAIL("while size fail 1")

        if (bufOut) memcpy(bufOut + parm_size,GLUE_WHILE_SETUP,GLUE_WHILE_SETUP_SIZE);
        parm_size+=GLUE_WHILE_SETUP_SIZE;

        looppt = bufOut + parm_size;
        if (bufOut) memcpy(bufOut + parm_size,GLUE_WHILE_BEGIN,sizeof(GLUE_WHILE_BEGIN));
        parm_size+=sizeof(GLUE_WHILE_BEGIN);

        subsz = compileOpcodes(ctx,op->parms.parms[0],bufOut ? (bufOut + parm_size) : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, RETURNVALUE_BOOL, NULL,&fUse1, NULL);
        if (fUse1 > *fpStackUse) *fpStackUse = fUse1;
        if (subsz<0) RET_MINUS1_FAIL("while coc fail")

        if (bufOut_len < parm_size + (int)(sizeof(GLUE_WHILE_END) + sizeof(GLUE_WHILE_CHECK_RV))) RET_MINUS1_FAIL("which size fial 2")

        parm_size+=subsz;
        if (bufOut) memcpy(bufOut + parm_size, GLUE_WHILE_END, sizeof(GLUE_WHILE_END));
        parm_size+=sizeof(GLUE_WHILE_END);
#ifndef GLUE_WHILE_END_NOJUMP
        jzoutpt = bufOut + parm_size;
#endif

        if (bufOut) memcpy(bufOut + parm_size, GLUE_WHILE_CHECK_RV, sizeof(GLUE_WHILE_CHECK_RV));
        parm_size+=sizeof(GLUE_WHILE_CHECK_RV);
        if (bufOut) 
        {
          GLUE_JMP_SET_OFFSET(bufOut + parm_size,(looppt - (bufOut+parm_size)) );
#ifndef GLUE_WHILE_END_NOJUMP
          GLUE_JMP_SET_OFFSET(jzoutpt, (bufOut + parm_size) - jzoutpt);
#endif
        }
        return rv_offset+parm_size;
      }

#endif
    }

    // special case: loop
    if (op->opcodeType == OPCODETYPE_FUNC2 && op->fntype == FN_LOOP)
    {
      int fUse1;
      int parm_size = compileOpcodes(ctx,op->parms.parms[0],bufOut,bufOut_len, computTableSize, namespacePathToThis, RETURNVALUE_FPSTACK, NULL,&fUse1, NULL);
      if (parm_size < 0) RET_MINUS1_FAIL("loop coc fail")
      
      *calledRvType = RETURNVALUE_BOOL;
      if (fUse1 > *fpStackUse) *fpStackUse=fUse1;
           
#ifndef GLUE_INLINE_LOOPS
      // todo: PPC looping support when loop length is small enough
      {
        void *stub;
        int stubsize;        
        unsigned char *newblock2, *p;
        stub = GLUE_realAddress(nseel_asm_repeat,&stubsize);
        if (bufOut_len < parm_size + stubsize) RET_MINUS1_FAIL("loop size fail")
        if (bufOut)
        {
          newblock2 = compileCodeBlockWithRet(ctx,op->parms.parms[1],computTableSize,namespacePathToThis, RETURNVALUE_IGNORE, NULL,fpStackUse, NULL);
      
          p = bufOut + parm_size;
          memcpy(p, stub, stubsize);
      
          p=EEL_GLUE_set_immediate(p,(INT_PTR)newblock2);
        }
        return rv_offset + parm_size + stubsize;
      }
#else
      {
        int subsz;
        int fUse2=0;
        unsigned char *skipptr1,*loopdest;

#ifndef GLUE_LOOP_LOADCNT_SIZE
#define GLUE_LOOP_LOADCNT_SIZE sizeof(GLUE_LOOP_LOADCNT)
#endif
        if (bufOut_len < parm_size + (int)(GLUE_LOOP_LOADCNT_SIZE + GLUE_LOOP_CLAMPCNT_SIZE + GLUE_LOOP_BEGIN_SIZE)) RET_MINUS1_FAIL("loop size fail")

        // store, convert to int, compare against 1, if less than, skip to end
        if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_LOADCNT,GLUE_LOOP_LOADCNT_SIZE);
        parm_size += GLUE_LOOP_LOADCNT_SIZE;
        skipptr1 = bufOut+parm_size; 

        // compare aginst max loop length, jump to loop start if not above it
        if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_CLAMPCNT,GLUE_LOOP_CLAMPCNT_SIZE);
        parm_size += GLUE_LOOP_CLAMPCNT_SIZE;

        // loop code:
        loopdest = bufOut + parm_size;

        if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_BEGIN,GLUE_LOOP_BEGIN_SIZE);
        parm_size += GLUE_LOOP_BEGIN_SIZE;

        subsz = compileOpcodes(ctx,op->parms.parms[1],bufOut ? (bufOut + parm_size) : NULL,bufOut_len - parm_size, computTableSize, namespacePathToThis, RETURNVALUE_IGNORE, NULL, &fUse2, NULL);
        if (subsz<0) RET_MINUS1_FAIL("loop coc fail")
        if (fUse2 > *fpStackUse) *fpStackUse=fUse2;

        parm_size += subsz;

        if (bufOut_len < parm_size + (int)sizeof(GLUE_LOOP_END)) RET_MINUS1_FAIL("loop size fail 2")

        if (bufOut) memcpy(bufOut+parm_size,GLUE_LOOP_END,sizeof(GLUE_LOOP_END));
        parm_size += sizeof(GLUE_LOOP_END);
        
        if (bufOut) 
        {
          GLUE_JMP_SET_OFFSET(bufOut + parm_size,loopdest - (bufOut+parm_size));
          GLUE_JMP_SET_OFFSET(skipptr1, (bufOut+parm_size) - skipptr1);
        }

        return rv_offset + parm_size;

      }
#endif
    }    
  }
 
  switch (op->opcodeType)
  {
    case OPCODETYPE_DIRECTVALUE:
        if (preferredReturnValues == RETURNVALUE_BOOL)
        {
          int w = fabs(op->parms.dv.directValue) >= NSEEL_CLOSEFACTOR;
          int wsz=(w?sizeof(GLUE_SET_P1_NZ):sizeof(GLUE_SET_P1_Z));

          *calledRvType = RETURNVALUE_BOOL;
          if (bufOut_len < wsz) RET_MINUS1_FAIL("direct bool size fail3")
          if (bufOut) memcpy(bufOut,w?GLUE_SET_P1_NZ:GLUE_SET_P1_Z,wsz);
          return rv_offset+wsz;
        }
        else if (preferredReturnValues & RETURNVALUE_FPSTACK)
        {
#ifdef GLUE_HAS_FLDZ
          if (op->parms.dv.directValue == 0.0)
          {
#if GLUE_MAX_FPSTACK_SIZE > 0
            if (*fpStackUse < 1) *fpStackUse = 1;
#endif
            *calledRvType = RETURNVALUE_FPSTACK;
            if (bufOut_len < sizeof(GLUE_FLDZ)) RET_MINUS1_FAIL("direct fp fail 1")
            if (bufOut) memcpy(bufOut,GLUE_FLDZ,sizeof(GLUE_FLDZ));
            return rv_offset+sizeof(GLUE_FLDZ);
          }
#endif
#ifdef GLUE_HAS_FLD1
          if (op->parms.dv.directValue == 1.0)
          {
#if GLUE_MAX_FPSTACK_SIZE > 0
            if (*fpStackUse < 1) *fpStackUse = 1;
#endif
            *calledRvType = RETURNVALUE_FPSTACK;
            if (bufOut_len < sizeof(GLUE_FLD1)) RET_MINUS1_FAIL("direct fp fail 1")
            if (bufOut) memcpy(bufOut,GLUE_FLD1,sizeof(GLUE_FLD1));
            return rv_offset+sizeof(GLUE_FLD1);
          }
#endif
        }
        WDL_FALLTHROUGH; // fall through
    case OPCODETYPE_DIRECTVALUE_TEMPSTRING:
    case OPCODETYPE_VALUE_FROM_NAMESPACENAME:
    case OPCODETYPE_VARPTR:
    case OPCODETYPE_VARPTRPTR:

        #if GLUE_HAS_FPREG2 > 0
          if (preferredReturnValues & RETURNVALUE_FPREG2)
          {
            if (preferredReturnValues != RETURNVALUE_FPREG2) RET_MINUS1_FAIL("RETURNVALUE_FPREG2 but not exact hm")
            if (bufOut_len < GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE) RET_MINUS1_FAIL("direct fp2 fail 2")
            if (bufOut)
            {
              if (generateValueToReg(ctx,op,bufOut,-2,namespacePathToThis, 1 /*allow caching*/)<0) RET_MINUS1_FAIL("direct fp2 fail gvr")
            }
            *calledRvType = RETURNVALUE_FPREG2;
            return rv_offset+GLUE_MOV_PX_DIRECTVALUE_TOFPREG2_SIZE;
          }
        #endif

      #ifdef GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE
        if (OPCODE_IS_TRIVIAL(op))
        {
          if (preferredReturnValues & RETURNVALUE_FPSTACK)
          {
#if GLUE_MAX_FPSTACK_SIZE > 0
            if (*fpStackUse < 1) *fpStackUse = 1;
#endif
            if (bufOut_len < GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE) RET_MINUS1_FAIL("direct fp fail 2")
            if (bufOut)
            {
              if (generateValueToReg(ctx,op,bufOut,-1,namespacePathToThis, 1 /*allow caching*/)<0) RET_MINUS1_FAIL("direct fp fail gvr")
            }
            *calledRvType = RETURNVALUE_FPSTACK;
            return rv_offset+GLUE_MOV_PX_DIRECTVALUE_TOSTACK_SIZE;
          }
        }
      #endif

      if (bufOut_len < GLUE_MOV_PX_DIRECTVALUE_SIZE) 
      {
        RET_MINUS1_FAIL("direct value fail 1")
      }
      if (bufOut) 
      {
        if (generateValueToReg(ctx,op,bufOut,0,namespacePathToThis, 
              (preferredReturnValues&(RETURNVALUE_FPSTACK|RETURNVALUE_CACHEABLE))!=0)<0) 
        {
          RET_MINUS1_FAIL("direct value gvr fail3")
        }
      }
    return rv_offset + GLUE_MOV_PX_DIRECTVALUE_SIZE;

    case OPCODETYPE_FUNCX:
    case OPCODETYPE_FUNC1:
    case OPCODETYPE_FUNC2:
    case OPCODETYPE_FUNC3:
      
      if (op->fntype == FUNCTYPE_EELFUNC)
      {
        int a, fpUse1=0;
        
        a = compileEelFunctionCall(ctx,op,bufOut,bufOut_len,computTableSize,namespacePathToThis, calledRvType,&fpUse1,canHaveDenormalOutput);
        if (a<0) return a;
        if (fpUse1 > *fpStackUse) *fpStackUse = fpUse1;
        rv_offset += a;
      }
      else
      {
        int a, fpUse1=0;
        a = compileNativeFunctionCall(ctx,op,bufOut,bufOut_len,computTableSize,namespacePathToThis, calledRvType,&fpUse1,preferredReturnValues,canHaveDenormalOutput);
        if (a<0)return a;
        if (fpUse1 > *fpStackUse) *fpStackUse = fpUse1;
        rv_offset += a;
      }        
    return rv_offset;
  }

  RET_MINUS1_FAIL("default opcode fail")
}

#ifdef DUMP_OPS_DURING_COMPILE
FILE *g_debugfp;
int g_debugfp_indent;
int g_debugfp_histsz=0;

void dumpOp(compileContext *ctx, opcodeRec *op, int start)
{
  if (start>=0)
  {
    if (g_debugfp)
    {
      static opcodeRec **hist;
      
      int x;
      int hit=0;
      if (!hist) hist = (opcodeRec**) calloc(1024,1024*sizeof(opcodeRec*));
      for(x=0;x<g_debugfp_histsz;x++)
      {
        if (hist[x] == op) { hit=1; break; }
      }
      if (x ==g_debugfp_histsz && g_debugfp_histsz<1024*1024) hist[g_debugfp_histsz++] = op;

      if (!start) 
      {
        g_debugfp_indent-=2;
        fprintf(g_debugfp,"%*s}(join)\n",g_debugfp_indent," ");
      }
      if (g_debugfp_indent>=100) *(char *)1=0;
      fprintf(g_debugfp,"%*s{ %p : %d%s: ",g_debugfp_indent," ",op,op->opcodeType, hit ? " -- DUPLICATE" : "");
      switch (op->opcodeType)
      {
        case OPCODETYPE_DIRECTVALUE:
          fprintf(g_debugfp,"dv %f",op->parms.dv.directValue);
        break;
        case OPCODETYPE_VARPTR:
          if (op->relname && op->relname[0])
          {
            fprintf(g_debugfp,"var %s",op->relname);
          }
          else
          {
            int wb; 
            for (wb = 0; wb < ctx->varTable_numBlocks; wb ++)
            {
              char **plist=ctx->varTable_Names[wb];
              if (!plist) break;
    
              if (op->parms.dv.valuePtr >= ctx->varTable_Values[wb] && op->parms.dv.valuePtr < ctx->varTable_Values[wb] + NSEEL_VARS_PER_BLOCK)
              {
                fprintf(g_debugfp,"var %s",plist[op->parms.dv.valuePtr - ctx->varTable_Values[wb]]);
                break;
              }
            }        
          }
        break;
        case OPCODETYPE_FUNC1:
        case OPCODETYPE_FUNC2:
        case OPCODETYPE_FUNC3:
        case OPCODETYPE_FUNCX:
          if (op->fntype == FUNCTYPE_FUNCTIONTYPEREC)
          {
            functionType *p=(functionType*)op->fn;
            fprintf(g_debugfp,"func %d: %s",p->nParams&0xff,p->name);
          }
          else
            fprintf(g_debugfp,"sf %d",op->fntype);
        break;

      }
      fprintf(g_debugfp,"\n");
      g_debugfp_indent+=2;
    }
  }
  else
  {
    if (g_debugfp)
    {
      g_debugfp_indent-=2;
      fprintf(g_debugfp,"%*s}%p\n",g_debugfp_indent," ",op);
    }
  }
}
#endif

int compileOpcodes(compileContext *ctx, opcodeRec *op, unsigned char *bufOut, int bufOut_len, int *computTableSize, const namespaceInformation *namespacePathToThis, 
                   int supportedReturnValues, int *rvType, int *fpStackUse, int *canHaveDenormalOutput)
{
  int code_returns=RETURNVALUE_NORMAL;
  int fpsu=0;
  int codesz;
  int denorm=0;

#ifdef DUMP_OPS_DURING_COMPILE
  dumpOp(ctx,op,1);
#endif
  
  codesz = compileOpcodesInternal(ctx,op,bufOut,bufOut_len,computTableSize,namespacePathToThis,&code_returns, supportedReturnValues,&fpsu,&denorm);
  if (denorm && canHaveDenormalOutput) *canHaveDenormalOutput=1;

#ifdef DUMP_OPS_DURING_COMPILE
  dumpOp(ctx,op,-1);
#endif
#ifdef EEL_DUMP_OPS
      // dump opcode trees for verification, after optimizing
      if (g_eel_dump_fp2)
      {
        fprintf(g_eel_dump_fp2,"-- compileOpcodes generated %d bytes of code!\r\n",codesz);
      }
#endif
  if (codesz < 0) return codesz;

#if GLUE_HAS_FPREG2 > 0
  if (supportedReturnValues & RETURNVALUE_FPREG2)
  {
    if (code_returns != RETURNVALUE_FPREG2)
      RET_MINUS1_FAIL("fpstack2 not return correct fail");
  }
#endif

  /*
  {
    wdl_log("opcode %d %d (%s): fpu use: %d\n",op->opcodeType,op->fntype,
      op->opcodeType >= OPCODETYPE_FUNC1 && op->fntype == FUNCTYPE_FUNCTIONTYPEREC ? (
      ((functionType *)op->fn)->name
      ) : "",
      fpsu);
  }
  */

  if (fpStackUse) *fpStackUse=fpsu;

  if (bufOut) bufOut += codesz;
  bufOut_len -= codesz;


  if (code_returns == RETURNVALUE_BOOL && !(supportedReturnValues & RETURNVALUE_BOOL) && supportedReturnValues)
  {
    int stubsize;
    void *stub = GLUE_realAddress(nseel_asm_booltofp,&stubsize);
    if (!stub || bufOut_len < stubsize) RET_MINUS1_FAIL(stub?"booltofp size":"booltfp addr")
    if (bufOut) 
    {
      memcpy(bufOut,stub,stubsize);
      bufOut += stubsize;
    }
    codesz+=stubsize;
    bufOut_len -= stubsize;
    
    code_returns = RETURNVALUE_FPSTACK;
  }


  // default processing of code_returns to meet return value requirements
  if (supportedReturnValues & code_returns) 
  {
    if (rvType) *rvType = code_returns;
    return codesz;
  }


  if (rvType) *rvType = RETURNVALUE_IGNORE;


  if (code_returns == RETURNVALUE_NORMAL)
  {
    if (supportedReturnValues & (RETURNVALUE_FPSTACK|RETURNVALUE_BOOL))
    {
      if (bufOut_len < GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE) RET_MINUS1_FAIL("pushvalatpxtofpstack,size")
      if (bufOut) 
      {
        GLUE_PUSH_VAL_AT_PX_TO_FPSTACK(bufOut,0); // always fld qword [eax] but we might change that later
        bufOut += GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE;
      }
      codesz += GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE;  
      bufOut_len -= GLUE_PUSH_VAL_AT_PX_TO_FPSTACK_SIZE;

      if (supportedReturnValues & RETURNVALUE_BOOL) 
      {
        code_returns = RETURNVALUE_FPSTACK;
      }
      else
      {
        if (rvType) *rvType = RETURNVALUE_FPSTACK;
      }
    }
  }

  if (code_returns == RETURNVALUE_FPSTACK)
  {
    if (supportedReturnValues & (RETURNVALUE_BOOL|RETURNVALUE_BOOL_REVERSED))
    {
      int stubsize;
      void *stub;
      
      if (supportedReturnValues & RETURNVALUE_BOOL_REVERSED)
      {
        if (rvType) *rvType = RETURNVALUE_BOOL_REVERSED;
        stub = GLUE_realAddress(nseel_asm_fptobool_rev,&stubsize);
      }
      else
      {
        if (rvType) *rvType = RETURNVALUE_BOOL;
        stub = GLUE_realAddress(nseel_asm_fptobool,&stubsize);
      }


      if (!stub || bufOut_len < stubsize) RET_MINUS1_FAIL(stub?"fptobool size":"fptobool addr")
      if (bufOut) 
      {
        memcpy(bufOut,stub,stubsize);
        bufOut += stubsize;
      }
      codesz+=stubsize;
      bufOut_len -= stubsize;
    }
    else if (supportedReturnValues & RETURNVALUE_NORMAL)
    {
      if (computTableSize) (*computTableSize) ++;

      if (bufOut_len < GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE) RET_MINUS1_FAIL("popfpstacktowtptopxsize")

      // generate fp-pop to temp space
      if (bufOut) GLUE_POP_FPSTACK_TO_WTP_TO_PX(bufOut,0);
      codesz+=GLUE_POP_FPSTACK_TO_WTP_TO_PX_SIZE;
      if (rvType) *rvType = RETURNVALUE_NORMAL;
    }
    else
    {
      // toss return value that will be ignored
      if (bufOut_len < GLUE_POP_FPSTACK_SIZE) RET_MINUS1_FAIL("popfpstack size")
      if (bufOut) memcpy(bufOut,GLUE_POP_FPSTACK,GLUE_POP_FPSTACK_SIZE);   
      codesz+=GLUE_POP_FPSTACK_SIZE;
    }
  }

  return codesz;
}


#if 0
static void movestringover(char *str, int amount)
{
  char tmp[1024+8];

  int l=(int)strlen(str);
  l=wdl_min(1024-amount-1,l);

  memcpy(tmp,str,l+1);

  while (l >= 0 && tmp[l]!='\n') l--;
  l++;

  tmp[l]=0;//ensure we null terminate

  memcpy(str+amount,tmp,l+1);
}
#endif

//------------------------------------------------------------------------------
NSEEL_CODEHANDLE NSEEL_code_compile(NSEEL_VMCTX _ctx, const char *_expression, int lineoffs)
{
  return NSEEL_code_compile_ex(_ctx,_expression,lineoffs,0);
}

typedef struct topLevelCodeSegmentRec {
  struct topLevelCodeSegmentRec *_next;
  void *code;
  int codesz;
  int tmptable_use;
} topLevelCodeSegmentRec;


NSEEL_CODEHANDLE NSEEL_code_compile_ex(NSEEL_VMCTX _ctx, const char *_expression, int lineoffs, int compile_flags)
{
  compileContext *ctx = (compileContext *)_ctx;
  const char *endptr;
  const char *_expression_end;
  codeHandleType *handle;
  topLevelCodeSegmentRec *startpts_tail=NULL;
  topLevelCodeSegmentRec *startpts=NULL;
  _codeHandleFunctionRec *oldCommonFunctionList;
  int curtabptr_sz=0;
  void *curtabptr=NULL;
  int had_err=0;

  if (!ctx) return 0;

  ctx->directValueCache=0;
  ctx->optimizeDisableFlags=0;
  ctx->gotEndOfInput=0;
  ctx->current_compile_flags = compile_flags;

  if (compile_flags & NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS_RESET)
  {
    ctx->functions_common=NULL; // reset common function list
  }
  else
  {
    // reset common compiled function code, forcing a recompile if shared
    _codeHandleFunctionRec *a = ctx->functions_common;
    while (a)
    {
      _codeHandleFunctionRec *b = a->derivedCopies;

      if (a->localstorage) 
      {
        // force local storage actual values to be reallocated if used again
        memset(a->localstorage,0,sizeof(EEL_F *) * a->localstorage_size);
      }

      a->startptr = NULL; // force this copy to be recompiled
      a->startptr_size = -1;

      while (b)
      {
        b->startptr = NULL; // force derived copies to get recompiled
        b->startptr_size = -1;
        // no need to reset b->localstorage, since it points to a->localstorage
        b=b->derivedCopies;
      }

      a=a->next;
    }
  }
  
  ctx->last_error_string[0]=0;

  if (!_expression || !*_expression) return 0;

  _expression_end = _expression + strlen(_expression);

  oldCommonFunctionList = ctx->functions_common;

  ctx->isGeneratingCommonFunction=0;
  ctx->isSharedFunctions = !!(compile_flags & NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS);
  ctx->functions_local = NULL;

  freeBlocks(&ctx->tmpblocks,0);
  freeBlocks(&ctx->blocks_head_code,1);
  freeBlocks(&ctx->blocks_head_data,0);
  memset(ctx->l_stats,0,sizeof(ctx->l_stats));

  handle = (codeHandleType*)newDataBlock(sizeof(codeHandleType),8);

  if (!handle) 
  {
    return 0;
  }

  
  memset(handle,0,sizeof(codeHandleType));

  ctx->l_stats[0] += (int)(_expression_end - _expression);
  ctx->tmpCodeHandle = handle;
  endptr=_expression;

  while (*endptr)
  {
    int computTableTop = 0;
    int startptr_size=0;
    void *startptr=NULL;
    opcodeRec *start_opcode=NULL;
    const char *expr=endptr;
    
    int function_numparms=0;
    char is_fname[NSEEL_MAX_VARIABLE_NAMELEN+1];
    is_fname[0]=0;

    memset(ctx->function_localTable_Size,0,sizeof(ctx->function_localTable_Size));
    memset(ctx->function_localTable_Names,0,sizeof(ctx->function_localTable_Names));
    ctx->function_localTable_ValuePtrs=0;
    ctx->function_usesNamespaces=0;
    ctx->function_curName=NULL;
    ctx->function_globalFlag=0;
        
    ctx->errVar=0;

    // single out top level segment
    {
      int had_something = 0, pcnt=0, pcnt2=0;
      int state=0;
      for (;;)
      {
        int l;
        const char *p=nseel_simple_tokenizer(&endptr,_expression_end,&l,&state);
        if (!p) 
        {
          if (pcnt || pcnt2) ctx->gotEndOfInput|=4;
          break;
        }

        if (*p == ';') 
        {
          if (had_something && !pcnt && !pcnt2) break;
        }
        else if (*p == '/' && l > 1 && (p[1] == '/' || p[1] == '*')) 
        {
          if (l > 19 && !strnicmp(p,"//#eel-no-optimize:",19))
            ctx->optimizeDisableFlags = atoi(p+19);
        }
        else
        {
          if (!had_something) 
          {
            expr = p;
            had_something = 1;
          }

          if (*p == '(') pcnt++;
          else if (*p == ')')  {  if (--pcnt<0) pcnt=0; }
          else if (*p == '[') pcnt2++;
          else if (*p == ']')  {  if (--pcnt2<0) pcnt2=0; }
        }
      }
      if (!*expr || !had_something) break;
    }

    // parse   

    {
      int tmplen,funcname_len;
      const char *p = expr;
      const char *tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL);
      const char *funcname = nseel_simple_tokenizer(&p,endptr,&funcname_len,NULL);
      if (tok1 && funcname && tmplen == 8 && !strnicmp(tok1,"function",8) && (isalpha_safe(funcname[0]) || funcname[0] == '_'))
      {
        int had_parms_locals=0;
        if (funcname_len > sizeof(is_fname)-1) funcname_len=sizeof(is_fname)-1;
        memcpy(is_fname, funcname, funcname_len);
        is_fname[funcname_len]=0;
        ctx->function_curName = is_fname; // only assigned for the duration of the loop, cleared later //-V507

        while (NULL != (tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL)))
        {
          int is_parms = 0, localTableContext = 0;
          int maxcnt=0;
          const char *sp_save;

          if (tok1[0] == '(')
          {
            if (had_parms_locals) 
            {
              expr = p-1; // begin compilation at this code!
              break;
            }
            is_parms = 1;
          }
          else
          {
            if (tmplen == 5 && !strnicmp(tok1,"local",tmplen)) localTableContext=0;
            else if (tmplen == 6 && !strnicmp(tok1,"static",tmplen)) localTableContext=0;
            else if (tmplen == 8 && !strnicmp(tok1,"instance",tmplen)) localTableContext=1;
            else if ((tmplen == 7 && !strnicmp(tok1,"globals",tmplen))  ||
                     (tmplen == 6 && !strnicmp(tok1,"global",tmplen)))
            {
              ctx->function_globalFlag = 1;
              localTableContext=2;
            }
            else break; // unknown token!

            tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL);
            if (!tok1 || tok1[0] != '(') break;
          }
          had_parms_locals = 1;


          sp_save=p;

          while (NULL != (tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL)))
          {
            if (tok1[0] == ')') break;
            if (*tok1 == '#' && localTableContext!=1 && localTableContext!=2) 
            {
              ctx->errVar = (int) (tok1 - _expression);
              lstrcpyn_safe(ctx->last_error_string,"#string can only be in instance() or globals()",sizeof(ctx->last_error_string));
              goto had_error;
            }

            if (isalpha_safe(*tok1) || *tok1 == '_' || *tok1 == '#')
            {
              maxcnt++;
              if (p < endptr && *p == '*')
              {
                if (!is_parms && localTableContext!=2)
                {
                  ctx->errVar = (int) (p - _expression);
                  lstrcpyn_safe(ctx->last_error_string,"namespace* can only be used in parameters or globals()",sizeof(ctx->last_error_string));
                  goto had_error;
                }
                p++;
              }
            }
            else if (*tok1 != ',')
            {
              ctx->errVar = (int)(tok1 - _expression);
              lstrcpyn_safe(ctx->last_error_string,"unknown character in function parameters",sizeof(ctx->last_error_string));
              goto had_error;
            }
          }

          if (tok1 && maxcnt > 0)
          {
            char **ot = ctx->function_localTable_Names[localTableContext];
            const int osz = ctx->function_localTable_Size[localTableContext];            
       
            maxcnt += osz;

            ctx->function_localTable_Names[localTableContext] = (char **)newTmpBlock(ctx,sizeof(char *) * maxcnt);

            if (ctx->function_localTable_Names[localTableContext])
            {
              int i=osz;
              if (osz && ot) memcpy(ctx->function_localTable_Names[localTableContext],ot,sizeof(char *) * osz);
              p=sp_save;

              while (NULL != (tok1 = nseel_simple_tokenizer(&p,endptr,&tmplen,NULL)))
              {
                if (tok1[0] == ')') break;
                if (isalpha_safe(*tok1) || *tok1 == '_' || *tok1 == '#')
                {
                  char *newstr;
                  int l = tmplen;
                  if (*p == '*')  // xyz* for namespace
                  {
                    p++;
                    l++;
                  }
                  if (l > NSEEL_MAX_VARIABLE_NAMELEN) l = NSEEL_MAX_VARIABLE_NAMELEN;
                  newstr = newTmpBlock(ctx,l+1);
                  if (newstr)
                  {
                    memcpy(newstr,tok1,l);
                    newstr[l]=0;
                    ctx->function_localTable_Names[localTableContext][i++] = newstr;
                  }
                }
              }
              ctx->function_localTable_Size[localTableContext]=i;
              if (is_parms) function_numparms = i;
            }         
          }
        }
      }
    }
    if (ctx->function_localTable_Size[0]>0)
    {
      ctx->function_localTable_ValuePtrs = 
          ctx->isSharedFunctions ? newDataBlock(ctx->function_localTable_Size[0] * sizeof(EEL_F *),8) : 
                                   newTmpBlock(ctx,ctx->function_localTable_Size[0] * sizeof(EEL_F *)); 
      if (!ctx->function_localTable_ValuePtrs)
      {
        ctx->function_localTable_Size[0]=0;
        function_numparms=0;
      }
      else
      {
        memset(ctx->function_localTable_ValuePtrs,0,sizeof(EEL_F *) * ctx->function_localTable_Size[0]); // force values to be allocated
      }
    }

   {
     int nseelparse(compileContext* context);
     void nseelrestart (void *input_file ,void *yyscanner );

     ctx->rdbuf_start = _expression;

#ifdef NSEEL_SUPER_MINIMAL_LEXER

     ctx->rdbuf = expr;
     ctx->rdbuf_end = endptr;
     if (!nseelparse(ctx) && !ctx->errVar)
     {
       start_opcode = ctx->result;
     }
#else

     nseelrestart(NULL,ctx->scanner);

     ctx->rdbuf = expr;
     ctx->rdbuf_end = endptr;

     if (!nseelparse(ctx) && !ctx->errVar)
     {
       start_opcode = ctx->result;
     }
     if (ctx->errVar)
     {
       const char *p=expr;
       ctx->errVar += expr-_expression;
     }
#endif
     ctx->rdbuf = NULL;
   }
           
    if (start_opcode)
    {
      int rvMode=0, fUse=0;

#ifdef LOG_OPT
      int sd=0;
      wdl_log("pre opt sz=%d (tsackDepth=%d)\n",compileOpcodes(ctx,start_opcode,NULL,1024*1024*256,NULL, NULL,RETURNVALUE_IGNORE,NULL,&sd,NULL),sd);
#endif

#ifdef EEL_DUMP_OPS
      // dump opcode trees for verification, before optimizing
      if (g_eel_dump_fp)
      {
        fprintf(g_eel_dump_fp,"-- opcode chunk --\r\n");
        dumpOpcodeTree(ctx,g_eel_dump_fp,start_opcode,2);        
      }
#endif

      if (!(ctx->optimizeDisableFlags&OPTFLAG_NO_OPTIMIZE)) optimizeOpcodes(ctx,start_opcode,is_fname[0] ? 1 : 0);
#ifdef LOG_OPT
      wdl_log("post opt sz=%d, stack depth=%d\n",compileOpcodes(ctx,start_opcode,NULL,1024*1024*256,NULL,NULL, RETURNVALUE_IGNORE,NULL,&sd,NULL),sd);
#endif

#ifdef EEL_DUMP_OPS
      // dump opcode trees for verification, after optimizing
      if (g_eel_dump_fp2)
      {
        fprintf(g_eel_dump_fp2,"-- POST-OPTIMIZED opcode chunk --\r\n");
        dumpOpcodeTree(ctx,g_eel_dump_fp2,start_opcode,2);        
      }
#endif

      if (is_fname[0])
      {
        _codeHandleFunctionRec *fr = ctx->isSharedFunctions ? newDataBlock(sizeof(_codeHandleFunctionRec),8) : 
                                        newTmpBlock(ctx,sizeof(_codeHandleFunctionRec)); 
        if (fr)
        {
          memset(fr,0,sizeof(_codeHandleFunctionRec));
          fr->startptr_size = -1;
          fr->opcodes = start_opcode;

          if (ctx->function_localTable_Size[0] > 0 && ctx->function_localTable_ValuePtrs)
          {
            if (ctx->function_localTable_Names[0])
            {
              int i;
              for(i=0;i<function_numparms;i++)
              {
                const char *nptr = ctx->function_localTable_Names[0][i];
                if (nptr && *nptr && nptr[strlen(nptr)-1] == '*') 
                {
                  fr->parameterAsNamespaceMask |= ((unsigned int)1)<<i;
                }
              }
            }
            fr->num_params=function_numparms;
            fr->localstorage = ctx->function_localTable_ValuePtrs;
            fr->localstorage_size = ctx->function_localTable_Size[0];
          }

          fr->usesNamespaces = ctx->function_usesNamespaces;
          fr->isCommonFunction = ctx->isSharedFunctions;

          lstrcpyn_safe(fr->fname,is_fname,sizeof(fr->fname));

          if (ctx->isSharedFunctions)
          {
            fr->next = ctx->functions_common;
            ctx->functions_common = fr;
          }
          else
          {
            fr->next = ctx->functions_local;
            ctx->functions_local = fr;
          }         
        }
        continue;
      }

#ifdef DUMP_OPS_DURING_COMPILE
      g_debugfp_indent=0;
      g_debugfp_histsz=0;
      g_debugfp = fopen("C:/temp/foo.txt","w");
#endif
      startptr_size = compileOpcodes(ctx,start_opcode,NULL,1024*1024*256,NULL, NULL, 
        is_fname[0] ? (RETURNVALUE_NORMAL|RETURNVALUE_FPSTACK) : RETURNVALUE_IGNORE, &rvMode, &fUse, NULL); // if not a function, force return value as address (avoid having to pop it ourselves
                                          // if a function, allow the code to decide how return values are generated

#ifdef DUMP_OPS_DURING_COMPILE
      if (g_debugfp) fclose(g_debugfp);
      g_debugfp=0;
#endif


      if (!startptr_size) continue; // optimized away
      if (startptr_size>0)
      {
        startptr = newTmpBlock(ctx,startptr_size);
        if (startptr)
        {
          startptr_size=compileOpcodes(ctx,start_opcode,(unsigned char*)startptr,startptr_size,&computTableTop, NULL, RETURNVALUE_IGNORE, NULL,NULL, NULL);
          if (startptr_size<=0) startptr = NULL;
          
        }
      }
    }

    if (!startptr) 
    {  
had_error:
#ifdef NSEEL_EEL1_COMPAT_MODE
      continue;

#else
      //if (!ctx->last_error_string[0])
      {
        int byteoffs = ctx->errVar;
        int linenumber;
        char cur_err[sizeof(ctx->last_error_string)];
        lstrcpyn_safe(cur_err,ctx->last_error_string,sizeof(cur_err));
        if (cur_err[0]) lstrcatn(cur_err,": ",sizeof(cur_err));
        else lstrcpyn_safe(cur_err,"syntax error: ",sizeof(cur_err));

        if (_expression + byteoffs >= _expression_end) 
        {
          if (ctx->gotEndOfInput&4) byteoffs = (int)(expr-_expression);
          else byteoffs=(int)(_expression_end-_expression);
        }

        if (byteoffs < 0) byteoffs=0;

        linenumber=findLineNumber(_expression,byteoffs)+1;

        if (ctx->gotEndOfInput&4)
        {
          snprintf(ctx->last_error_string,sizeof(ctx->last_error_string),"%d: %.200smissing ) or ]",linenumber+lineoffs,cur_err);
        }
        else
        {
          const char *p = _expression + byteoffs;
          int x=1, right_amt_nospace=0, left_amt_nospace=0;
          while (x < 32 && p-x >= _expression && p[-x] != '\r' && p[-x] != '\n')
          {
            if (!isspace_safe(p[-x])) left_amt_nospace=x;
            x++;
          }
          x=0;
          while (x < 60 && p[x] && p[x] != '\r' && p[x] != '\n')
          {
            if (!isspace_safe(p[x])) right_amt_nospace=x+1;
            x++;
          }

          // display left_amt >>>> right_amt_nospace
          snprintf(ctx->last_error_string,sizeof(ctx->last_error_string),"%d: %.200s'%.*s <!> %.*s'",linenumber+lineoffs,cur_err,
            left_amt_nospace, p-left_amt_nospace,
            right_amt_nospace ? right_amt_nospace : 5,right_amt_nospace ? p : "<eol>");
        }
      }

      startpts=NULL;
      startpts_tail=NULL; 
      had_err=1;
      break; 
#endif
    }
    
    if (!is_fname[0]) // redundant check (if is_fname[0] is set and we succeeded, it should continue)
                      // but we'll be on the safe side
    {
      topLevelCodeSegmentRec *p = newTmpBlock(ctx,sizeof(topLevelCodeSegmentRec));
      p->_next=0;
      p->code = startptr;
      p->codesz = startptr_size;
      p->tmptable_use = computTableTop;
                  
      if (!startpts_tail) startpts_tail=startpts=p;
      else
      {
        startpts_tail->_next=p;
        startpts_tail=p;
      }

      if (curtabptr_sz < computTableTop)
      {
        curtabptr_sz=computTableTop;
      }
    }
  }

  memset(ctx->function_localTable_Size,0,sizeof(ctx->function_localTable_Size));
  memset(ctx->function_localTable_Names,0,sizeof(ctx->function_localTable_Names));
  ctx->function_localTable_ValuePtrs=0;
  ctx->function_usesNamespaces=0;
  ctx->function_curName=NULL;
  ctx->function_globalFlag=0;

  ctx->tmpCodeHandle = NULL;
    
  if (handle->want_stack)
  {
    if (!handle->stack) startpts=NULL;
  }

  if (startpts) 
  {
    curtabptr_sz += 2; // many functions use the worktable for temporary storage of up to 2 EEL_F's

    handle->workTable_size = curtabptr_sz;
    handle->workTable = curtabptr = newDataBlock((curtabptr_sz+MIN_COMPUTABLE_SIZE + COMPUTABLE_EXTRA_SPACE) * sizeof(EEL_F),32);

#ifdef EEL_VALIDATE_WORKTABLE_USE
    if (curtabptr) memset(curtabptr,0x3a,(curtabptr_sz+MIN_COMPUTABLE_SIZE + COMPUTABLE_EXTRA_SPACE) * sizeof(EEL_F));
#endif
    if (!curtabptr) startpts=NULL;
  }


  if (startpts || (!had_err && (compile_flags & NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS)))
  {
    unsigned char *writeptr;
    topLevelCodeSegmentRec *p=startpts;
    int size=sizeof(GLUE_RET)+GLUE_FUNC_ENTER_SIZE+GLUE_FUNC_LEAVE_SIZE; // for ret at end :)
    int wtpos=0;

    // now we build one big code segment out of our list of them, inserting a mov esi, computable before each item as necessary
    while (p)
    {
      if (wtpos <= 0)
      {
        wtpos=MIN_COMPUTABLE_SIZE;
        size += GLUE_RESET_WTP(NULL,0);
      }
      size+=p->codesz;
      wtpos -= p->tmptable_use;
      p=p->_next;
    }
    handle->code = newCodeBlock(size,32);
    if (handle->code)
    {
      writeptr=(unsigned char *)handle->code;
      #if GLUE_FUNC_ENTER_SIZE > 0
        memcpy(writeptr,&GLUE_FUNC_ENTER,GLUE_FUNC_ENTER_SIZE); 
        writeptr += GLUE_FUNC_ENTER_SIZE;
      #endif
      p=startpts;
      wtpos=0;
      while (p)
      {
        if (wtpos <= 0)
        {
          wtpos=MIN_COMPUTABLE_SIZE;
          writeptr+=GLUE_RESET_WTP(writeptr,curtabptr);
        }
        memcpy(writeptr,(char*)p->code,p->codesz);
        writeptr += p->codesz;
        wtpos -= p->tmptable_use;
      
        p=p->_next;
      }
      #if GLUE_FUNC_LEAVE_SIZE > 0
        memcpy(writeptr,&GLUE_FUNC_LEAVE,GLUE_FUNC_LEAVE_SIZE); 
        writeptr += GLUE_FUNC_LEAVE_SIZE;
      #endif
      memcpy(writeptr,&GLUE_RET,sizeof(GLUE_RET)); writeptr += sizeof(GLUE_RET);
      ctx->l_stats[1]=size;
      handle->code_size = (int) (writeptr - (unsigned char *)handle->code);
#if defined(__arm__) || defined(__aarch64__)
      __clear_cache(handle->code,writeptr);
#endif
    }
    
    handle->blocks_code = ctx->blocks_head_code;
#ifndef EEL_DOESNT_NEED_EXEC_PERMS
    eel_set_blocks_allow_execute(handle->blocks_code,1);
#endif
    handle->blocks_data = ctx->blocks_head_data;
    ctx->blocks_head_code=0;
    ctx->blocks_head_data=0;
  }
  else
  {
    // failed compiling, or failed calloc()
    handle=NULL;
  }


  ctx->directValueCache=0;
  ctx->functions_local = NULL;
  
  ctx->isGeneratingCommonFunction=0;
  ctx->isSharedFunctions=0;

  freeBlocks(&ctx->tmpblocks,0);
  freeBlocks(&ctx->blocks_head_code,1);
  freeBlocks(&ctx->blocks_head_data,0);

  if (handle)
  {
    handle->compile_flags = compile_flags;
    handle->ramPtr = ctx->ram_state->blocks;
    memcpy(handle->code_stats,ctx->l_stats,sizeof(ctx->l_stats));
    nseel_evallib_stats[0]+=ctx->l_stats[0];
    nseel_evallib_stats[1]+=ctx->l_stats[1];
    nseel_evallib_stats[2]+=ctx->l_stats[2];
    nseel_evallib_stats[3]+=ctx->l_stats[3];
    nseel_evallib_stats[4]++;
  }
  else
  {
    ctx->functions_common = oldCommonFunctionList; // failed compiling, remove any added common functions from the list

    // remove any derived copies of functions due to error, since we may have added some that have been freed
    while (oldCommonFunctionList)
    {
      oldCommonFunctionList->derivedCopies=NULL;
      oldCommonFunctionList=oldCommonFunctionList->next;
    }
  }
  memset(ctx->l_stats,0,sizeof(ctx->l_stats));

  return (NSEEL_CODEHANDLE)handle;
}

//------------------------------------------------------------------------------
void NSEEL_code_execute(NSEEL_CODEHANDLE code)
{
#ifndef GLUE_TABPTR_IGNORED
  INT_PTR tabptr;
#endif
  INT_PTR codeptr;
  codeHandleType *h = (codeHandleType *)code;
  if (!h || !h->code) return;

  codeptr = (INT_PTR) h->code;
#if 0
  {
	unsigned int *p=(unsigned int *)codeptr;
	while (*p != GLUE_RET[0])
	{
		printf("instr:%04X:%04X\n",*p>>16,*p&0xffff);
		p++;
	}
  }
#endif

#ifndef GLUE_TABPTR_IGNORED
  tabptr=(INT_PTR)h->workTable;
#endif
  //printf("calling code!\n");
  GLUE_CALL_CODE(tabptr,codeptr,(INT_PTR)h->ramPtr);

}

int NSEEL_code_geterror_flag(NSEEL_VMCTX ctx)
{
  compileContext *c=(compileContext *)ctx;
  if (c) return (c->gotEndOfInput ? 1 : 0);
  return 0;
}

char *NSEEL_code_getcodeerror(NSEEL_VMCTX ctx)
{
  compileContext *c=(compileContext *)ctx;
  if (ctx && c->last_error_string[0]) return c->last_error_string;
  return 0;
}

//------------------------------------------------------------------------------
void NSEEL_code_free(NSEEL_CODEHANDLE code)
{
  codeHandleType *h = (codeHandleType *)code;
  if (h != NULL)
  {
#ifdef EEL_VALIDATE_WORKTABLE_USE
    if (h->workTable)
    {
      char *p = ((char*)h->workTable) + h->workTable_size*sizeof(EEL_F);
      int x;
      for(x=COMPUTABLE_EXTRA_SPACE*sizeof(EEL_F) - 1;x >= 0; x --)
        if (p[x] != 0x3a)
        {
          wdl_log("worktable overrun at byte %d (wts=%d), value = %f\n",x,h->workTable_size, *(EEL_F*)(p+(x&~(sizeof(EEL_F)-1))));
          break;
        }
    }
#endif

    nseel_evallib_stats[0]-=h->code_stats[0];
    nseel_evallib_stats[1]-=h->code_stats[1];
    nseel_evallib_stats[2]-=h->code_stats[2];
    nseel_evallib_stats[3]-=h->code_stats[3];
    nseel_evallib_stats[4]--;

    freeBlocks(&h->blocks_code,1);
    freeBlocks(&h->blocks_data,0);
  }

}

//------------------------------------------------------------------------------

NSEEL_VMCTX NSEEL_VM_alloc() // return a handle
{
  compileContext *ctx=calloc(1,sizeof(compileContext));

  #ifdef NSEEL_SUPER_MINIMAL_LEXER
    if (ctx) ctx->scanner = ctx;
  #else
    if (ctx)
    {
      int nseellex_init(void ** ptr_yy_globals);
      void nseelset_extra(void *user_defined , void *yyscanner);
      if (nseellex_init(&ctx->scanner))
      {
        free(ctx);
        return NULL;
      }
      nseelset_extra(ctx,ctx->scanner);
    }
  #endif

  if (ctx) 
  {
    ctx->ram_state = __newBlock_align(&ctx->ctx_pblocks,sizeof(*ctx->ram_state),16,0);
    memset(ctx->ram_state,0,sizeof(*ctx->ram_state));
    ctx->ram_state->sign_mask[0] = ctx->ram_state->sign_mask[1] = WDL_UINT64_CONST(0x8000000000000000);
    ctx->ram_state->abs_mask[0] = ctx->ram_state->abs_mask[1]   = WDL_UINT64_CONST(0x7FFFFFFFFFFFFFFF);
    ctx->ram_state->maxblocks = NSEEL_RAM_BLOCKS_DEFAULTMAX;
    ctx->ram_state->closefact = NSEEL_CLOSEFACTOR;
  }
  return ctx;
}

int NSEEL_VM_setramsize(NSEEL_VMCTX _ctx, int maxent)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (!ctx) return 0;
  if (maxent > 0)
  {
    maxent = (maxent + NSEEL_RAM_ITEMSPERBLOCK - 1)/NSEEL_RAM_ITEMSPERBLOCK;
    if (maxent > NSEEL_RAM_BLOCKS) maxent = NSEEL_RAM_BLOCKS;
    ctx->ram_state->maxblocks = maxent;
  }
  
  return ctx->ram_state->maxblocks * NSEEL_RAM_ITEMSPERBLOCK;
}

void NSEEL_VM_preallocram(NSEEL_VMCTX _ctx, int maxent)
{
  compileContext *ctx = (compileContext *)_ctx;
  int x;
  if (!ctx || !maxent) return;

  if (maxent < 0)
  {
    maxent = ctx->ram_state->maxblocks;
  }
  else
  {
    maxent = (maxent + NSEEL_RAM_ITEMSPERBLOCK - 1)/NSEEL_RAM_ITEMSPERBLOCK;
    if (maxent > ctx->ram_state->maxblocks) maxent = ctx->ram_state->maxblocks;
  }
  for (x = 0; x < maxent; x ++)
    __NSEEL_RAMAlloc(ctx->ram_state->blocks,x * NSEEL_RAM_ITEMSPERBLOCK);
}

void NSEEL_VM_SetFunctionValidator(NSEEL_VMCTX _ctx, const char * (*validateFunc)(const char *fn_name, void *user), void *user)
{
  if (_ctx)
  {
    compileContext *ctx = (compileContext *)_ctx;
    ctx->func_check = validateFunc;
    ctx->func_check_user = user;
  }
}

void NSEEL_VM_SetFunctionTable(NSEEL_VMCTX _ctx, eel_function_table *tab)
{
  if (_ctx)
  {
    compileContext *ctx = (compileContext *)_ctx;
    ctx->registered_func_tab = tab;
  }
}
void NSEEL_VM_free(NSEEL_VMCTX _ctx) // free when done with a VM and ALL of its code have been freed, as well
{

  if (_ctx)
  {
    compileContext *ctx=(compileContext *)_ctx;
    EEL_GROWBUF_RESIZE(&ctx->varNameList,-1);
    NSEEL_VM_freeRAM(_ctx);

    freeBlocks(&ctx->ctx_pblocks,0);

    // these should be 0 normally but just in case
    freeBlocks(&ctx->tmpblocks,0);
    freeBlocks(&ctx->blocks_head_code,1);
    freeBlocks(&ctx->blocks_head_data,0);


    #ifndef NSEEL_SUPER_MINIMAL_LEXER
      if (ctx->scanner)
      {
       int nseellex_destroy(void *yyscanner);
       nseellex_destroy(ctx->scanner);
      }
    #endif
    ctx->scanner=0;
    if (ctx->has_used_global_vars)
    {
      nseel_globalVarItem *p = NULL;
      NSEEL_HOSTSTUB_EnterMutex();
      if (--nseel_vms_referencing_globallist_cnt == 0)
      {
        // clear and free globals
        p = nseel_globalreg_list;
        nseel_globalreg_list=0;
      }
      NSEEL_HOSTSTUB_LeaveMutex();

      while (p)
      {
        nseel_globalVarItem *op = p;
        p=p->_next;
        free(op);
      }
    }
    free(ctx);
  }

}

int *NSEEL_code_getstats(NSEEL_CODEHANDLE code)
{
  codeHandleType *h = (codeHandleType *)code;
  if (h)
  {
    return h->code_stats;
  }
  return 0;
}

void NSEEL_VM_SetStringFunc(NSEEL_VMCTX ctx, 
    EEL_F (*onString)(void *caller_this, struct eelStringSegmentRec *list),
    EEL_F (*onNamedString)(void *caller_this, const char *name))
{
  if (ctx)
  {
    compileContext *c=(compileContext*)ctx;
    c->onString = onString;
    c->onNamedString = onNamedString;
  }
}

void NSEEL_VM_SetCustomFuncThis(NSEEL_VMCTX ctx, void *thisptr)
{
  if (ctx)
  {
    compileContext *c=(compileContext*)ctx;
    c->caller_this=thisptr;
  }
}





void *NSEEL_PProc_RAM(void *data, int data_size, compileContext *ctx)
{
  if (data_size>0) data=EEL_GLUE_set_immediate(data, (INT_PTR)ctx->ram_state->blocks); 
  return data;
}

void *NSEEL_PProc_THIS(void *data, int data_size, compileContext *ctx)
{
  if (data_size>0) data=EEL_GLUE_set_immediate(data, (INT_PTR)ctx->caller_this);
  return data;
}

static int vartable_lowerbound(compileContext *ctx, const char *name, int *ismatch)
{
  int a = 0, c = EEL_GROWBUF_GET_SIZE(&ctx->varNameList);
  varNameRec **list = EEL_GROWBUF_GET(&ctx->varNameList);
  while (a != c)
  {
    const int b = (a+c)/2;
    const int cmp = strnicmp(name,list[b]->str,NSEEL_MAX_VARIABLE_NAMELEN);
    if (cmp > 0) a = b+1;
    else if (cmp < 0) c = b;
    else
    {
      *ismatch = 1;
      return b;
    }
  }
  *ismatch = 0;
  return a;
}

static void vartable_cull_list(compileContext *ctx, int refcnt_chk)
{
  const int ni = EEL_GROWBUF_GET_SIZE(&ctx->varNameList);
  int i = ni, ndel = 0;
  varNameRec **rd = EEL_GROWBUF_GET(&ctx->varNameList), **wr=rd;
  while (i--)
  {
    varNameRec *v = rd[0];
    if ((!refcnt_chk || !v->refcnt) && !v->isreg) 
    {
      ndel++;
    }
    else
    {
      if (wr != rd) *wr = *rd;
      wr++;
    }
    rd++;
  }
  if (ndel) EEL_GROWBUF_RESIZE(&ctx->varNameList,ni - ndel);
}

void NSEEL_VM_remove_unused_vars(NSEEL_VMCTX _ctx)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (ctx) vartable_cull_list(ctx,1);
}

void NSEEL_VM_remove_all_nonreg_vars(NSEEL_VMCTX _ctx)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (ctx) vartable_cull_list(ctx,0);
}

void NSEEL_VM_clear_var_refcnts(NSEEL_VMCTX _ctx)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (ctx)
  {
    int i = EEL_GROWBUF_GET_SIZE(&ctx->varNameList);
    varNameRec **rd = EEL_GROWBUF_GET(&ctx->varNameList);
    while (i--)
    {
      rd[0]->refcnt=0;
      rd++;
    }
  }
}


#ifdef NSEEL_EEL1_COMPAT_MODE
static EEL_F __nseel_global_regs[100];
double *NSEEL_getglobalregs() { return __nseel_global_regs; }
#endif

EEL_F *get_global_var(compileContext *ctx, const char *gv, int addIfNotPresent)
{
  nseel_globalVarItem *p;
#ifdef NSEEL_EEL1_COMPAT_MODE
  if (!strnicmp(gv,"reg",3) && gv[3]>='0' && gv[3] <= '9' && gv[4] >= '0' && gv[4] <= '9' && !gv[5])
  {
    return __nseel_global_regs + atoi(gv+3);
  }
#endif

  NSEEL_HOSTSTUB_EnterMutex(); 
  if (!ctx->has_used_global_vars)
  {
    ctx->has_used_global_vars++;
    nseel_vms_referencing_globallist_cnt++;
  }

  p = nseel_globalreg_list;
  while (p)
  {
    if (!stricmp(p->name,gv)) break;
    p=p->_next;
  }

  if (!p && addIfNotPresent)
  {
    size_t gvl = strlen(gv);
    p = (nseel_globalVarItem*)malloc(sizeof(nseel_globalVarItem) + gvl);
    if (p)
    {
      p->data=0.0;
      strcpy(p->name,gv);
      p->_next = nseel_globalreg_list;
      nseel_globalreg_list=p;
    }
  }
  NSEEL_HOSTSTUB_LeaveMutex(); 
  return p ? &p->data : NULL;
}



EEL_F *nseel_int_register_var(compileContext *ctx, const char *name, int isReg, const char **namePtrOut)
{
  int slot, match;

  if (isReg == 0 && ctx->getVariable)
  {
    EEL_F *ret = ctx->getVariable(ctx->getVariable_userctx, name);
    if (ret) return ret;
  }

  if (!strnicmp(name,"_global.",8) && name[8])
  {
    EEL_F *a=get_global_var(ctx,name+8,isReg >= 0);
    if (a) return a;
  }

  slot = vartable_lowerbound(ctx,name, &match);
  if (match)
  {
    varNameRec *v = EEL_GROWBUF_GET(&ctx->varNameList)[slot];
    if (isReg >= 0)
    {
      v->refcnt++;
      if (isReg) v->isreg=isReg;
      if (namePtrOut) *namePtrOut = v->str;
    }
    return v->value;
  }
  if (isReg < 0) return NULL;

  if (ctx->varValueStore_left<1)
  {
    const int sz=500;
    ctx->varValueStore_left = sz;
    ctx->varValueStore = (EEL_F *)newCtxDataBlock((int)sizeof(EEL_F)*sz,8);
  }
  if (ctx->varValueStore)
  {
    int listsz = EEL_GROWBUF_GET_SIZE(&ctx->varNameList);
    size_t l = strlen(name);
    varNameRec *vh;
    if (l > NSEEL_MAX_VARIABLE_NAMELEN) l = NSEEL_MAX_VARIABLE_NAMELEN;
    vh = (varNameRec*) newCtxDataBlock( (int) (sizeof(varNameRec) + l),8);
    if (!vh || EEL_GROWBUF_RESIZE(&ctx->varNameList, (listsz+1))) return NULL; // alloc fail

    (vh->value = ctx->varValueStore++)[0]=0.0;
    ctx->varValueStore_left--;

    vh->refcnt=1;
    vh->isreg=isReg;
    memcpy(vh->str,name,l);
    vh->str[l] = 0;
    if (namePtrOut) *namePtrOut = vh->str;

    if (slot < listsz)
    {
      memmove(EEL_GROWBUF_GET(&ctx->varNameList) + slot+1, 
              EEL_GROWBUF_GET(&ctx->varNameList) + slot, (listsz - slot) * sizeof(EEL_GROWBUF_GET(&ctx->varNameList)[0]));
    }
    EEL_GROWBUF_GET(&ctx->varNameList)[slot] = vh;

    return vh->value;
  }
  return NULL;
}


//------------------------------------------------------------------------------

void NSEEL_VM_enumallvars(NSEEL_VMCTX ctx, int (*func)(const char *name, EEL_F *val, void *ctx), void *userctx)
{
  compileContext *tctx = (compileContext *) ctx;
  int ni;
  varNameRec **rd;
  if (!tctx) return;
  
  ni = EEL_GROWBUF_GET_SIZE(&tctx->varNameList);
  rd = EEL_GROWBUF_GET(&tctx->varNameList);
  while (ni--)
  {
    if (!func(rd[0]->str,rd[0]->value,userctx)) break;
    rd++;
  }
}


//------------------------------------------------------------------------------
EEL_F *NSEEL_VM_regvar(NSEEL_VMCTX _ctx, const char *var)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (!ctx) return 0;
  
  if (!strnicmp(var,"reg",3) && strlen(var) == 5 && isdigit_safe(var[3]) && isdigit_safe(var[4]))
  {
    EEL_F *a=get_global_var(ctx,var,1);
    if (a) return a;
  }
  
  return nseel_int_register_var(ctx,var,1,NULL);
}

EEL_F *NSEEL_VM_getvar(NSEEL_VMCTX _ctx, const char *var)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (!ctx) return 0;
  
  if (!strnicmp(var,"reg",3) && strlen(var) == 5 && isdigit_safe(var[3]) && isdigit_safe(var[4]))
  {
    EEL_F *a=get_global_var(ctx,var,0);
    if (a) return a;
  }
  
  return nseel_int_register_var(ctx,var,-1,NULL);
}

int  NSEEL_VM_get_var_refcnt(NSEEL_VMCTX _ctx, const char *name)
{
  compileContext *ctx = (compileContext *)_ctx;
  int slot,match;
  if (!ctx) return -1;
  slot = vartable_lowerbound(ctx,name, &match);
  return match ? EEL_GROWBUF_GET(&ctx->varNameList)[slot]->refcnt : -1;
}




opcodeRec *nseel_createFunctionByName(compileContext *ctx, const char *name, int np, opcodeRec *code1, opcodeRec *code2, opcodeRec *code3)
{
  int chkamt=0;
  functionType *f=nseel_getFunctionByName(ctx,name,&chkamt);
  if (f) while (chkamt-->=0)
  {
    if ((f->nParams&FUNCTIONTYPE_PARAMETERCOUNTMASK) == np)
    {
      opcodeRec *o=newOpCode(ctx,NULL, np==3?OPCODETYPE_FUNC3:np==2?OPCODETYPE_FUNC2:OPCODETYPE_FUNC1);
      if (o) 
      {
        o->fntype = FUNCTYPE_FUNCTIONTYPEREC;
        o->fn = f;
        o->parms.parms[0]=code1;
        o->parms.parms[1]=code2;
        o->parms.parms[2]=code3;
      }
      return o;
    }
    f++;
    if (chkamt < 0 || stricmp(f->name,name)) break;
  }
  return NULL;
}




//------------------------------------------------------------------------------
opcodeRec *nseel_translate(compileContext *ctx, const char *tmp, size_t tmplen) // tmplen 0 = null term
{
  // this depends on the string being nul terminated eventually, tmplen is used more as a hint than anything else
  if ((tmp[0] == '0' || tmp[0] == '$') && toupper_safe(tmp[1])=='X')
  {
    char *p;
    return nseel_createCompiledValue(ctx,(EEL_F)strtoul(tmp+2,&p,16));
  }
  else if (tmp[0] == '$')
  {
    if (tmp[1] == '~')
    {
      char *p=(char*)tmp+2;
      unsigned int v=(unsigned int) strtoul(tmp+2,&p,10);
      if (v>53) v=53;
      return nseel_createCompiledValue(ctx,(EEL_F)((((WDL_INT64)1) << v) - 1));
    }
    else if (!tmplen ? !stricmp(tmp,"$E") : (tmplen == 2 && !strnicmp(tmp,"$E",2)))
      return nseel_createCompiledValue(ctx,(EEL_F)2.718281828459045);
    else if (!tmplen ? !stricmp(tmp, "$PI") : (tmplen == 3 && !strnicmp(tmp, "$PI", 3)))
      return nseel_createCompiledValue(ctx,(EEL_F)3.141592653589793);
    else if (!tmplen ? !stricmp(tmp, "$PHI") : (tmplen == 4 && !strnicmp(tmp, "$PHI", 4)))
      return nseel_createCompiledValue(ctx,(EEL_F)1.6180339887498948);
    else if ((!tmplen || tmplen == 4) && tmp[1] == '\'' && tmp[2] && tmp[3] == '\'')
      return nseel_createCompiledValue(ctx,(EEL_F)tmp[2]);      
    else return NULL;
  }
  else if (tmp[0] == '\'')
  {
    char b[64];
    int x,sz;
    unsigned int rv=0;

    if (!tmplen) // nul terminated tmplen, calculate a workable length
    {
      // faster than strlen(tmp) if tmp is large, we'll never need more than ~18 chars anyway
      while (tmplen < 32 && tmp[tmplen]) tmplen++;
    }
    
    sz = tmplen > 0 ? nseel_filter_escaped_string(b,sizeof(b),tmp+1, tmplen - 1, '\'') : 0;
        
    if (sz > 4) 
    {
      if (ctx->last_error_string[0]) lstrcatn(ctx->last_error_string, ", ", sizeof(ctx->last_error_string));
      snprintf_append(ctx->last_error_string,sizeof(ctx->last_error_string),"multi-byte character '%.5s...' too long",b);
      return NULL; // do not allow 'xyzxy', limit to 4 bytes
    }

    for (x=0;x<sz;x++) rv = (rv<<8) + ((unsigned char*)b)[x];
    return nseel_createCompiledValue(ctx,(EEL_F)rv);
  }
  else if (tmp[0] == '#')
  {
    char buf[2048];
    if (!tmplen) while (tmplen < sizeof(buf)-1 && tmp[tmplen]) tmplen++;
    else if (tmplen > sizeof(buf)-1) tmplen = sizeof(buf)-1;
    memcpy(buf,tmp,tmplen);
    buf[tmplen]=0;
    if (ctx->onNamedString) 
    {
      if (tmplen>0 && buf[1]&&ctx->function_curName)
      {
        int err=0;
        opcodeRec *r = nseel_resolve_named_symbol(ctx,nseel_createCompiledValuePtr(ctx,NULL,buf),-1, &err);
        if (r)
        {
          if (r->opcodeType!=OPCODETYPE_VALUE_FROM_NAMESPACENAME) 
          {
            r->opcodeType = OPCODETYPE_DIRECTVALUE;
            r->parms.dv.directValue = ctx->onNamedString(ctx->caller_this,buf+1);
            r->parms.dv.valuePtr=NULL;
          }
          return r;
        }
        if (err) return NULL;
      }

      // if not namespaced symbol, return directly
      if (!buf[1])
      {
        opcodeRec *r=newOpCode(ctx,NULL,OPCODETYPE_DIRECTVALUE_TEMPSTRING);
        if (r) r->parms.dv.directValue = -10000.0;
        return r;
      }
      return nseel_createCompiledValue(ctx,ctx->onNamedString(ctx->caller_this,buf+1));
    }
  }
  return nseel_createCompiledValue(ctx,(EEL_F)atof(tmp));
}

void NSEEL_VM_set_var_resolver(NSEEL_VMCTX _ctx, EEL_F *(*res)(void *userctx, const char *name), void *userctx)
{
  compileContext *ctx = (compileContext *)_ctx;
  if (ctx)
  {
    ctx->getVariable = res;
    ctx->getVariable_userctx = userctx;
  }
}


#if defined(__ppc__) || defined(EEL_TARGET_PORTABLE)
  // blank stubs 
  void eel_enterfp(int s[2]) {}
  void eel_leavefp(int s[2]) {}
#endif
