From fefc306a546c0a2216aa9dc9c45f61efd0305e09 Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@zdoom.fake>
Date: Wed, 16 Sep 2009 01:39:44 +0000
Subject: [PATCH] - Make PClass derive from DObject so that it can participate
 in garbage collection. - Import VM.

SVN r1841 (scripting)
---
 src/d_dehacked.cpp   |   12 +-
 src/dobjgc.cpp       |    9 +
 src/dobjtype.cpp     |   20 +-
 src/dobjtype.h       |   29 +-
 src/v_video.h        |    2 +-
 zdoom.vcproj         |   28 +
 zscript/vm.h         |  791 ++++++++++++++++++++++
 zscript/vmdisasm.cpp |  343 ++++++++++
 zscript/vmexec.cpp   |  185 ++++++
 zscript/vmexec.h     | 1495 ++++++++++++++++++++++++++++++++++++++++++
 zscript/vmframe.cpp  |  294 +++++++++
 zscript/vmops.h      |  210 ++++++
 12 files changed, 3399 insertions(+), 19 deletions(-)
 create mode 100644 zscript/vm.h
 create mode 100644 zscript/vmdisasm.cpp
 create mode 100644 zscript/vmexec.cpp
 create mode 100644 zscript/vmexec.h
 create mode 100644 zscript/vmframe.cpp
 create mode 100644 zscript/vmops.h

diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp
index f63978190..1ac3ff287 100644
--- a/src/d_dehacked.cpp
+++ b/src/d_dehacked.cpp
@@ -149,10 +149,10 @@ static TArray<const PClass *> WeaponNames;
 // List of states that are hacked to use a codepointer
 struct MBFParamState
 {
-	FState * state;
+	FState *state;
 	int pointer;
 };
-static TArray<MBFParamState *> MBFParamStates;
+static TArray<MBFParamState> MBFParamStates;
 // Data on how to correctly modify the codepointers
 struct CodePointerAlias
 {
@@ -1610,9 +1610,9 @@ static void SetPointer(FState *state, PSymbol *sym, int frame = 0)
 		{
 			if (!symname.CompareNoCase(MBFCodePointers[i].name))
 			{
-				MBFParamState * newstate = new MBFParamState;
-				newstate->state = state;
-				newstate->pointer = i;
+				MBFParamState newstate;
+				newstate.state = state;
+				newstate.pointer = i;
 				MBFParamStates.Push(newstate);
 				break; // No need to cycle through the rest of the list.
 			}
@@ -2494,7 +2494,7 @@ static void UnloadDehSupp ()
 		// Handle MBF params here, before the required arrays are cleared
 		for (unsigned int i=0; i < MBFParamStates.Size(); i++)
 		{
-			SetDehParams(MBFParamStates[i]->state, MBFParamStates[i]->pointer);
+			SetDehParams(MBFParamStates[i].state, MBFParamStates[i].pointer);
 		}
 		MBFParamStates.Clear();
 		MBFParamStates.ShrinkToFit();
diff --git a/src/dobjgc.cpp b/src/dobjgc.cpp
index 1c8e618bd..0ba5d7c81 100644
--- a/src/dobjgc.cpp
+++ b/src/dobjgc.cpp
@@ -327,6 +327,15 @@ static void MarkRoot()
 		SectorMarker->SecNum = 0;
 	}
 	Mark(SectorMarker);
+	// Mark symbol tables
+	for (unsigned j = 0; j < PClass::m_Types.Size(); ++j)
+	{
+		PClass *cls = PClass::m_Types[j];
+		if (cls != NULL)
+		{
+			cls->Symbols.MarkSymbols();
+		}
+	}
 	// Mark bot stuff.
 	Mark(bglobal.firstthing);
 	Mark(bglobal.body1);
diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp
index 03eecc7a9..93586bcbf 100644
--- a/src/dobjtype.cpp
+++ b/src/dobjtype.cpp
@@ -467,21 +467,37 @@ const PClass *PClass::NativeClass() const
 
 // Symbol tables ------------------------------------------------------------
 
+IMPLEMENT_ABSTRACT_CLASS(PSymbol);
+IMPLEMENT_CLASS(PSymbolConst);
+IMPLEMENT_CLASS(PSymbolVariable);
+IMPLEMENT_CLASS(PSymbolActionFunction);
+
 PSymbol::~PSymbol()
 {
 }
 
+PSymbolTable::PSymbolTable()
+: ParentSymbolTable(NULL)
+{
+}
+
 PSymbolTable::~PSymbolTable ()
 {
 	ReleaseSymbols();
 }
 
-void PSymbolTable::ReleaseSymbols()
+void PSymbolTable::MarkSymbols()
 {
 	for (unsigned int i = 0; i < Symbols.Size(); ++i)
 	{
-		delete Symbols[i];
+		GC::Mark(Symbols[i]);
 	}
+}
+
+void PSymbolTable::ReleaseSymbols()
+{
+	// The GC will take care of deleting the symbols. We just need to
+	// clear our references to them.
 	Symbols.Clear();
 }
 
diff --git a/src/dobjtype.h b/src/dobjtype.h
index 88defaefe..d58ce2f00 100644
--- a/src/dobjtype.h
+++ b/src/dobjtype.h
@@ -16,8 +16,10 @@ enum ESymbolType
 	SYM_ActionFunction
 };
 
-struct PSymbol
+class PSymbol : public DObject
 {
+	DECLARE_ABSTRACT_CLASS(PSymbol, DObject);
+public:
 	virtual ~PSymbol();
 
 	ESymbolType SymbolType;
@@ -29,8 +31,10 @@ protected:
 
 // A constant value ---------------------------------------------------------
 
-struct PSymbolConst : public PSymbol
+class PSymbolConst : public PSymbol
 {
+	DECLARE_CLASS(PSymbolConst, PSymbol);
+public:
 	int ValueType;
 	union
 	{
@@ -39,17 +43,21 @@ struct PSymbolConst : public PSymbol
 	};
 
 	PSymbolConst(FName name) : PSymbol(name, SYM_Const) {}
+	PSymbolConst() : PSymbol(NAME_None, SYM_Const) {}
 };
 
 // A variable ---------------------------------------------------------
 
-struct PSymbolVariable : public PSymbol
+class PSymbolVariable : public PSymbol
 {
+	DECLARE_CLASS(PSymbolVariable, PSymbol);
+public:
 	FExpressionType ValueType;
 	int size;
 	intptr_t offset;
 
 	PSymbolVariable(FName name) : PSymbol(name, SYM_Variable) {}
+	PSymbolVariable() : PSymbol(NAME_None, SYM_Variable) {}
 };
 
 // An action function -------------------------------------------------------
@@ -74,26 +82,27 @@ struct FState;
 struct StateCallData;
 typedef void (*actionf_p)(AActor *self, AActor *stateowner, FState *state, int parameters, StateCallData *statecall);
 
-struct PSymbolActionFunction : public PSymbol
+class PSymbolActionFunction : public PSymbol
 {
+	DECLARE_CLASS(PSymbolActionFunction, PSymbol);
+public:
 	FString Arguments;
 	actionf_p Function;
 	int defaultparameterindex;
 
 	PSymbolActionFunction(FName name) : PSymbol(name, SYM_ActionFunction) {}
+	PSymbolActionFunction() : PSymbol(NAME_None, SYM_ActionFunction) {}
 };
 
 // A symbol table -----------------------------------------------------------
 
-class PSymbolTable
+struct PSymbolTable
 {
-public:
-	PSymbolTable() : ParentSymbolTable(NULL)
-	{
-	}
-
+	PSymbolTable();
 	~PSymbolTable();
 
+	void MarkSymbols();
+
 	// Sets the table to use for searches if this one doesn't contain the
 	// requested symbol.
 	void SetParentTable (PSymbolTable *parent);
diff --git a/src/v_video.h b/src/v_video.h
index c6857d55a..84a59db50 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -382,7 +382,7 @@ protected:
 	DFrameBuffer () {}
 
 private:
-	DWORD LastMS, LastSec, FrameCount, LastCount, LastTic;
+	uint32 LastMS, LastSec, FrameCount, LastCount, LastTic;
 };
 
 
diff --git a/zdoom.vcproj b/zdoom.vcproj
index 2b0c1b899..a416fa6a8 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -6446,6 +6446,34 @@
 				>
 			</File>
 		</Filter>
+		<Filter
+			Name="ZScript"
+			>
+			<File
+				RelativePath=".\zscript\vm.h"
+				>
+			</File>
+			<File
+				RelativePath=".\zscript\vmdisasm.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\zscript\vmexec.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\zscript\vmexec.h"
+				>
+			</File>
+			<File
+				RelativePath=".\zscript\vmframe.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\zscript\vmops.h"
+				>
+			</File>
+		</Filter>
 	</Files>
 	<Globals>
 	</Globals>
diff --git a/zscript/vm.h b/zscript/vm.h
new file mode 100644
index 000000000..8a8b9e9f8
--- /dev/null
+++ b/zscript/vm.h
@@ -0,0 +1,791 @@
+#ifndef VM_H
+#define VM_H
+
+#include "zstring.h"
+#include "dobject.h"
+
+#define MAX_RETURNS		8	// Maximum number of results a function called by script code can return
+#define MAX_TRY_DEPTH	8	// Maximum number of nested TRYs in a single function
+
+
+typedef unsigned char		VM_UBYTE;
+typedef signed char			VM_SBYTE;
+typedef unsigned short		VM_UHALF;
+typedef signed short		VM_SHALF;
+typedef unsigned int		VM_UWORD;
+typedef signed int			VM_SWORD;
+
+#define VM_EPSILON			(1/1024.0)
+
+enum
+{
+#include "vmops.h"
+NUM_OPS
+};
+
+// Flags for A field of CMPS
+enum
+{
+	CMP_CHECK = 1,
+
+	CMP_EQ = 0,
+	CMP_LT = 2,
+	CMP_LE = 4,
+	CMP_METHOD_MASK = 6,
+
+	CMP_BK = 8,
+	CMP_CK = 16,
+	CMP_APPROX = 32,
+};
+
+// Floating point operations for FLOP
+enum
+{
+	FLOP_ABS,
+	FLOP_NEG,
+	FLOP_ACOS,
+	FLOP_ASIN,
+	FLOP_ATAN,
+	FLOP_COS,
+	FLOP_COSH,
+	FLOP_EXP,
+	FLOP_LOG,
+	FLOP_LOG10,
+	FLOP_SIN,
+	FLOP_SINH,
+	FLOP_TAN,
+	FLOP_TANH,
+	FLOP_SQRT,
+	FLOP_CEIL,
+	FLOP_FLOOR,
+};
+
+// Cast operations
+enum
+{
+	CAST_I2F,
+	CAST_I2S,
+	CAST_F2I,
+	CAST_F2S,
+	CAST_P2S,
+	CAST_S2I,
+	CAST_S2F,
+};
+
+// Register types for VMParam
+enum
+{
+	REGT_INT		= 0,
+	REGT_FLOAT		= 1,
+	REGT_STRING		= 2,
+	REGT_POINTER	= 3,
+	REGT_TYPE		= 3,
+
+	REGT_KONST		= 4,
+	REGT_MULTIREG	= 8,	// (e.g. a vector)
+	REGT_FINAL		= 16,	// used with RET: this is the final return value
+	REGT_ADDROF		= 32,	// used with PARAM: pass address of this register
+
+	REGT_NIL		= 255	// parameter was omitted
+};
+
+// Tags for address registers
+enum
+{
+	ATAG_GENERIC,			// pointer to something; we don't care what
+	ATAG_OBJECT,			// pointer to an object; will be followed by GC
+
+	// The following are all for documentation during debugging and are
+	// functionally no different than ATAG_GENERIC.
+
+	ATAG_FRAMEPOINTER,		// pointer to extra stack frame space for this function
+	ATAG_DREGISTER,			// pointer to a data register
+	ATAG_FREGISTER,			// pointer to a float register
+	ATAG_SREGISTER,			// pointer to a string register
+	ATAG_AREGISTER,			// pointer to an address register
+
+};
+
+class VMFunction : public DObject
+{
+	DECLARE_CLASS(VMFunction, DObject);
+public:
+	bool Native;
+};
+
+enum EVMOpMode
+{
+	MODE_ASHIFT		= 0,
+	MODE_BSHIFT		= 4,
+	MODE_CSHIFT		= 8,
+	MODE_BCSHIFT	= 12,
+
+	MODE_ATYPE		= 15 << MODE_ASHIFT,
+	MODE_BTYPE		= 15 << MODE_BSHIFT,
+	MODE_CTYPE		= 15 << MODE_CSHIFT,
+	MODE_BCTYPE		= 31 << MODE_BCSHIFT,
+
+	MODE_I			= 0,
+	MODE_F,
+	MODE_S,
+	MODE_P,
+	MODE_V,
+	MODE_X,
+	MODE_KI,
+	MODE_KF,
+	MODE_KS,
+	MODE_KP,
+	MODE_KV,
+	MODE_UNUSED,
+	MODE_IMMS,
+	MODE_IMMZ,
+	MODE_JOINT,
+
+	MODE_PARAM,
+	MODE_THROW,
+	MODE_CATCH,
+	MODE_CAST,
+
+	MODE_AI			= MODE_I << MODE_ASHIFT,
+	MODE_AF			= MODE_F << MODE_ASHIFT,
+	MODE_AS			= MODE_S << MODE_ASHIFT,
+	MODE_AP			= MODE_P << MODE_ASHIFT,
+	MODE_AV			= MODE_V << MODE_ASHIFT,
+	MODE_AX			= MODE_X << MODE_ASHIFT,
+	MODE_AKP		= MODE_KP << MODE_ASHIFT,
+	MODE_AUNUSED	= MODE_UNUSED << MODE_ASHIFT,
+	MODE_AIMMS		= MODE_IMMS << MODE_ASHIFT,
+	MODE_AIMMZ		= MODE_IMMZ << MODE_ASHIFT,
+
+	MODE_BI			= MODE_I << MODE_BSHIFT,
+	MODE_BF			= MODE_F << MODE_BSHIFT,
+	MODE_BS			= MODE_S << MODE_BSHIFT,
+	MODE_BP			= MODE_P << MODE_BSHIFT,
+	MODE_BV			= MODE_V << MODE_BSHIFT,
+	MODE_BX			= MODE_X << MODE_BSHIFT,
+	MODE_BKI		= MODE_KI << MODE_BSHIFT,
+	MODE_BKF		= MODE_KF << MODE_BSHIFT,
+	MODE_BKS		= MODE_KS << MODE_BSHIFT,
+	MODE_BKP		= MODE_KP << MODE_BSHIFT,
+	MODE_BKV		= MODE_KV << MODE_BSHIFT,
+	MODE_BUNUSED	= MODE_UNUSED << MODE_BSHIFT,
+	MODE_BIMMS		= MODE_IMMS << MODE_BSHIFT,
+	MODE_BIMMZ		= MODE_IMMZ << MODE_BSHIFT,
+
+	MODE_CI			= MODE_I << MODE_CSHIFT,
+	MODE_CF			= MODE_F << MODE_CSHIFT,
+	MODE_CS			= MODE_S << MODE_CSHIFT,
+	MODE_CP			= MODE_P << MODE_CSHIFT,
+	MODE_CV			= MODE_V << MODE_CSHIFT,
+	MODE_CX			= MODE_X << MODE_CSHIFT,
+	MODE_CKI		= MODE_KI << MODE_CSHIFT,
+	MODE_CKF		= MODE_KF << MODE_CSHIFT,
+	MODE_CKS		= MODE_KS << MODE_CSHIFT,
+	MODE_CKP		= MODE_KP << MODE_CSHIFT,
+	MODE_CKV		= MODE_KV << MODE_CSHIFT,
+	MODE_CUNUSED	= MODE_UNUSED << MODE_CSHIFT,
+	MODE_CIMMS		= MODE_IMMS << MODE_CSHIFT,
+	MODE_CIMMZ		= MODE_IMMZ << MODE_CSHIFT,
+
+	MODE_BCJOINT	= (MODE_JOINT << MODE_BSHIFT) | (MODE_JOINT << MODE_CSHIFT),
+	MODE_BCKI		= MODE_KI << MODE_BCSHIFT,
+	MODE_BCKF		= MODE_KF << MODE_BCSHIFT,
+	MODE_BCKS		= MODE_KS << MODE_BCSHIFT,
+	MODE_BCKP		= MODE_KP << MODE_BCSHIFT,
+	MODE_BCIMMS		= MODE_IMMS << MODE_BCSHIFT,
+	MODE_BCIMMZ		= MODE_IMMZ << MODE_BCSHIFT,
+	MODE_BCPARAM	= MODE_PARAM << MODE_BCSHIFT,
+	MODE_BCTHROW	= MODE_THROW << MODE_BCSHIFT,
+	MODE_BCCATCH	= MODE_CATCH << MODE_BCSHIFT,
+	MODE_BCCAST		= MODE_CAST << MODE_BCSHIFT,
+
+	MODE_ABCJOINT	= (MODE_JOINT << MODE_ASHIFT) | MODE_BCJOINT,
+};
+
+struct VMOpInfo
+{
+	const char *Name;
+	int Mode;
+};
+
+extern const VMOpInfo OpInfo[NUM_OPS];
+
+struct VMReturn
+{
+	void *Location;
+	VM_SHALF RegNum;	// Used to find ObjFlag index for pointers; set negative if the caller is native code and doesn't care
+	VM_UBYTE RegType;	// Same as VMParam RegType, except REGT_KONST is invalid; only used by asserts
+
+	void SetInt(int val)
+	{
+		*(int *)Location = val;
+	}
+	void SetFloat(double val)
+	{
+		*(double *)Location = val;
+	}
+	void SetVector(const double val[3])
+	{
+		((double *)Location)[0] = val[0];
+		((double *)Location)[1] = val[1];
+		((double *)Location)[2] = val[2];
+	}
+	void SetString(const FString &val)
+	{
+		*(FString *)Location = val;
+	}
+	void SetPointer(void *val)
+	{
+		*(void **)Location = val;
+	}
+
+	void IntAt(int *loc)
+	{
+		Location = loc;
+		RegNum = -1;
+		RegType = REGT_INT;
+	}
+	void FloatAt(double *loc)
+	{
+		Location = loc;
+		RegNum = -1;
+		RegType = REGT_FLOAT;
+	}
+	void StringAt(FString *loc)
+	{
+		Location = loc;
+		RegNum = -1;
+		RegType = REGT_STRING;
+	}
+	void PointerAt(void **loc)
+	{
+		Location = loc;
+		RegNum = -1;
+		RegType = REGT_POINTER;
+	}
+};
+
+struct VMRegisters;
+
+
+struct VMValue
+{
+	union
+	{
+		int i;
+		struct { void *a; int atag; };
+		double f;
+		struct { int pad[3]; int Type; };
+		struct { int foo[4]; } biggest;
+	};
+
+	// Unfortunately, FString cannot be used directly.
+	// Fortunately, it is relatively simple.
+	FString &s() { return *(FString *)&a; }
+	const FString &s() const { return *(FString *)&a; }
+
+	VMValue()
+	{
+		a = NULL;
+		Type = REGT_NIL;
+	}
+	~VMValue()
+	{
+		Kill();
+	}
+	VMValue(const VMValue &o)
+	{
+		biggest = o.biggest;
+		if (Type == REGT_STRING)
+		{
+			::new(&s()) FString(o.s());
+		}
+	}
+	VMValue(int v)
+	{
+		i = v;
+		Type = REGT_INT;
+	}
+	VMValue(double v)
+	{
+		f = v;
+		Type = REGT_FLOAT;
+	}
+	VMValue(const char *s)
+	{
+		::new(&a) FString(s);
+		Type = REGT_STRING;
+	}
+	VMValue(const FString &s)
+	{
+		::new(&a) FString(s);
+		Type = REGT_STRING;
+	}
+	VMValue(DObject *v)
+	{
+		a = v;
+		atag = ATAG_OBJECT;
+		Type = REGT_POINTER;
+	}
+	VMValue(void *v)
+	{
+		a = v;
+		atag = ATAG_GENERIC;
+		Type = REGT_POINTER;
+	}
+	VMValue(void *v, int tag)
+	{
+		a = v;
+		atag = tag;
+		Type = REGT_POINTER;
+	}
+	VMValue &operator=(const VMValue &o)
+	{
+		if (o.Type == REGT_STRING)
+		{
+			if (Type == REGT_STRING)
+			{
+				s() = o.s();
+			}
+			else
+			{
+				new(&s()) FString(o.s());
+				Type = REGT_STRING;
+			}
+		}
+		else
+		{
+			Kill();
+			biggest = o.biggest;
+		}
+		return *this;
+	}
+	VMValue &operator=(int v)
+	{
+		Kill();
+		i = v;
+		Type = REGT_INT;
+		return *this;
+	}
+	VMValue &operator=(double v)
+	{
+		Kill();
+		f = v;
+		Type = REGT_FLOAT;
+		return *this;
+	}
+	VMValue &operator=(const FString &v)
+	{
+		if (Type == REGT_STRING)
+		{
+			s() = v;
+		}
+		else
+		{
+			::new(&s()) FString(v);
+			Type = REGT_STRING;
+		}
+		return *this;
+	}
+	VMValue &operator=(const char *v)
+	{
+		if (Type == REGT_STRING)
+		{
+			s() = v;
+		}
+		else
+		{
+			::new(&s()) FString(v);
+			Type = REGT_STRING;
+		}
+		return *this;
+	}
+	VMValue &operator=(DObject *v)
+	{
+		Kill();
+		a = v;
+		atag = ATAG_OBJECT;
+		Type = REGT_POINTER;
+		return *this;
+	}
+	VMValue &operator=(void *v)
+	{
+		Kill();
+		a = v;
+		atag = ATAG_GENERIC;
+		Type = REGT_POINTER;
+		return *this;
+	}
+	void SetNil()
+	{
+		Kill();
+		Type = REGT_NIL;
+	}
+	bool operator==(const VMValue &o)
+	{
+		return Test(o) == 0;
+	}
+	bool operator!=(const VMValue &o)
+	{
+		return Test(o) != 0;
+	}
+	bool operator< (const VMValue &o)
+	{
+		return Test(o) <  0;
+	}
+	bool operator<=(const VMValue &o)
+	{
+		return Test(o) <= 0;
+	}
+	bool operator> (const VMValue &o)
+	{
+		return Test(o) >  0;
+	}
+	bool operator>=(const VMValue &o)
+	{
+		return Test(o) >= 0;
+	}
+	int Test(const VMValue &o, int inexact=false)
+	{
+		double diff;
+
+		if (Type == o.Type)
+		{
+			switch(Type)
+			{
+			case REGT_NIL:
+				return 0;
+
+			case REGT_INT:
+				return i - o.i;
+
+			case REGT_FLOAT:
+				diff = f - o.f;
+do_double:		if (inexact)
+				{
+					return diff < -VM_EPSILON ? -1 : diff > VM_EPSILON ? 1 : 0;
+				}
+				return diff < 0 ? -1 : diff > 0 ? 1 : 0;
+
+			case REGT_STRING:
+				return inexact ? s().CompareNoCase(o.s()) : s().Compare(o.s());
+
+			case REGT_POINTER:
+				return int((const VM_UBYTE *)a - (const VM_UBYTE *)o.a);
+			}
+			assert(0);		// Should not get here
+			return 2;
+		}
+		if (Type == REGT_FLOAT && o.Type == REGT_INT)
+		{
+			diff = f - o.i;
+			goto do_double;
+		}
+		if (Type == REGT_INT && o.Type == REGT_FLOAT)
+		{
+			diff = i - o.f;
+			goto do_double;
+		}
+		// Bad comparison
+		return 2;
+	}
+	FString ToString()
+	{
+		if (Type == REGT_STRING)
+		{
+			return s();
+		}
+		else if (Type == REGT_NIL)
+		{
+			return "nil";
+		}
+		FString t;
+		if (Type == REGT_INT)
+		{
+			t.Format ("%d", i);
+		}
+		else if (Type == REGT_FLOAT)
+		{
+			t.Format ("%.14g", f);
+		}
+		else if (Type == REGT_POINTER)
+		{
+			// FIXME
+			t.Format ("Object: %p", a);
+		}
+		return t;
+	}
+	int ToInt()
+	{
+		if (Type == REGT_INT)
+		{
+			return i;
+		}
+		if (Type == REGT_FLOAT)
+		{
+			return int(f);
+		}
+		if (Type == REGT_STRING)
+		{
+			return s().ToLong();
+		}
+		// FIXME
+		return 0;
+	}
+	double ToDouble()
+	{
+		if (Type == REGT_FLOAT)
+		{
+			return f;
+		}
+		if (Type == REGT_INT)
+		{
+			return i;
+		}
+		if (Type == REGT_STRING)
+		{
+			return s().ToDouble();
+		}
+		// FIXME
+		return 0;
+	}
+	void Kill()
+	{
+		if (Type == REGT_STRING)
+		{
+			s().~FString();
+		}
+	}
+};
+
+// VM frame layout:
+//	VMFrame header
+//  parameter stack		- 16 byte boundary, 16 bytes each
+//	double registers	- 8 bytes each
+//	string registers	- 4 or 8 bytes each
+//	address registers	- 4 or 8 bytes each
+//	data registers		- 4 bytes each
+//	address register tags-1 byte each
+//	extra space			- 16 byte boundary
+struct VMFrame
+{
+	VMFrame *ParentFrame;
+	VMFunction *Func;
+	VM_UBYTE NumRegD;
+	VM_UBYTE NumRegF;
+	VM_UBYTE NumRegS;
+	VM_UBYTE NumRegA;
+	VM_UHALF MaxParam;
+	VM_UHALF NumParam;		// current number of parameters
+
+	static int FrameSize(int numregd, int numregf, int numregs, int numrega, int numparam, int numextra)
+	{
+		int size = (sizeof(VMFrame) + 15) & ~15;
+		size += numparam * sizeof(VMValue);
+		size += numregf * sizeof(double);
+		size += numrega * (sizeof(void *) + sizeof(VM_UBYTE));
+		size += numregs * sizeof(FString);
+		size += numregd * sizeof(int);
+		if (numextra != 0)
+		{
+			size = (size + 15) & ~15;
+			size += numextra;
+		}
+		return size;
+	}
+
+	int *GetRegD() const
+	{
+		return (int *)(GetRegA() + NumRegA);
+	}
+
+	double *GetRegF() const
+	{
+		return (double *)(GetParam() + MaxParam);
+	}
+
+	FString *GetRegS() const
+	{
+		return (FString *)(GetRegF() + NumRegF);
+	}
+
+	void **GetRegA() const
+	{
+		return (void **)(GetRegS() + NumRegS);
+	}
+
+	VM_UBYTE *GetRegATag() const
+	{
+		return (VM_UBYTE *)(GetRegD() + NumRegD);
+	}
+
+	VMValue *GetParam() const
+	{
+		return (VMValue *)(((size_t)(this + 1) + 15) & ~15);
+	}
+
+	void *GetExtra() const
+	{
+		VM_UBYTE *ptag = GetRegATag();
+		ptrdiff_t ofs = ptag - (VM_UBYTE *)this;
+		return (VM_UBYTE *)this + ((ofs + NumRegA + 15) & ~15);
+	}
+
+	void GetAllRegs(int *&d, double *&f, FString *&s, void **&a, VM_UBYTE *&atag, VMValue *&param) const
+	{
+		// Calling the individual functions produces suboptimal code. :(
+		param = GetParam();
+		f = (double *)(param + MaxParam);
+		s = (FString *)(f + NumRegF);
+		a = (void **)(s + NumRegS);
+		d = (int *)(a + NumRegA);
+		atag = (VM_UBYTE *)(d + NumRegD);
+	}
+
+	void InitRegS();
+};
+
+struct VMRegisters
+{
+	VMRegisters(const VMFrame *frame)
+	{
+		frame->GetAllRegs(d, f, s, a, atag, param);
+	}
+
+	VMRegisters(const VMRegisters &o)
+		: d(o.d), f(o.f), s(o.s), a(o.a), atag(o.atag), param(o.param)
+	{ }
+
+	int *d;
+	double *f;
+	FString *s;
+	void **a;
+	VM_UBYTE *atag;
+	VMValue *param;
+};
+
+struct VMException : public DObject
+{
+	DECLARE_CLASS(VMFunction, DObject);
+};
+
+class VMScriptFunction : public VMFunction
+{
+	DECLARE_CLASS(VMScriptFunction, VMFunction);
+public:
+	const VM_UBYTE *Code;
+	int *KonstD;
+	double *KonstF;
+	FString *KonstS;
+	void **KonstA;
+	int ExtraSpace;
+	int NumCodeBytes;
+	VM_UBYTE NumRegD;
+	VM_UBYTE NumRegF;
+	VM_UBYTE NumRegS;
+	VM_UBYTE NumRegA;
+	VM_UBYTE NumKonstD;
+	VM_UBYTE NumKonstF;
+	VM_UBYTE NumKonstS;
+	VM_UBYTE NumKonstA;
+	VM_UHALF MaxParam;		// Maximum number of parameters this function has on the stack at once
+	VM_UBYTE NumArgs;		// Number of arguments this function takes
+};
+
+class VMFrameStack
+{
+public:
+	VMFrameStack();
+	~VMFrameStack();
+	VMFrame *AllocFrame(int numregd, int numregf, int numregs, int numrega);
+	VMFrame *AllocFrame(VMScriptFunction *func);
+	VMFrame *PopFrame();
+	VMFrame *TopFrame()
+	{
+		assert(Blocks != NULL && Blocks->LastFrame != NULL);
+		return Blocks->LastFrame;
+	}
+	int Call(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults, VMException **trap=NULL);
+private:
+	enum { BLOCK_SIZE = 4096 };		// Default block size
+	struct BlockHeader
+	{
+		BlockHeader *NextBlock;
+		VMFrame *LastFrame;
+		VM_UBYTE *FreeSpace;
+		int BlockSize;
+	};
+	BlockHeader *Blocks;
+	BlockHeader *UnusedBlocks;
+	VMFrame *Alloc(int size);
+};
+
+class VMNativeFunction : public VMFunction
+{
+	DECLARE_CLASS(VMNativeFunction, VMFunction);
+public:
+	// Return value is the number of results.
+	int (*NativeCall)(VMFrameStack *stack, VMValue *param, int numparam, VMReturn *ret, int numret);
+};
+
+
+class VMParamFiller
+{
+public:
+	VMParamFiller(const VMFrame *frame) : Reg(frame), RegD(0), RegF(0), RegS(0), RegA(0) {}
+	VMParamFiller(const VMRegisters *reg) : Reg(*reg), RegD(0), RegF(0), RegS(0), RegA(0) {}
+
+	void ParamInt(int val)
+	{
+		Reg.d[RegD++] = val;
+	}
+
+	void ParamFloat(double val)
+	{
+		Reg.f[RegF++] = val;
+	}
+
+	void ParamString(FString &val)
+	{
+		Reg.s[RegS++] = val;
+	}
+
+	void ParamString(const char *val)
+	{
+		Reg.s[RegS++] = val;
+	}
+
+	void ParamObject(DObject *obj)
+	{
+		Reg.a[RegA] = obj;
+		Reg.atag[RegA] = true;
+		RegA++;
+	}
+
+	void ParamPointer(void *ptr)
+	{
+		Reg.a[RegA] = ptr;
+		Reg.atag[RegA] = false;
+		RegA++;
+	}
+
+private:
+	const VMRegisters Reg;
+	int RegD, RegF, RegS, RegA;
+};
+
+
+enum EVMEngine
+{
+	VMEngine_Default,
+	VMEngine_Unchecked,
+	VMEngine_Checked
+};
+
+void VMSelectEngine(EVMEngine engine);
+extern int (*VMExec)(VMFrameStack *stack, const VM_UBYTE *pc, VMReturn *ret, int numret);
+void VMFillParams(VMValue *params, VMFrame *callee, int numparam);
+
+void VMDisasm(const VM_UBYTE *code, int codesize, const VMScriptFunction *func);
+
+#endif
diff --git a/zscript/vmdisasm.cpp b/zscript/vmdisasm.cpp
new file mode 100644
index 000000000..7ca2a3dc3
--- /dev/null
+++ b/zscript/vmdisasm.cpp
@@ -0,0 +1,343 @@
+#include "vm.h"
+
+#define LI		MODE_AI | MODE_BCJOINT | MODE_BCIMMS
+#define LKI		MODE_AI | MODE_BCJOINT | MODE_BCKI
+#define LKF		MODE_AF | MODE_BCJOINT | MODE_BCKF
+#define LKS		MODE_AS | MODE_BCJOINT | MODE_BCKS
+#define LKP		MODE_AP | MODE_BCJOINT | MODE_BCKP
+#define LFP		MODE_AP | MODE_BUNUSED | MODE_CUNUSED
+
+#define RIRPKI	MODE_AI | MODE_BP | MODE_CKI
+#define RIRPRI	MODE_AI | MODE_BP | MODE_CI
+#define RFRPKI	MODE_AF | MODE_BP | MODE_CKI
+#define RFRPRI	MODE_AF | MODE_BP | MODE_CI
+#define RSRPKI	MODE_AS | MODE_BP | MODE_CKI
+#define RSRPRI	MODE_AS | MODE_BP | MODE_CI
+#define RPRPKI	MODE_AP | MODE_BP | MODE_CKI
+#define RPRPRI	MODE_AP | MODE_BP | MODE_CI
+#define RVRPKI	MODE_AV | MODE_BP | MODE_CKI
+#define RVRPRI	MODE_AV | MODE_BP | MODE_CI
+#define RIRPI8	MODE_AI | MODE_BP | MODE_CIMMZ
+
+#define RPRIKI	MODE_AP | MODE_BI | MODE_CKI
+#define RPRIRI	MODE_AP | MODE_BI | MODE_CI
+#define RPRFKI	MODE_AP | MODE_BF | MODE_CKI
+#define RPRFRI	MODE_AP | MODE_BF | MODE_CI
+#define RPRSKI	MODE_AP | MODE_BS | MODE_CKI
+#define RPRSRI	MODE_AP | MODE_BS | MODE_CI
+#define RPRPKI	MODE_AP | MODE_BP | MODE_CKI
+#define RPRPRI	MODE_AP | MODE_BP | MODE_CI
+#define RPRVKI	MODE_AP | MODE_BV | MODE_CKI
+#define RPRVRI	MODE_AP | MODE_BV | MODE_CI
+#define RPRII8	MODE_AP | MODE_BI | MODE_CIMMZ
+
+#define RIRI	MODE_AI | MODE_BI | MODE_CUNUSED
+#define RFRF	MODE_AF | MODE_BF | MODE_CUNUSED
+#define	RSRS	MODE_AS | MODE_BS | MODE_CUNUSED
+#define RPRP	MODE_AP | MODE_BP | MODE_CUNUSED
+#define RXRXI8	MODE_AX | MODE_BX | MODE_CIMMZ
+#define RPRPRP	MODE_AP | MODE_BP | MODE_CP
+#define RPRPKP	MODE_AP | MODE_BP | MODE_CKP
+
+#define RII16	MODE_AI | MODE_BCJOINT | MODE_BCIMMS
+#define I24		MODE_ABCJOINT
+#define I8		MODE_AIMMZ | MODE_BUNUSED | MODE_CUNUSED
+#define __BCP	MODE_AUNUSED | MODE_BCJOINT | MODE_BCPARAM
+#define RPI8I8	MODE_AP | MODE_BIMMZ | MODE_CIMMZ
+#define KPI8I8	MODE_AKP | MODE_BIMMZ | MODE_CIMMZ
+#define I8BCP	MODE_AIMMZ | MODE_BCJOINT | MODE_BCPARAM
+#define THROW	MODE_AIMMZ | MODE_BCTHROW
+#define CATCH	MODE_AIMMZ | MODE_BCCATCH
+#define CAST	MODE_AX | MODE_BX | MODE_CIMMZ | MODE_BCCAST
+
+#define RSRSRS	MODE_AS | MODE_BS | MODE_CS
+#define RIRS	MODE_AI | MODE_BS | MODE_CUNUSED
+#define I8RXRX	MODE_AIMMZ | MODE_BX | MODE_CX
+
+#define RIRIRI	MODE_AI | MODE_BI | MODE_CI
+#define RIRII8	MODE_AI | MODE_BI | MODE_CIMMZ
+#define RIRIKI	MODE_AI | MODE_BI | MODE_CKI
+#define RIKIRI	MODE_AI | MODE_BKI | MODE_CI
+#define RIKII8	MODE_AI | MODE_BKI | MODE_CIMMZ
+#define RIRIIs	MODE_AI | MODE_BI | MODE_CIMMS
+#define RIRI	MODE_AI | MODE_BI | MODE_CUNUSED
+#define I8RIRI	MODE_AIMMZ | MODE_BI | MODE_CI
+#define I8RIKI	MODE_AIMMZ | MODE_BI | MODE_CKI
+#define I8KIRI	MODE_AIMMZ | MODE_BKI | MODE_CI
+
+#define RFRFRF	MODE_AF | MODE_BF | MODE_CF
+#define RFRFKF	MODE_AF | MODE_BF | MODE_CKF
+#define RFKFRF	MODE_AF | MODE_BKF | MODE_CF
+#define I8RFRF	MODE_AIMMZ | MODE_BF | MODE_CF
+#define I8RFKF	MODE_AIMMZ | MODE_BF | MODE_CKF
+#define I8KFRF	MODE_AIMMZ | MODE_BKF | MODE_CF
+#define RFRFI8	MODE_AF | MODE_BF | MODE_CIMMZ
+
+#define RVRV	MODE_AV | MODE_BV | MODE_CUNUSED
+#define RVRVRV	MODE_AV | MODE_BV | MODE_CV
+#define RVRVKV	MODE_AV | MODE_BV | MODE_CKV
+#define RVKVRV	MODE_AV | MODE_BKV | MODE_CV
+#define RFRV	MODE_AF | MODE_BV | MODE_CUNUSED
+#define I8RVRV	MODE_AIMMZ | MODE_BV | MODE_CV
+#define I8RVKV	MODE_AIMMZ | MODE_BV | MODE_CKV
+
+#define RPRPRI	MODE_AP | MODE_BP | MODE_CI
+#define RPRPKI	MODE_AP | MODE_BP | MODE_CKI
+#define RIRPRP	MODE_AI | MODE_BP | MODE_CP
+#define I8RPRP	MODE_AIMMZ | MODE_BP | MODE_CP
+#define I8RPKP	MODE_AIMMZ | MODE_BP | MODE_CKP
+
+const VMOpInfo OpInfo[NUM_OPS] =
+{
+#define xx(op, name, mode)	{ #name, mode }
+#include "vmops.h"
+};
+
+#ifdef WORDS_BIGENDIAN
+#define JMPOFS(x)		((*(VM_SWORD *)(x) << 8) >> 6)
+#else
+#define JMPOFS(x)		((*(VM_SWORD *)(x) >> 6) & ~3)
+#endif
+
+static int print_reg(int col, int arg, int mode, int immshift, const VMScriptFunction *func);
+
+void VMDisasm(const VM_UBYTE *code, int codesize, const VMScriptFunction *func)
+{
+	const char *name;
+	int col;
+	int mode;
+	int a;
+
+	for (int i = 0; i < codesize; i += 4)
+	{
+		name = OpInfo[code[i]].Name;
+		mode = OpInfo[code[i]].Mode;
+		a = code[i+1];
+
+		// String comparison encodes everything in a single instruction.
+		if (code[i] == OP_CMPS)
+		{
+			switch (a & CMP_METHOD_MASK)
+			{
+			case CMP_EQ:	name = "eq";	break;
+			case CMP_LT:	name = "lt";	break;
+			case CMP_LE:	name = "le";	break;
+			}
+			mode = MODE_AIMMZ;
+			mode |= (a & CMP_BK) ? MODE_BKS : MODE_BS;
+			mode |= (a & CMP_CK) ? MODE_CKS : MODE_CS;
+			a &= CMP_CHECK | CMP_APPROX;
+		}
+
+		printf("%08x: %02x%02x%02x%02x %-8s", i, code[i], code[i+1], code[i+2], code[i+3], name);
+		col = 0;
+		switch (code[i])
+		{
+		case OP_JMP:
+		case OP_TRY:
+			col = printf("%08x", i + 4 + JMPOFS(&code[i]));
+			break;
+
+		case OP_RET:
+			if (code[i+2] != REGT_NIL)
+			{
+				if ((code[i+2] & REGT_FINAL) && a == 0)
+				{
+					col = print_reg(0, *(VM_UHALF *)&code[i+2], MODE_PARAM, 16, func);
+				}
+				else
+				{
+					col = print_reg(0, a, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
+					col += print_reg(col, *(VM_UHALF *)&code[i+2], MODE_PARAM, 16, func);
+					if (code[i+2] & REGT_FINAL)
+					{
+						col += printf(" [final]");
+					}
+				}
+			}
+			break;
+
+		default:
+			if ((mode & MODE_BCTYPE) == MODE_BCCAST)
+			{
+				switch (code[i+3])
+				{
+				case CAST_I2F:
+					mode = MODE_AF | MODE_BI | MODE_CUNUSED;
+					break;
+				case CAST_I2S:
+					mode = MODE_AS | MODE_BI | MODE_CUNUSED;
+					break;
+				case CAST_F2I:
+					mode = MODE_AI | MODE_BF | MODE_CUNUSED;
+					break;
+				case CAST_F2S:
+					mode = MODE_AS | MODE_BF | MODE_CUNUSED;
+					break;
+				case CAST_P2S:
+					mode = MODE_AS | MODE_BP | MODE_CUNUSED;
+					break;
+				case CAST_S2I:
+					mode = MODE_AI | MODE_BS | MODE_CUNUSED;
+					break;
+				case CAST_S2F:
+					mode = MODE_AF | MODE_BS | MODE_CUNUSED;
+					break;
+				default:
+					mode = MODE_AX | MODE_BX | MODE_CIMMZ;
+					break;
+				}
+			}
+			col = print_reg(0, a, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
+			if ((mode & MODE_BCTYPE) == MODE_BCTHROW)
+			{
+				mode = (code[i+1] == 0) ? (MODE_BP | MODE_CUNUSED) : (MODE_BKP | MODE_CUNUSED);
+			}
+			else if ((mode & MODE_BCTYPE) == MODE_BCCATCH)
+			{
+				switch (code[i+1])
+				{
+				case 0:
+					mode = MODE_BUNUSED | MODE_CUNUSED;
+					break;
+				case 1:
+					mode = MODE_BUNUSED | MODE_CP;
+					break;
+				case 2:
+					mode = MODE_BP | MODE_CP;
+					break;
+				case 3:
+					mode = MODE_BKP | MODE_CP;
+					break;
+				default:
+					mode = MODE_BIMMZ | MODE_CIMMZ;
+					break;
+				}
+			}
+			if ((mode & (MODE_BTYPE | MODE_CTYPE)) == MODE_BCJOINT)
+			{
+				col += print_reg(col, *(VM_UHALF *)&code[i+2], (mode & MODE_BCTYPE) >> MODE_BCSHIFT, 16, func);
+			}
+			else
+			{
+				col += print_reg(col, code[i+2], (mode & MODE_BTYPE) >> MODE_BSHIFT, 24, func);
+				col += print_reg(col, code[i+3], (mode & MODE_CTYPE) >> MODE_CSHIFT, 24, func);
+			}
+			break;
+		}
+		if (col > 30)
+		{
+			col = 30;
+		}
+		printf("%*c", 30 - col, ';');
+		if (code[i] == OP_JMP || code[i] == OP_TRY)
+		{
+			printf("%d\n", JMPOFS(&code[i]) >> 2);
+		}
+		else
+		{
+			printf("%d,%d,%d\n", code[i+1], code[i+2], code[i+3]);
+		}
+	}
+}
+
+static int print_reg(int col, int arg, int mode, int immshift, const VMScriptFunction *func)
+{
+	if (mode == MODE_UNUSED)
+	{
+		return 0;
+	}
+	if (col > 0)
+	{
+		col = printf(",");
+	}
+	switch(mode)
+	{
+	case MODE_I:
+		return col+printf("d%d", arg);
+	case MODE_F:
+		return col+printf("f%d", arg);
+	case MODE_S:
+		return col+printf("s%d", arg);
+	case MODE_P:
+		return col+printf("a%d", arg);
+	case MODE_V:
+		return col+printf("v%d", arg);
+
+	case MODE_KI:
+		if (func != NULL)
+		{
+			return col+printf("%d", func->KonstD[arg]);
+		}
+		return printf("kd%d", arg);
+	case MODE_KF:
+		if (func != NULL)
+		{
+			return col+printf("%f", func->KonstF[arg]);
+		}
+		return col+printf("kf%d", arg);
+	case MODE_KS:
+		if (func != NULL)
+		{
+			return col+printf("\"%s\"", func->KonstS[arg].GetChars());
+		}
+		return col+printf("ks%d", arg);
+	case MODE_KP:
+		if (func != NULL)
+		{
+			return col+printf("%p", func->KonstA[arg]);
+		}
+		return col+printf("ka%d", arg);
+	case MODE_KV:
+		if (func != NULL)
+		{
+			return col+printf("(%f,%f,%f)", func->KonstF[arg], func->KonstF[arg+1], func->KonstF[arg+2]);
+		}
+		return col+printf("kv%d", arg);
+
+	case MODE_IMMS:
+		return col+printf("%d", (arg << immshift) >> immshift);
+
+	case MODE_IMMZ:
+		return col+printf("%d", arg);
+
+	case MODE_PARAM:
+		{
+			union { VM_UHALF Together; struct { VM_UBYTE RegType, RegNum; }; } p;
+			p.Together = arg;
+			switch (p.RegType & (REGT_TYPE | REGT_KONST | REGT_MULTIREG))
+			{
+			case REGT_INT:
+				return col+printf("d%d", p.RegNum);
+			case REGT_FLOAT:
+				return col+printf("f%d", p.RegNum);
+			case REGT_STRING:
+				return col+printf("s%d", p.RegNum);
+			case REGT_POINTER:
+				return col+printf("a%d", p.RegNum);
+			case REGT_FLOAT | REGT_MULTIREG:
+				return col+printf("v%d", p.RegNum);
+			case REGT_INT | REGT_KONST:
+				return col+print_reg(0, p.RegNum, MODE_KI, 0, func);
+			case REGT_FLOAT | REGT_KONST:
+				return col+print_reg(0, p.RegNum, MODE_KF, 0, func);
+			case REGT_STRING | REGT_KONST:
+				return col+print_reg(0, p.RegNum, MODE_KS, 0, func);
+			case REGT_POINTER | REGT_KONST:
+				return col+print_reg(0, p.RegNum, MODE_KP, 0, func);
+			case REGT_FLOAT | REGT_MULTIREG | REGT_KONST:
+				return col+print_reg(0, p.RegNum, MODE_KV, 0, func);
+			default:
+				return col+printf("param[t=%d,%c,%c,n=%d]",
+					p.RegType & REGT_TYPE,
+					p.RegType & REGT_KONST ? 'k' : 'r',
+					p.RegType & REGT_MULTIREG ? 'm' : 's',
+					p.RegNum);
+			}
+		}
+
+	default:
+		return col+printf("$%d", arg);
+	}
+	return col;
+}
diff --git a/zscript/vmexec.cpp b/zscript/vmexec.cpp
new file mode 100644
index 000000000..9c32b4fc5
--- /dev/null
+++ b/zscript/vmexec.cpp
@@ -0,0 +1,185 @@
+#include <math.h>
+#include "vm.h"
+
+#define IMPLEMENT_VMEXEC
+
+#if !defined(COMPGOTO) && defined(__GNUC__)
+#define COMPGOTO 1
+#endif
+
+#if COMPGOTO
+#define OP(x)	x
+#define NEXTOP	do { unsigned op = *pc; a = pc[1]; pc += 4; goto *ops[op]; } while(0)
+#else
+#define OP(x)	case OP_##x
+#define NEXTOP	break
+#endif
+
+#define luai_nummod(a,b)        ((a) - floor((a)/(b))*(b))
+
+#define A				(*(pc - 3))
+#define B				(*(pc - 2))
+#define C				(			*(pc - 1))
+#define Cs				(*(VM_SBYTE *)(pc - 1))
+#define BC				(*(VM_UHALF *)(pc - 2))
+#define BCs				(*(VM_SHALF *)(pc - 2))
+
+#ifdef WORDS_BIGENDIAN
+#define JMPOFS(x)		((*(VM_SWORD *)(x) << 8) >> 6)
+#else
+#define JMPOFS(x)		((*(VM_SWORD *)(x) >> 6) & ~3)
+#endif
+
+#define KC				(konst[C])
+#define RC				(reg.i[C])
+
+#define PA				(reg.a[A])
+#define PB				(reg.a[B])
+
+#define ASSERTD(x)		assert((unsigned)(x) < f->NumRegD)
+#define ASSERTF(x)		assert((unsigned)(x) < f->NumRegF)
+#define ASSERTA(x)		assert((unsigned)(x) < f->NumRegA)
+#define ASSERTS(x)		assert((unsigned)(x) < f->NumRegS)
+
+#define ASSERTKD(x)		assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstD)
+#define ASSERTKF(x)		assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstF)
+#define ASSERTKA(x)		assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstA)
+#define ASSERTKS(x)		assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstS)
+
+#define THROW(x)
+
+#define CMPJMP(test) \
+	if ((test) == (a & CMP_CHECK)) { \
+		assert(*pc == OP_JMP); \
+		pc += 4 + JMPOFS(pc); \
+	} else { \
+		pc += 4; \
+	}
+
+enum
+{
+	X_READ_NIL,
+	X_WRITE_NIL,
+	X_TOO_MANY_TRIES,
+};
+
+#define GETADDR(a,o,x) \
+	if (a == 0) { THROW(x); } \
+	ptr = (VM_SBYTE *)a + x \
+
+static const VM_UWORD ZapTable[16] =
+{
+	0x00000000, 0x000000FF, 0x0000FF00, 0x0000FFFF,
+	0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00FFFFFF,
+	0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF,
+	0xFFFF0000, 0xFFFF00FF, 0xFFFFFF00, 0xFFFFFFFF
+};
+
+#ifdef NDEBUG
+#define WAS_NDEBUG 1
+#else
+#define WAS_NDEBUG 0
+#endif
+
+#if WAS_NDEBUG
+#undef NDEBUG
+#endif
+#undef assert
+#include <assert.h>
+struct VMExec_Checked
+{
+#include "vmexec.h"
+};
+#if WAS_NDEBUG
+#define NDEBUG
+#endif
+
+#if !WAS_NDEBUG
+#define NDEBUG
+#endif
+#undef assert
+#include <assert.h>
+struct VMExec_Unchecked
+{
+#include "vmexec.h"
+};
+#if !WAS_NDEBUG
+#undef NDEBUG
+#endif
+#undef assert
+#include <assert.h>
+
+int (*VMExec)(VMFrameStack *stack, const VM_UBYTE *pc, VMReturn *ret, int numret) =
+#ifdef NDEBUG
+VMExec_Unchecked::Exec
+#else
+VMExec_Checked::Exec
+#endif
+;
+
+void VMSelectEngine(EVMEngine engine)
+{
+	switch (engine)
+	{
+	case VMEngine_Default:
+#ifdef NDEBUG
+		VMExec = VMExec_Unchecked::Exec;
+#else
+		VMExec = VMExec_Checked::Exec;
+#endif
+		break;
+	case VMEngine_Unchecked:
+		VMExec = VMExec_Unchecked::Exec;
+		break;
+	case VMEngine_Checked:
+		VMExec = VMExec_Checked::Exec;
+		break;
+	}
+}
+
+//===========================================================================
+//
+// VMFillParams
+//
+// Takes parameters from the paramater stack and stores them in the callee's
+// registers.
+//
+//===========================================================================
+
+void VMFillParams(VMValue *params, VMFrame *callee, int numparam)
+{
+	unsigned int regd, regf, regs, rega;
+	VMScriptFunction *calleefunc = static_cast<VMScriptFunction *>(callee->Func);
+	const VMRegisters calleereg(callee);
+
+	assert(calleefunc != NULL && !calleefunc->Native);
+	assert(numparam == calleefunc->NumArgs);
+	assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3);
+
+	regd = regf = regs = rega = 0;
+	for (int i = 0; i < numparam; ++i)
+	{
+		VMValue &p = params[i];
+		if (p.Type < REGT_STRING)
+		{
+			if (p.Type == REGT_INT)
+			{
+				calleereg.d[regd++] = p.i;
+			}
+			else // p.Type == REGT_FLOAT
+			{
+				calleereg.f[regf++] = p.f;
+			}
+		}
+		else if (p.Type == REGT_STRING)
+		{
+			calleereg.s[regs++] = p.s();
+		}
+		else
+		{
+			assert(p.Type == REGT_POINTER);
+			calleereg.a[rega] = p.a;
+			calleereg.atag[rega++] = p.atag;
+		}
+	}
+}
diff --git a/zscript/vmexec.h b/zscript/vmexec.h
new file mode 100644
index 000000000..c0b268589
--- /dev/null
+++ b/zscript/vmexec.h
@@ -0,0 +1,1495 @@
+#ifndef IMPLEMENT_VMEXEC
+#error vmexec.h must not be #included outside vmexec.cpp. Use vm.h instead.
+#endif
+
+
+static int Exec(VMFrameStack *stack, const VM_UBYTE *pc, VMReturn *ret, int numret)
+{
+#if COMPGOTO
+	static const void * const ops[256] =
+	{
+#define xx(op,sym,mode) &&op
+#include "vmops.h"
+	};
+#endif
+	const VM_UBYTE *exception_frames[MAX_TRY_DEPTH];
+	int try_depth = 0;
+	VMFrame *f = stack->TopFrame();
+	VMScriptFunction *sfunc;
+	const VMRegisters reg(f);
+	const int *konstd;
+	const double *konstf;
+	const FString *konsts;
+	void * const *konsta;
+
+	if (f->Func != NULL && !f->Func->Native)
+	{
+		sfunc = static_cast<VMScriptFunction *>(f->Func);
+		konstd = sfunc->KonstD;
+		konstf = sfunc->KonstF;
+		konsts = sfunc->KonstS;
+		konsta = sfunc->KonstA;
+	}
+	else
+	{
+		sfunc = NULL;
+		konstd = NULL;
+		konstf = NULL;
+		konsts = NULL;
+		konsta = NULL;
+	}
+
+	void *ptr;
+	double fb, fc;
+	const double *fbp, *fcp;
+	int a, b, c;
+
+begin:
+	try
+	{
+#if !COMPGOTO
+	VM_UBYTE op;
+	for(;;) switch(op = pc[0], a = pc[1], pc += 4, op)
+#else
+	NEXTOP;
+#endif
+	{
+	OP(LI):
+		ASSERTD(a);
+		reg.d[a] = BCs;
+		NEXTOP;
+	OP(LK):
+		ASSERTD(a); ASSERTKD(BC);
+		reg.d[a] = konstd[BC];
+		NEXTOP;
+	OP(LKF):
+		ASSERTF(a); ASSERTKF(BC);
+		reg.f[a] = konstf[BC];
+		NEXTOP;
+	OP(LKS):
+		ASSERTS(a); ASSERTKS(BC);
+		reg.s[a] = konsts[BC];
+		NEXTOP;
+	OP(LKP):
+		ASSERTA(a); ASSERTKA(BC);
+		reg.a[a] = konsta[BC];
+		reg.atag[a] = ATAG_OBJECT;
+		NEXTOP;
+	OP(LFP):
+		ASSERTA(a); assert(sfunc != NULL); assert(sfunc->ExtraSpace > 0);
+		reg.a[a] = f->GetExtra();
+		reg.atag[a] = ATAG_FRAMEPOINTER;
+		NEXTOP;
+
+	OP(LB):
+		ASSERTD(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.d[a] = *(VM_SBYTE *)ptr;
+		NEXTOP;
+	OP(LB_R):
+		ASSERTD(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.d[a] = *(VM_SBYTE *)ptr;
+		NEXTOP;
+	OP(LH):
+		ASSERTD(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.d[a] = *(VM_SHALF *)ptr;
+		NEXTOP;
+	OP(LH_R):
+		ASSERTD(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.d[a] = *(VM_SHALF *)ptr;
+		NEXTOP;
+	OP(LW):
+		ASSERTD(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.d[a] = *(VM_SWORD *)ptr;
+		NEXTOP;
+	OP(LW_R):
+		ASSERTD(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.d[a] = *(VM_SWORD *)ptr;
+		NEXTOP;
+	OP(LBU):
+		ASSERTD(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.d[a] = *(VM_UBYTE *)ptr;
+		NEXTOP;
+	OP(LBU_R):
+		ASSERTD(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.d[a] = *(VM_UBYTE *)ptr;
+		NEXTOP;
+	OP(LHU):
+		ASSERTD(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.d[a] = *(VM_UHALF *)ptr;
+		NEXTOP;
+	OP(LHU_R):
+		ASSERTD(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.d[a] = *(VM_UHALF *)ptr;
+		NEXTOP;
+
+	OP(LSP):
+		ASSERTF(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.f[a] = *(float *)ptr;
+		NEXTOP;
+	OP(LSP_R):
+		ASSERTF(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.f[a] = *(float *)ptr;
+		NEXTOP;
+	OP(LDP):
+		ASSERTF(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.f[a] = *(double *)ptr;
+		NEXTOP;
+	OP(LDP_R):
+		ASSERTF(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.f[a] = *(double *)ptr;
+		NEXTOP;
+
+	OP(LS):
+		ASSERTS(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.s[a] = *(FString *)ptr;
+		NEXTOP;
+	OP(LS_R):
+		ASSERTS(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.s[a] = *(FString *)ptr;
+		NEXTOP;
+	OP(LO):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.a[a] = *(void **)ptr;
+		reg.atag[a] = ATAG_OBJECT;
+		NEXTOP;
+	OP(LO_R):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.a[a] = *(void **)ptr;
+		reg.atag[a] = ATAG_OBJECT;
+		NEXTOP;
+	OP(LP):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.a[a] = *(void **)ptr;
+		reg.atag[a] = ATAG_GENERIC;
+		NEXTOP;
+	OP(LP_R):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.a[a] = *(void **)ptr;
+		reg.atag[a] = ATAG_GENERIC;
+		NEXTOP;
+	OP(LV):
+		ASSERTF(a+2); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		{
+			float *v = (float *)ptr;
+			reg.f[a] = v[0];
+			reg.f[a+1] = v[1];
+			reg.f[a+2] = v[2];
+		}
+		NEXTOP;
+	OP(LV_R):
+		ASSERTF(a+2); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		{
+			float *v = (float *)ptr;
+			reg.f[a] = v[0];
+			reg.f[a+1] = v[1];
+			reg.f[a+2] = v[2];
+		}
+		NEXTOP;
+	OP(LX):
+		ASSERTF(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.f[a] = *(VM_SWORD *)ptr / 65536.0;
+		NEXTOP;
+	OP(LX_R):
+		ASSERTF(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.f[a] = *(VM_SWORD *)ptr / 65536.0;
+		NEXTOP;
+	OP(LBIT):
+		ASSERTD(a); ASSERTA(B);
+		GETADDR(PB,0,X_READ_NIL);
+		reg.d[a] = !!(*(VM_UBYTE *)ptr & C);
+		NEXTOP;
+
+	OP(SB):
+		ASSERTA(a); ASSERTD(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(VM_SBYTE *)ptr = reg.d[B];
+		NEXTOP;
+	OP(SB_R):
+		ASSERTA(a); ASSERTD(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(VM_SBYTE *)ptr = reg.d[B];
+		NEXTOP;
+	OP(SH):
+		ASSERTA(a); ASSERTD(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(VM_SHALF *)ptr = reg.d[B];
+		NEXTOP;
+	OP(SH_R):
+		ASSERTA(a); ASSERTD(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(VM_SHALF *)ptr = reg.d[B];
+		NEXTOP;
+	OP(SW):
+		ASSERTA(a); ASSERTD(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(VM_SWORD *)ptr = reg.d[B];
+		NEXTOP;
+	OP(SW_R):
+		ASSERTA(a); ASSERTD(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(VM_SWORD *)ptr = reg.d[B];
+		NEXTOP;
+	OP(SSP):
+		ASSERTA(a); ASSERTF(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(float *)ptr = (float)reg.f[B];
+		NEXTOP;
+	OP(SSP_R):
+		ASSERTA(a); ASSERTF(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(float *)ptr = (float)reg.f[B];
+		NEXTOP;
+	OP(SDP):
+		ASSERTA(a); ASSERTF(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(double *)ptr = reg.f[B];
+		NEXTOP;
+	OP(SDP_R):
+		ASSERTA(a); ASSERTF(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(double *)ptr = reg.f[B];
+		NEXTOP;
+	OP(SS):
+		ASSERTA(a); ASSERTS(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(FString *)ptr = reg.s[B];
+		NEXTOP;
+	OP(SS_R):
+		ASSERTA(a); ASSERTS(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(FString *)ptr = reg.s[B];
+		NEXTOP;
+	OP(SP):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(void **)ptr = reg.a[B];
+		NEXTOP;
+	OP(SP_R):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(void **)ptr = reg.a[B];
+		NEXTOP;
+	OP(SV):
+		ASSERTA(a); ASSERTF(B+2); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		{
+			float *v = (float *)ptr;
+			v[0] = (float)reg.f[B];
+			v[1] = (float)reg.f[B+1];
+			v[2] = (float)reg.f[B+2];
+		}
+		NEXTOP;
+	OP(SV_R):
+		ASSERTA(a); ASSERTF(B+2); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		{
+			float *v = (float *)ptr;
+			v[0] = (float)reg.f[B];
+			v[1] = (float)reg.f[B+1];
+			v[2] = (float)reg.f[B+2];
+		}
+		NEXTOP;
+	OP(SX):
+		ASSERTA(a); ASSERTF(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(VM_SWORD *)ptr = (VM_SWORD)(reg.f[B] * 65536.0);
+		NEXTOP;
+	OP(SX_R):
+		ASSERTA(a); ASSERTF(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		*(VM_SWORD *)ptr = (VM_SWORD)(reg.f[B] * 65536.0);
+		NEXTOP;
+	OP(SBIT):
+		ASSERTA(a); ASSERTD(B);
+		GETADDR(PA,0,X_WRITE_NIL);
+		if (reg.d[B])
+		{
+			*(VM_UBYTE *)ptr |= C;
+		}
+		else
+		{
+			*(VM_UBYTE *)ptr &= ~C;
+		}
+		NEXTOP;
+
+	OP(MOVE):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = reg.d[B];
+		NEXTOP;
+	OP(MOVEF):
+		ASSERTF(a); ASSERTF(B);
+		reg.f[a] = reg.f[B];
+		NEXTOP;
+	OP(MOVES):
+		ASSERTS(a); ASSERTS(B);
+		reg.s[a] = reg.s[B];
+		NEXTOP;
+	OP(MOVEA):
+		ASSERTA(a); ASSERTA(B);
+		reg.a[a] = reg.a[B];
+		reg.atag[a] = reg.atag[B];
+		NEXTOP;
+	OP(CAST):
+		if (C == CAST_I2F)
+		{
+			ASSERTF(a); ASSERTD(B);
+			reg.f[A] = reg.d[B];
+		}
+		else if (C == CAST_F2I)
+		{
+			ASSERTD(a); ASSERTF(B);
+			reg.d[A] = (int)reg.f[B];
+		}
+		else
+		{
+			DoCast(reg, f, a, B, C);
+		}
+		NEXTOP;
+	OP(DYNCAST_R):
+		// UNDONE
+		NEXTOP;
+	OP(DYNCAST_K):
+		// UNDONE
+		NEXTOP;
+	
+	OP(TEST):
+		ASSERTD(a);
+		if (reg.d[a] != BC)
+		{
+			pc += 4;
+		}
+		NEXTOP;
+	OP(JMP):
+		pc += JMPOFS(pc - 4);
+		NEXTOP;
+	OP(IJMP):
+		ASSERTD(a);
+		pc += (BCs + reg.d[a]) << 2;
+		assert(*pc == OP_JMP);
+		pc += (1 + *((VM_SHALF *)pc + 1)) << 2;
+		NEXTOP;
+	OP(PARAM):
+		assert(f->NumParam < sfunc->MaxParam);
+		{
+			VMValue *param = &reg.param[f->NumParam++];
+			b = B;
+			if (b == REGT_NIL)
+			{
+				::new(param) VMValue();
+			}
+			else
+			{
+				switch(b & (REGT_TYPE | REGT_KONST | REGT_ADDROF))
+				{
+				case REGT_INT:
+					assert(C < f->NumRegD);
+					::new(param) VMValue(reg.d[C]);
+					break;
+				case REGT_INT | REGT_ADDROF:
+					assert(C < f->NumRegD);
+					::new(param) VMValue(&reg.d[C], ATAG_DREGISTER);
+					break;
+				case REGT_INT | REGT_KONST:
+					assert(C < sfunc->NumKonstD);
+					::new(param) VMValue(konstd[C]);
+					break;
+				case REGT_STRING:
+					assert(C < f->NumRegS);
+					::new(param) VMValue(reg.s[C]);
+					break;
+				case REGT_STRING | REGT_ADDROF:
+					assert(C < f->NumRegS);
+					::new(param) VMValue(&reg.s[C], ATAG_SREGISTER);
+					break;
+				case REGT_STRING | REGT_KONST:
+					assert(C < sfunc->NumKonstS);
+					::new(param) VMValue(konsts[C]);
+					break;
+				case REGT_POINTER:
+					assert(C < f->NumRegA);
+					::new(param) VMValue(reg.a[C], reg.atag[C]);
+					break;
+				case REGT_POINTER | REGT_ADDROF:
+					assert(C < f->NumRegA);
+					::new(param) VMValue(&reg.a[C], ATAG_AREGISTER);
+					break;
+				case REGT_POINTER | REGT_KONST:
+					assert(C < sfunc->NumKonstA);
+					::new(param) VMValue(konsta[C]);
+					break;
+				case REGT_FLOAT:
+					if (b & REGT_MULTIREG)
+					{
+						assert(C < f->NumRegF - 2);
+						assert(f->NumParam < sfunc->MaxParam - 1);
+						::new(param) VMValue(reg.f[C]);
+						::new(param+1) VMValue(reg.f[C+1]);
+						::new(param+2) VMValue(reg.f[C+2]);
+						f->NumParam += 2;
+					}
+					else
+					{
+						assert(C < f->NumRegF);
+						::new(param) VMValue(reg.f[C]);
+					}
+					break;
+				case REGT_FLOAT | REGT_ADDROF:
+					assert(C < f->NumRegF);
+					::new(param) VMValue(&reg.f[C], ATAG_FREGISTER);
+					break;
+				case REGT_FLOAT | REGT_KONST:
+					if (b & REGT_MULTIREG)
+					{
+						assert(C < sfunc->NumKonstF - 2);
+						assert(f->NumParam < sfunc->MaxParam - 1);
+						::new(param) VMValue(konstf[C]);
+						::new(param+1) VMValue(konstf[C+1]);
+						::new(param+2) VMValue(konstf[C+2]);
+						f->NumParam += 2;
+					}
+					else
+					{
+						assert(C < sfunc->NumKonstF);
+						::new(param) VMValue(konstf[C]);
+					}
+					break;
+				default:
+					assert(0);
+					break;
+				}
+			}
+		}
+		NEXTOP;
+	OP(CALL_K):
+		ASSERTKA(a);
+		ptr = sfunc->KonstA[a];
+		goto Do_CALL;
+	OP(CALL):
+		ASSERTA(a);
+		ptr = reg.a[a];
+	Do_CALL:
+		assert(B <= f->NumParam);
+		assert(C <= MAX_RETURNS);
+		{
+			VMFunction *call = (VMFunction *)ptr;
+			VMReturn returns[MAX_RETURNS];
+			int numret;
+
+			FillReturns(reg, f, returns, pc, C);
+			if (call->Native)
+			{
+				numret = static_cast<VMNativeFunction *>(call)->NativeCall(stack, reg.param + f->NumParam - B, B, returns, C);
+			}
+			else
+			{
+				VMScriptFunction *script = static_cast<VMScriptFunction *>(call);
+				VMFrame *newf = stack->AllocFrame(script);
+				VMFillParams(reg.param + f->NumParam - B, newf, B);
+				try
+				{
+					numret = Exec(stack, script->Code, returns, C);
+				}
+				catch(...)
+				{
+					stack->PopFrame();
+					throw;
+				}
+				stack->PopFrame();
+			}
+			assert(numret == C);
+			for (b = B; b != 0; --b)
+			{
+				reg.param[--f->NumParam].~VMValue();
+			}
+			pc += C * 4;		// Skip RESULTs
+		}
+		NEXTOP;
+	OP(RET):
+		if (B == REGT_NIL)
+		{ // No return values
+			return 0;
+		}
+		assert(a < numret);
+		SetReturn(reg, f, &ret[a], B, C);
+		if (B & REGT_FINAL)
+		{
+			return a + 1;
+		}
+		NEXTOP;
+	OP(RESULT):
+		// This instruction is just a placeholder to indicate where a return
+		// value should be stored. It does nothing on its own and should not
+		// be executed.
+		assert(0);
+		NEXTOP;
+
+	OP(TRY):
+		assert(try_depth < MAX_TRY_DEPTH);
+		if (try_depth >= MAX_TRY_DEPTH)
+		{
+			THROW(X_TOO_MANY_TRIES);
+		}
+		assert(*(pc + JMPOFS(pc - 4)) == OP_CATCH);
+		exception_frames[try_depth++] = pc + JMPOFS(pc - 4);
+		NEXTOP;
+	OP(UNTRY):
+		assert(a <= try_depth);
+		try_depth -= a;
+		NEXTOP;
+	OP(THROW):
+		if (a == 0)
+		{
+			ASSERTA(B);
+			throw((VMException *)reg.a[B]);
+		}
+		else
+		{
+			ASSERTKA(B);
+			throw((VMException *)konsta[B]);
+		}
+		NEXTOP;
+	OP(CATCH):
+		// This instruction is handled by our own catch handler and should
+		// not be executed by the normal VM code.
+		assert(0);
+		NEXTOP;
+
+	OP(CONCAT):
+		ASSERTS(a); ASSERTS(B); ASSERTS(C);
+		{
+			FString *rB = &reg.s[B];
+			FString *rC = &reg.s[C];
+			FString concat(*rB);
+			for (++rB; rB <= rC; ++rB)
+			{
+				concat += *rB;
+			}
+			reg.s[a] = concat;
+		}
+		NEXTOP;
+	OP(LENS):
+		ASSERTD(a); ASSERTS(B);
+		reg.d[a] = (int)reg.s[B].Len();
+		NEXTOP;
+	
+	OP(CMPS):
+		// String comparison is a fairly expensive operation, so I've
+		// chosen to conserve a few opcodes by condensing all the
+		// string comparisons into a single one.
+		{
+			const FString *b, *c;
+			int test, method;
+			bool cmp;
+
+			if (a & CMP_BK)
+			{
+				ASSERTKS(B);
+				b = &konsts[B];
+			}
+			else
+			{
+				ASSERTS(B);
+				b = &reg.s[B];
+			}
+			if (a & CMP_CK)
+			{
+				ASSERTKS(C);
+				c = &konsts[C];
+			}
+			else
+			{
+				ASSERTS(C);
+				c = &reg.s[C];
+			}
+			test = (a & CMP_APPROX) ? b->CompareNoCase(*c) : b->Compare(*c);
+			method = a & CMP_METHOD_MASK;
+			if (method == CMP_EQ)
+			{
+				cmp = !test;
+			}
+			else if (method == CMP_LT)
+			{
+				cmp = (test < 0);
+			}
+			else
+			{
+				assert(method == CMP_LE);
+				cmp = (test <= 0);
+			}
+			if (cmp == (a & CMP_CHECK))
+			{
+				assert(*pc == OP_JMP);
+				pc += 4 + JMPOFS(pc);
+			}
+			else
+			{
+				pc += 4;
+			}
+		}
+		NEXTOP;
+
+	OP(SLL_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] << reg.d[C];
+		NEXTOP;
+	OP(SLL_RI):
+		ASSERTD(a); ASSERTD(B); assert(0 <= C && C <= 31);
+		reg.d[a] = reg.d[B] << C;
+		NEXTOP;
+	OP(SLL_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		reg.d[a] = konstd[B] << reg.d[C];
+		NEXTOP;
+
+	OP(SRL_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = (unsigned)reg.d[B] >> reg.d[C];
+		NEXTOP;
+	OP(SRL_RI):
+		ASSERTD(a); ASSERTD(B); assert(0 <= C && C <= 31);
+		reg.d[a] = (unsigned)reg.d[B] >> C;
+		NEXTOP;
+	OP(SRL_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		reg.d[a] = (unsigned)konstd[B] >> C;
+		NEXTOP;
+
+	OP(SRA_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] >> reg.d[C];
+		NEXTOP;
+	OP(SRA_RI):
+		ASSERTD(a); ASSERTD(B); assert(0 <= C && C <= 31);
+		reg.d[a] = reg.d[B] >> C;
+		NEXTOP;
+	OP(SRA_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		reg.d[a] = konstd[B] >> reg.d[C];
+		NEXTOP;
+
+	OP(ADD_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] + reg.d[C];
+		NEXTOP;
+	OP(ADD_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] + konstd[C];
+		NEXTOP;
+	OP(ADDI):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = reg.d[B] + Cs;
+		NEXTOP;
+
+	OP(SUB_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] - reg.d[C];
+		NEXTOP;
+	OP(SUB_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] - konstd[C];
+		NEXTOP;
+	OP(SUB_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		reg.d[a] = konstd[B] - reg.d[C];
+		NEXTOP;
+
+	OP(MUL_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] * reg.d[C];
+		NEXTOP;
+	OP(MUL_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] * konstd[C];
+		NEXTOP;
+
+	OP(DIV_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] / reg.d[C];
+		NEXTOP;
+	OP(DIV_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] / konstd[C];
+		NEXTOP;
+	OP(DIV_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		reg.d[a] = konstd[B] / reg.d[C];
+		NEXTOP;
+
+	OP(MOD_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] % reg.d[C];
+		NEXTOP;
+	OP(MOD_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] % konstd[C];
+		NEXTOP;
+	OP(MOD_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		reg.d[a] = konstd[B] % reg.d[C];
+		NEXTOP;
+
+	OP(AND_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] & reg.d[C];
+		NEXTOP;
+	OP(AND_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] & konstd[C];
+		NEXTOP;
+
+	OP(OR_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] | reg.d[C];
+		NEXTOP;
+	OP(OR_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] | konstd[C];
+		NEXTOP;
+
+	OP(XOR_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] ^ reg.d[C];
+		NEXTOP;
+	OP(XOR_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] ^ konstd[C];
+		NEXTOP;
+
+	OP(MIN_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] < reg.d[C] ? reg.d[B] : reg.d[C];
+		NEXTOP;
+	OP(MIN_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] < konstd[C] ? reg.d[B] : konstd[C];
+		NEXTOP;
+	OP(MAX_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] > reg.d[C] ? reg.d[B] : reg.d[C];
+		NEXTOP;
+	OP(MAX_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = reg.d[B] > konstd[C] ? reg.d[B] : konstd[C];
+		NEXTOP;
+
+	OP(ABS):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = abs(reg.d[B]);
+		NEXTOP;
+
+	OP(NEG):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = -reg.d[B];
+		NEXTOP;
+
+	OP(NOT):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = ~reg.d[B];
+		NEXTOP;
+
+	OP(SEXT):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = (VM_SWORD)(reg.d[B] << C) >> C;
+		NEXTOP;
+
+	OP(ZAP_R):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] & ZapTable[(reg.d[C] & 15) ^ 15];
+		NEXTOP;
+	OP(ZAP_I):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = reg.d[B] & ZapTable[(C & 15) ^ 15];
+		NEXTOP;
+	OP(ZAPNOT_R):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = reg.d[B] & ZapTable[reg.d[C] & 15];
+		NEXTOP;
+	OP(ZAPNOT_I):
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = reg.d[B] & ZapTable[C & 15];
+		NEXTOP;
+
+	OP(EQ_R):
+		ASSERTD(B); ASSERTD(C);
+		CMPJMP(reg.d[B] == reg.d[C]);
+		NEXTOP;
+	OP(EQ_K):
+		ASSERTD(B); ASSERTKD(C);
+		CMPJMP(reg.d[B] == konstd[C]);
+		NEXTOP;
+	OP(LT_RR):
+		ASSERTD(B); ASSERTD(C);
+		CMPJMP(reg.d[B] < reg.d[C]);
+		NEXTOP;
+	OP(LT_RK):
+		ASSERTD(B); ASSERTKD(C);
+		CMPJMP(reg.d[B] < konstd[C]);
+		NEXTOP;
+	OP(LT_KR):
+		ASSERTKD(B); ASSERTD(C);
+		CMPJMP(konstd[B] < reg.d[C]);
+		NEXTOP;
+	OP(LE_RR):
+		ASSERTD(B); ASSERTD(C);
+		CMPJMP(reg.d[B] <= reg.d[C]);
+		NEXTOP;
+	OP(LE_RK):
+		ASSERTD(B); ASSERTKD(C);
+		CMPJMP(reg.d[B] <= konstd[C]);
+		NEXTOP;
+	OP(LE_KR):
+		ASSERTKD(B); ASSERTD(C);
+		CMPJMP(konstd[B] <= reg.d[C]);
+		NEXTOP;
+	OP(LTU_RR):
+		ASSERTD(B); ASSERTD(C);
+		CMPJMP((VM_UWORD)reg.d[B] < (VM_UWORD)reg.d[C]);
+		NEXTOP;
+	OP(LTU_RK):
+		ASSERTD(B); ASSERTKD(C);
+		CMPJMP((VM_UWORD)reg.d[B] < (VM_UWORD)konstd[C]);
+		NEXTOP;
+	OP(LTU_KR):
+		ASSERTKD(B); ASSERTD(C);
+		CMPJMP((VM_UWORD)konstd[B] < (VM_UWORD)reg.d[C]);
+		NEXTOP;
+	OP(LEU_RR):
+		ASSERTD(B); ASSERTD(C);
+		CMPJMP((VM_UWORD)reg.d[B] <= (VM_UWORD)reg.d[C]);
+		NEXTOP;
+	OP(LEU_RK):
+		ASSERTD(B); ASSERTKD(C);
+		CMPJMP((VM_UWORD)reg.d[B] <= (VM_UWORD)konstd[C]);
+		NEXTOP;
+	OP(LEU_KR):
+		ASSERTKD(B); ASSERTD(C);
+		CMPJMP((VM_UWORD)konstd[B] <= (VM_UWORD)reg.d[C]);
+		NEXTOP;
+
+	OP(ADDF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = reg.f[B] + reg.f[C];
+		NEXTOP;
+	OP(ADDF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = reg.f[B] + konstf[C];
+		NEXTOP;
+
+	OP(SUBF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = reg.f[B] - reg.f[C];
+		NEXTOP;
+	OP(SUBF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = reg.f[B] - konstf[C];
+		NEXTOP;
+	OP(SUBF_KR):
+		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
+		reg.f[a] = konstf[B] - reg.f[C];
+		NEXTOP;
+
+	OP(MULF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = reg.f[B] * reg.f[C];
+		NEXTOP;
+	OP(MULF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = reg.f[B] * konstf[C];
+		NEXTOP;
+
+	OP(DIVF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = reg.f[B] / reg.f[C];
+		NEXTOP;
+	OP(DIVF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = reg.f[B] / konstf[C];
+		NEXTOP;
+	OP(DIVF_KR):
+		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
+		reg.f[a] = konstf[B] / reg.f[C];
+		NEXTOP;
+
+	OP(MODF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		fb = reg.f[B]; fc = reg.f[C];
+	Do_MODF:
+		reg.f[a] = luai_nummod(fb, fc);
+		NEXTOP;
+	OP(MODF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		fb = reg.f[B]; fc = konstf[C];
+		goto Do_MODF;
+		NEXTOP;
+	OP(MODF_KR):
+		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
+		fb = konstf[B]; fc = reg.f[C];
+		goto Do_MODF;
+		NEXTOP;
+
+	OP(POWF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = pow(reg.f[B], reg.f[C]);
+		NEXTOP;
+	OP(POWF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = pow(reg.f[B], konstf[C]);
+		NEXTOP;
+	OP(POWF_KR):
+		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
+		reg.f[a] = pow(konstf[B], reg.f[C]);
+		NEXTOP;
+
+	OP(MINF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = reg.f[B] < reg.f[C] ? reg.f[B] : reg.f[C];
+		NEXTOP;
+	OP(MINF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = reg.f[B] < konstf[C] ? reg.f[B] : konstf[C];
+		NEXTOP;
+	OP(MAXF_RR):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = reg.f[B] > reg.f[C] ? reg.f[B] : reg.f[C];
+		NEXTOP;
+	OP(MAXF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = reg.f[B] > konstf[C] ? reg.f[B] : konstf[C];
+		NEXTOP;
+
+	OP(FLOP):
+		ASSERTF(a); ASSERTF(B);
+		fb = reg.f[B];
+		reg.f[a] = (C == FLOP_ABS) ? fabs(fb) : (C == FLOP_NEG) ? -fb : DoFLOP(C, fb);
+		NEXTOP;
+	
+	OP(EQF_R):
+		ASSERTF(B); ASSERTF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP(fabs(reg.f[C] - reg.f[B]) < VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[C] == reg.f[B]);
+		}
+		NEXTOP;
+	OP(EQF_K):
+		ASSERTF(B); ASSERTKF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP(fabs(konstf[C] - reg.f[B]) < VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(konstf[C] == reg.f[B]);
+		}
+		NEXTOP;
+	OP(LTF_RR):
+		ASSERTF(B); ASSERTF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP((reg.f[B] - reg.f[C]) < -VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[B] < reg.f[C]);
+		}
+		NEXTOP;
+	OP(LTF_RK):
+		ASSERTF(B); ASSERTKF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP((reg.f[B] - konstf[C]) < -VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[B] < konstf[C]);
+		}
+		NEXTOP;
+	OP(LTF_KR):
+		ASSERTKF(B); ASSERTF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP((konstf[B] - reg.f[C]) < -VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(konstf[B] < reg.f[C]);
+		}
+		NEXTOP;
+	OP(LEF_RR):
+		ASSERTF(B); ASSERTF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP((reg.f[B] - reg.f[C]) <= -VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[B] <= reg.f[C]);
+		}
+		NEXTOP;
+	OP(LEF_RK):
+		ASSERTF(B); ASSERTKF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP((reg.f[B] - konstf[C]) <= -VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[B] <= konstf[C]);
+		}
+		NEXTOP;
+	OP(LEF_KR):
+		ASSERTKF(B); ASSERTF(C);
+		if (a & CMP_APPROX)
+		{
+			CMPJMP((konstf[B] - reg.f[C]) <= -VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(konstf[B] <= reg.f[C]);
+		}
+		NEXTOP;
+
+	OP(NEGV):
+		ASSERTF(a+2); ASSERTF(B+2);
+		reg.f[a] = -reg.f[B];
+		reg.f[a+1] = -reg.f[B+1];
+		reg.f[a+2] = -reg.f[B+2];
+		NEXTOP;
+
+	OP(ADDV_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2);
+		fcp = &reg.f[C];
+	Do_ADDV:
+		fbp = &reg.f[B];
+		reg.f[a] = fbp[0] + fcp[0];
+		reg.f[a+1] = fbp[1] + fcp[1];
+		reg.f[a+2] = fbp[2] + fcp[2];
+		NEXTOP;
+	OP(ADDV_RK):
+		fcp = &konstf[C];
+		goto Do_ADDV;
+
+	OP(SUBV_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2);
+		fbp = &reg.f[B];
+		fcp = &reg.f[C];
+	Do_SUBV:
+		reg.f[a] = fbp[0] - fcp[0];
+		reg.f[a+1] = fbp[1] - fcp[1];
+		reg.f[a+2] = fbp[2] - fcp[2];
+		NEXTOP;
+	OP(SUBV_RK):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C+2);
+		fbp = &reg.f[B];
+		fcp = &konstf[C];
+		goto Do_SUBV;
+	OP(SUBV_KR):
+		ASSERTF(A+2); ASSERTKF(B+2); ASSERTF(C+2);
+		fbp = &konstf[B];
+		fcp = &reg.f[C];
+		goto Do_SUBV;
+
+	OP(DOTV_RR):
+		ASSERTF(a); ASSERTF(B+2); ASSERTF(C+2);
+		reg.f[a] = reg.f[B] * reg.f[C] + reg.f[B+1] * reg.f[C+1] + reg.f[B+2] * reg.f[C+2];
+		NEXTOP;
+	OP(DOTV_RK):
+		ASSERTF(a); ASSERTF(B+2); ASSERTKF(C+2);
+		reg.f[a] = reg.f[B] * konstf[C] + reg.f[B+1] * konstf[C+1] + reg.f[B+2] * konstf[C+2];
+		NEXTOP;
+
+	OP(CROSSV_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2);
+		fbp = &reg.f[B];
+		fcp = &reg.f[C];
+	Do_CROSSV:
+		{
+			double t[3];
+			t[2] = fbp[0] * fcp[1] - fbp[1] * fcp[0];
+			t[1] = fbp[2] * fcp[0] - fbp[0] * fcp[2];
+			t[0] = fbp[1] * fcp[2] - fbp[2] * fcp[1];
+			reg.f[a] = t[0]; reg.f[a+1] = t[1]; reg.f[a+2] = t[2];
+		}
+		NEXTOP;
+	OP(CROSSV_RK):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C+2);
+		fbp = &reg.f[B];
+		fcp = &konstf[C];
+		goto Do_CROSSV;
+	OP(CROSSV_KR):
+		ASSERTF(a+2); ASSERTKF(B+2); ASSERTF(C+2);
+		fbp = &reg.f[B];
+		fcp = &konstf[C];
+		goto Do_CROSSV;
+
+	OP(MULVF_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C);
+		fc = reg.f[C];
+		fbp = &reg.f[B];
+	Do_MULV:
+		reg.f[a] = fbp[0] * fc;
+		reg.f[a+1] = fbp[1] * fc;
+		reg.f[a+2] = fbp[2] * fc;
+		NEXTOP;
+	OP(MULVF_RK):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C);
+		fc = konstf[C];
+		fbp = &reg.f[B];
+		goto Do_MULV;
+	OP(MULVF_KR):
+		ASSERTF(a+2); ASSERTKF(B+2); ASSERTF(C);
+		fc = reg.f[C];
+		fbp = &konstf[B];
+		goto Do_MULV;
+
+	OP(LENV):
+		ASSERTF(a); ASSERTF(B+2);
+		reg.f[a] = sqrt(reg.f[B] * reg.f[B] + reg.f[B+1] * reg.f[B+1] + reg.f[B+2] * reg.f[B+2]);
+		NEXTOP;
+
+	OP(EQV_R):
+		ASSERTF(B+2); ASSERTF(C+2);
+		fcp = &reg.f[C];
+	Do_EQV:
+		if (a & CMP_APPROX)
+		{
+			CMPJMP(fabs(reg.f[B  ] - fcp[0]) < VM_EPSILON &&
+				   fabs(reg.f[B+1] - fcp[1]) < VM_EPSILON &&
+				   fabs(reg.f[B+2] - fcp[2]) < VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[B] == fcp[0] && reg.f[B+1] == fcp[1] && reg.f[B+2] == fcp[2]);
+		}
+		NEXTOP;
+	OP(EQV_K):
+		ASSERTF(B+2); ASSERTKF(C+2);
+		fcp = &konstf[C];
+		goto Do_EQV;
+
+	OP(ADDA_RR):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		c = reg.d[C];
+	Do_ADDA:
+		if (reg.a[B] == NULL)	// Leave NULL pointers as NULL pointers
+		{
+			c = 0;
+		}
+		reg.a[a] = (VM_UBYTE *)reg.a[B] + c;
+		reg.atag[a] = c == 0 ? reg.atag[B] : ATAG_GENERIC;
+		NEXTOP;
+	OP(ADDA_RK):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		c = konstd[C];
+		goto Do_ADDA;
+
+	OP(SUBA):
+		ASSERTD(a); ASSERTA(B); ASSERTA(C);
+		reg.d[a] = (VM_UWORD)((VM_UBYTE *)reg.a[B] - (VM_UBYTE *)reg.a[C]);
+		NEXTOP;
+
+	OP(EQA_R):
+		ASSERTA(B); ASSERTA(C);
+		CMPJMP(reg.a[B] == reg.a[C]);
+		NEXTOP;
+	OP(EQA_K):
+		ASSERTA(B); ASSERTKA(C);
+		CMPJMP(reg.a[B] == konsta[C]);
+		NEXTOP;
+	}
+	}
+	catch(VMException *exception)
+	{
+		// Try to find a handler for the exception.
+		PClass *extype = exception->GetClass();
+
+		while(--try_depth >= 0)
+		{
+			pc = exception_frames[try_depth];
+			assert(pc[0] == OP_CATCH);
+			while (pc[1] > 1)
+			{
+				// CATCH must be followed by JMP if it doesn't terminate a catch chain.
+				assert(pc[4] == OP_JMP);
+
+				PClass *type;
+				int b = pc[2];
+
+				if (pc[1] == 2)
+				{
+					ASSERTA(b);
+					type = (PClass *)reg.a[b];
+				}
+				else
+				{
+					assert(pc[1] == 3);
+					ASSERTKA(b);
+					type = (PClass *)konsta[b];
+				}
+				ASSERTA(pc[3]);
+				if (type == extype)
+				{
+					// Found a handler. Store the exception in pC, skip the JMP,
+					// and begin executing its code.
+					reg.a[pc[3]] = exception;
+					reg.atag[pc[3]] = ATAG_OBJECT;
+					pc += 8;
+					goto begin;
+				}
+				// This catch didn't handle it. Try the next one.
+				pc += 4 + JMPOFS(pc + 4);
+				assert(pc[0] == OP_CATCH);
+			}
+			if (pc[1] == 1)
+			{
+				// Catch any type of VMException. This terminates the chain.
+				ASSERTA(pc[3]);
+				reg.a[pc[3]] = exception;
+				reg.atag[pc[3]] = ATAG_OBJECT;
+				pc += 4;
+				goto begin;
+			}
+			// This frame failed. Try the next one out.
+		}
+		// Nothing caught it. Rethrow and let somebody else deal with it.
+		throw;
+	}
+	return 0;
+}
+
+static double DoFLOP(int flop, double v)
+{
+	switch(flop)
+	{
+	case FLOP_ABS:		return fabs(v);
+	case FLOP_NEG:		return -v;
+	case FLOP_ACOS:		return acos(v);
+	case FLOP_ASIN:		return asin(v);
+	case FLOP_ATAN:		return atan(v);
+	case FLOP_COS:		return cos(v);
+	case FLOP_COSH:		return cosh(v);
+	case FLOP_EXP:		return exp(v);
+	case FLOP_LOG:		return log(v);
+	case FLOP_LOG10:	return log10(v);
+	case FLOP_SIN:		return sin(v);
+	case FLOP_SINH:		return sinh(v);
+	case FLOP_TAN:		return tan(v);
+	case FLOP_TANH:		return tanh(v);
+	case FLOP_SQRT:		return sqrt(v);
+	case FLOP_CEIL:		return ceil(v);
+	case FLOP_FLOOR:	return floor(v);
+	}
+	assert(0);
+	return 0;
+}
+
+static void DoCast(const VMRegisters &reg, const VMFrame *f, int a, int b, int cast)
+{
+	switch (cast)
+	{
+	case CAST_I2F:
+		ASSERTF(a); ASSERTD(b);
+		reg.f[a] = reg.d[b];
+		break;
+	case CAST_I2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a].Format("%d", reg.d[b]);
+		break;
+
+	case CAST_F2I:
+		ASSERTD(a); ASSERTF(b);
+		reg.d[a] = (int)reg.f[b];
+		break;
+	case CAST_F2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a].Format("%.14g", reg.f[b]);
+		break;
+
+	case CAST_P2S:
+		ASSERTS(a); ASSERTA(b);
+		reg.s[a].Format("%s<%p>", reg.atag[b] == ATAG_OBJECT ? "Object" : "Pointer", reg.a[b]);
+		break;
+
+	case CAST_S2I:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = (VM_SWORD)reg.s[b].ToLong();
+		break;
+	case CAST_S2F:
+		ASSERTF(a); ASSERTS(b);
+		reg.f[a] = reg.s[b].ToDouble();
+		break;
+
+	default:
+		assert(0);
+	}
+}
+
+//===========================================================================
+//
+// FillReturns
+//
+// Fills in an array of pointers to locations to store return values in.
+//
+//===========================================================================
+
+static void FillReturns(const VMRegisters &reg, VMFrame *frame, VMReturn *returns, const VM_UBYTE *retval, int numret)
+{
+	int i, type, num;
+	VMReturn *ret;
+
+	assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3);
+
+	for (i = 0, ret = returns; i < numret; ++i, ++ret, retval += 4)
+	{
+		assert(retval[0] == OP_RESULT);				// opcode
+		ret->RegType = type = retval[2];
+		ret->RegNum = num = retval[3];
+		assert(!(type & REGT_KONST));
+		type &= REGT_TYPE;
+		if (type < REGT_STRING)
+		{
+			if (type == REGT_INT)
+			{
+				assert(num < frame->NumRegD);
+				ret->Location = &reg.d[num];
+			}
+			else // type == REGT_FLOAT
+			{
+				assert(num < frame->NumRegF);
+				ret->Location = &reg.f[num];
+			}
+		}
+		else if (type == REGT_STRING)
+		{
+			assert(num < frame->NumRegS);
+			ret->Location = &reg.s[num];
+		}
+		else
+		{
+			assert(type == REGT_POINTER);
+			assert(num < frame->NumRegA);
+			ret->Location = &reg.a[num];
+		}
+	}
+}
+
+//===========================================================================
+//
+// SetReturn
+//
+// Used by script code to set a return value.
+//
+//===========================================================================
+
+static void SetReturn(const VMRegisters &reg, VMFrame *frame, VMReturn *ret, VM_UBYTE regtype, int regnum)
+{
+	const void *src;
+	VM_UBYTE atag;
+	VMScriptFunction *func = static_cast<VMScriptFunction *>(frame->Func);
+
+	assert(func != NULL && !func->Native);
+	assert((regtype & ~(REGT_KONST | REGT_FINAL)) == ret->RegType);
+
+	switch (regtype & REGT_TYPE)
+	{
+	case REGT_INT:
+		assert(!(regtype & REGT_MULTIREG));
+		if (regtype & REGT_KONST)
+		{
+			assert(regnum < func->NumKonstD);
+			src = &func->KonstD[regnum];
+		}
+		else
+		{
+			assert(regnum < frame->NumRegD);
+			src = &reg.d[regnum];
+		}
+		ret->SetInt(*(int *)src);
+		break;
+
+	case REGT_FLOAT:
+		if (regtype & REGT_KONST)
+		{
+			assert(regnum + ((regtype & REGT_KONST) ? 2u : 0u) < func->NumKonstF);
+			src = &func->KonstF[regnum];
+		}
+		else
+		{
+			assert(regnum + ((regtype & REGT_KONST) ? 2u : 0u) < frame->NumRegF);
+			src = &reg.f[regnum];
+		}
+		if (regtype & REGT_MULTIREG)
+		{
+			ret->SetVector((double *)src);
+		}
+		else
+		{
+			ret->SetFloat(*(double *)src);
+		}
+		break;
+
+	case REGT_STRING:
+		assert(!(regtype & REGT_MULTIREG));
+		if (regtype & REGT_KONST)
+		{
+			assert(regnum < func->NumKonstS);
+			src = &func->KonstS[regnum];
+		}
+		else
+		{
+			assert(regnum < frame->NumRegS);
+			src = &reg.s[regnum];
+		}
+		ret->SetString(*(const FString *)src);
+		break;
+
+	case REGT_POINTER:
+		assert(!(regtype & REGT_MULTIREG));
+		if (regtype & REGT_KONST)
+		{
+			assert(regnum < func->NumKonstA);
+			ret->SetPointer(func->KonstA[regnum]);
+			atag = ATAG_OBJECT;
+		}
+		else
+		{
+			assert(regnum < frame->NumRegA);
+			ret->SetPointer(reg.a[regnum]);
+			atag = reg.atag[regnum];
+		}
+		if (ret->RegNum >= 0)
+		{
+			VMFrame *parent = frame->ParentFrame;
+			assert(parent != NULL);
+			parent->GetRegATag()[ret->RegNum] = atag;
+		}
+		break;
+	}
+}
diff --git a/zscript/vmframe.cpp b/zscript/vmframe.cpp
new file mode 100644
index 000000000..208bc4725
--- /dev/null
+++ b/zscript/vmframe.cpp
@@ -0,0 +1,294 @@
+#include <new>
+#include "vm.h"
+
+
+//===========================================================================
+//
+// VMFrame :: InitRegS
+//
+// Initialize the string registers of a newly-allocated VMFrame.
+//
+//===========================================================================
+
+void VMFrame::InitRegS()
+{
+	FString *regs = GetRegS();
+	for (int i = 0; i < NumRegS; ++i)
+	{
+		::new(&regs[i]) FString;
+	}
+}
+
+//===========================================================================
+//
+// VMFrameStack - Constructor
+//
+//===========================================================================
+
+VMFrameStack::VMFrameStack()
+{
+	Blocks = NULL;
+	UnusedBlocks = NULL;
+}
+
+//===========================================================================
+//
+// VMFrameStack - Destructor
+//
+//===========================================================================
+
+VMFrameStack::~VMFrameStack()
+{
+	while (PopFrame() != NULL)
+	{ }
+	if (Blocks != NULL)
+	{
+		BlockHeader *block, *next;
+		for (block = Blocks; block != NULL; block = next)
+		{
+			next = block->NextBlock;
+			delete[] (VM_UBYTE *)block;
+		}
+	}
+	if (UnusedBlocks != NULL)
+	{
+		BlockHeader *block, *next;
+		for (block = UnusedBlocks; block != NULL; block = next)
+		{
+			next = block->NextBlock;
+			delete[] (VM_UBYTE *)block;
+		}
+	}
+	Blocks = NULL;
+	UnusedBlocks = NULL;
+}
+
+//===========================================================================
+//
+// VMFrameStack :: AllocFrame
+//
+// Allocates a frame from the stack with the desired number of registers.
+//
+//===========================================================================
+
+VMFrame *VMFrameStack::AllocFrame(int numregd, int numregf, int numregs, int numrega)
+{
+	assert((unsigned)numregd < 255);
+	assert((unsigned)numregf < 255);
+	assert((unsigned)numregs < 255);
+	assert((unsigned)numrega < 255);
+	// To keep the arguments to this function simpler, it assumes that every
+	// register might be used as a parameter for a single call.
+	int numparam = numregd + numregf + numregs + numrega;
+	int size = VMFrame::FrameSize(numregd, numregf, numregs, numrega, numparam, 0);
+	VMFrame *frame = Alloc(size);
+	frame->NumRegD = numregd;
+	frame->NumRegF = numregf;
+	frame->NumRegS = numregs;
+	frame->NumRegA = numrega;
+	frame->MaxParam = numparam;
+	frame->InitRegS();
+	return frame;
+}
+
+//===========================================================================
+//
+// VMFrameStack :: AllocFrame
+//
+// Allocates a frame from the stack suitable for calling a particular
+// function.
+//
+//===========================================================================
+
+VMFrame *VMFrameStack::AllocFrame(VMScriptFunction *func)
+{
+	int size = VMFrame::FrameSize(func->NumRegD, func->NumRegF, func->NumRegS, func->NumRegA,
+		func->MaxParam, func->ExtraSpace);
+	VMFrame *frame = Alloc(size);
+	frame->Func = func;
+	frame->NumRegD = func->NumRegD;
+	frame->NumRegF = func->NumRegF;
+	frame->NumRegS = func->NumRegS;
+	frame->NumRegA = func->NumRegA;
+	frame->MaxParam = func->MaxParam;
+	frame->Func = func;
+	frame->InitRegS();
+	return frame;
+}
+
+//===========================================================================
+//
+// VMFrameStack :: Alloc
+//
+// Allocates space for a frame. Its size will be rounded up to a multiple
+// of 16 bytes.
+//
+//===========================================================================
+
+VMFrame *VMFrameStack::Alloc(int size)
+{
+	BlockHeader *block;
+	VMFrame *frame, *parent;
+
+	size = (size + 15) & ~15;
+	block = Blocks;
+	if (block != NULL)
+	{
+		parent = block->LastFrame;
+	}
+	else
+	{
+		parent = NULL;
+	}
+	if (block == NULL || ((VM_UBYTE *)block + block->BlockSize) < (block->FreeSpace + size))
+	{ // Not enough space. Allocate a new block.
+		int blocksize = ((sizeof(BlockHeader) + 15) & ~15) + size;
+		BlockHeader **blockp;
+		if (blocksize < BLOCK_SIZE)
+		{
+			blocksize = BLOCK_SIZE;
+		}
+		for (blockp = &UnusedBlocks, block = *blockp; block != NULL; block = block->NextBlock)
+		{
+			if (block->BlockSize >= blocksize)
+			{
+				break;
+			}
+		}
+		if (block != NULL)
+		{
+			*blockp = block->NextBlock;
+		}
+		else
+		{
+			block = (BlockHeader *)new VM_UBYTE[blocksize];
+			block->BlockSize = blocksize;
+		}
+		block->FreeSpace = (VM_UBYTE *)block + ((sizeof(BlockHeader) + 15) & ~15);
+		block->LastFrame = NULL;
+		block->NextBlock = Blocks;
+		Blocks = block;
+	}
+	frame = (VMFrame *)block->FreeSpace;
+	memset(frame, 0, size);
+	frame->ParentFrame = parent;
+	block->FreeSpace += size;
+	block->LastFrame = frame;
+	return frame;
+}
+
+
+//===========================================================================
+//
+// VMFrameStack :: PopFrame
+//
+// Pops the top frame off the stack, returning a pointer to the new top
+// frame.
+//
+//===========================================================================
+
+VMFrame *VMFrameStack::PopFrame()
+{
+	if (Blocks == NULL)
+	{
+		return NULL;
+	}
+	VMFrame *frame = Blocks->LastFrame;
+	if (frame == NULL)
+	{
+		return NULL;
+	}
+	// Free any string registers this frame had.
+	FString *regs = frame->GetRegS();
+	for (int i = frame->NumRegS; i != 0; --i)
+	{
+		(regs++)->~FString();
+	}
+	// Free any parameters this frame left behind.
+	VMValue *param = frame->GetParam();
+	for (int i = frame->NumParam; i != 0; --i)
+	{
+		(param++)->~VMValue();
+	}
+	VMFrame *parent = frame->ParentFrame;
+	if (parent == NULL)
+	{
+		// Popping the last frame off the stack.
+		if (Blocks != NULL)
+		{
+			assert(Blocks->NextBlock == NULL);
+			Blocks->LastFrame = NULL;
+			Blocks->FreeSpace = (VM_UBYTE *)Blocks + ((sizeof(BlockHeader) + 15) & ~15);
+		}
+		return NULL;
+	}
+	if ((VM_UBYTE *)parent < (VM_UBYTE *)Blocks || (VM_UBYTE *)parent >= (VM_UBYTE *)Blocks + Blocks->BlockSize)
+	{ // Parent frame is in a different block, so move this one to the unused list.
+		BlockHeader *next = Blocks->NextBlock;
+		assert(next != NULL);
+		assert((VM_UBYTE *)parent >= (VM_UBYTE *)next && (VM_UBYTE *)parent < (VM_UBYTE *)next + next->BlockSize);
+		Blocks->NextBlock = UnusedBlocks;
+		UnusedBlocks = Blocks;
+		Blocks = next;
+	}
+	else
+	{
+		Blocks->LastFrame = parent;
+		Blocks->FreeSpace = (VM_UBYTE *)frame;
+	}
+	return parent;
+}
+
+//===========================================================================
+//
+// VMFrameStack :: Call
+//
+// Calls a function, either native or scripted. If an exception occurs while
+// executing, the stack is cleaned up. If trap is non-NULL, it is set to the
+// VMException that was caught and the return value is negative. Otherwise,
+// any caught exceptions will be rethrown. Under normal termination, the
+// return value is the number of results from the function.
+//
+//===========================================================================
+
+int VMFrameStack::Call(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults, VMException **trap)
+{
+	bool allocated = false;
+	try
+	{
+		if (func->Native)
+		{
+			return static_cast<VMNativeFunction *>(func)->NativeCall(this, params, numparams, results, numresults);
+		}
+		else
+		{
+			AllocFrame(static_cast<VMScriptFunction *>(func));
+			allocated = true;
+			VMFillParams(params, TopFrame(), numparams);
+			int numret = VMExec(this, static_cast<VMScriptFunction *>(func)->Code, results, numresults);
+			PopFrame();
+			return numret;
+		}
+	}
+	catch (VMException *exception)
+	{
+		if (allocated)
+		{
+			PopFrame();
+		}
+		if (trap != NULL)
+		{
+			*trap = exception;
+			return -1;
+		}
+		throw;
+	}
+	catch (...)
+	{
+		if (allocated)
+		{
+			PopFrame();
+		}
+		throw;
+	}
+}
diff --git a/zscript/vmops.h b/zscript/vmops.h
new file mode 100644
index 000000000..f7d821bb9
--- /dev/null
+++ b/zscript/vmops.h
@@ -0,0 +1,210 @@
+#ifndef xx
+#define xx(op, name, mode) OP_##op
+#endif
+
+// Load constants.
+xx(LI,		li,		LI),		// load immediate signed 16-bit constant
+xx(LK,		lk,		LKI),		// load integer constant
+xx(LKF,		lk,		LKF),		// load float constant
+xx(LKS,		lk,		LKS),		// load string constant
+xx(LKP,		lk,		LKP),		// load pointer constant
+xx(LFP,		lf,		LFP),		// load frame pointer
+
+// Load from memory. rA = *(rB + rkC)
+xx(LB,		lb,		RIRPKI),	// load byte
+xx(LB_R,	lb,		RIRPRI),
+xx(LH,		lh,		RIRPKI),	// load halfword
+xx(LH_R,	lh,		RIRPRI),
+xx(LW,		lw,		RIRPKI),	// load word
+xx(LW_R,	lw,		RIRPRI),
+xx(LBU,		lbu,	RIRPKI),	// load byte unsigned
+xx(LBU_R,	lbu,	RIRPRI),
+xx(LHU,		lhu,	RIRPKI),	// load halfword unsigned
+xx(LHU_R,	lhu,	RIRPRI),
+xx(LSP,		lsp,	RFRPKI),	// load single-precision fp
+xx(LSP_R,	lsp,	RFRPRI),
+xx(LDP,		ldp,	RFRPKI),	// load double-precision fp
+xx(LDP_R,	ldp,	RFRPRI),
+xx(LS,		ls,		RSRPKI),	// load string
+xx(LS_R,	ls,		RSRPRI),
+xx(LO,		lo,		RPRPKI),	// load object
+xx(LO_R,	lo,		RPRPRI),
+xx(LP,		lp,		RPRPKI),	// load pointer
+xx(LP_R,	lp,		RPRPRI),
+xx(LV,		lv,		RVRPKI),	// load vector
+xx(LV_R,	lv,		RVRPRI),
+xx(LX,		lx,		RFRPKI),	// load fixed point
+xx(LX_R,	lx,		RFRPRI),
+
+xx(LBIT,	lbit,	RIRPI8),	// rA = !!(*rB & C)  -- *rB is a byte
+
+// Store instructions. *(rA + rkC) = rB
+xx(SB,		sb,		RPRIKI),		// store byte
+xx(SB_R,	sb,		RPRIRI),
+xx(SH,		sh,		RPRIKI),		// store halfword
+xx(SH_R,	sh,		RPRIRI),
+xx(SW,		sw,		RPRIKI),		// store word
+xx(SW_R,	sw,		RPRIRI),
+xx(SSP,		ssp,	RPRFKI),		// store single-precision fp
+xx(SSP_R,	ssp,	RPRFRI),
+xx(SDP,		sdp,	RPRFKI),		// store double-precision fp
+xx(SDP_R,	sdp,	RPRFRI),
+xx(SS,		ss,		RPRSKI),		// store string
+xx(SS_R,	ss,		RPRSRI),
+xx(SP,		sp,		RPRPKI),		// store pointer
+xx(SP_R,	sp,		RPRPRI),
+xx(SV,		sv,		RPRVKI),		// store vector
+xx(SV_R,	sv,		RPRVRI),
+xx(SX,		sx,		RPRFKI),		// store fixed point
+xx(SX_R,	sx,		RPRFRI),
+
+xx(SBIT,	sbit,	RPRII8),		// *rA |= C if rB is true, *rA &= ~C otherwise
+
+// Move instructions.
+xx(MOVE,		mov,	RIRI),		// dA = dB
+xx(MOVEF,		mov,	RFRF),		// fA = fB
+xx(MOVES,		mov,	RSRS),		// sA = sB
+xx(MOVEA,		mov,	RPRP),		// aA = aB
+xx(CAST,		cast,	CAST),		// xA = xB, conversion specified by C
+xx(DYNCAST_R,	dyncast,RPRPRP),	// aA = aB after casting to rkC (specifying a class)
+xx(DYNCAST_K,	dyncast,RPRPKP),
+
+// Control flow.
+xx(TEST,	test,	RII16),		// if (dA != BC) then pc++
+xx(JMP,		jmp,	I24),		// pc += ABC		-- The ABC fields contain a signed 24-bit offset.
+xx(IJMP,	ijmp,	RII16),		// pc += dA + BC	-- BC is a signed offset. The target instruction must be a JMP.
+xx(PARAM,	param,	__BCP),		// push parameter encoded in BC for function call or result for return
+xx(CALL,	call,	RPI8I8),	// Call function pkA with parameter count B and expected result count C
+xx(CALL_K,	call,	KPI8I8),
+xx(RESULT,	result,	__BCP),		// Result should go in register encoded in BC (in caller, after CALL)
+xx(RET,		ret,	I8BCP),		// Copy value from register encoded in BC to return value A, possibly returning
+xx(TRY,		try,	I24),		// When an exception is thrown, start searching for a handler at pc + ABC
+xx(UNTRY,	untry,	I8),		// Pop A entries off the exception stack
+xx(THROW,	throw,	THROW),		// A == 0: Throw exception object pB
+								// A != 0: Throw exception object pkB
+xx(CATCH,	catch,	CATCH),		// A == 0: continue search on next try
+								// A == 1: continue execution at instruction immediately following CATCH (catches any exception)
+								// A == 2: (pB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
+								// A == 3: (pkB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
+								// for A > 0, exception is stored in pC
+
+// String instructions.
+xx(CONCAT,		concat,	RSRSRS),		// sA = sB.. ... ..sC
+xx(LENS,		lens,	RIRS),			// dA = sB.Length
+xx(CMPS,		cmps,	I8RXRX),		// if ((skB op skC) != (A & 1)) then pc++
+
+// Integer math.
+xx(SLL_RR,		sll,	RIRIRI),		// dA = dkB << diC
+xx(SLL_RI,		sll,	RIRII8),
+xx(SLL_KR,		sll,	RIKIRI),
+xx(SRL_RR,		srl,	RIRIRI),		// dA = dkB >> diC  -- unsigned
+xx(SRL_RI,		srl,	RIRII8),
+xx(SRL_KR,		srl,	RIKIRI),
+xx(SRA_RR,		sra,	RIRIRI),		// dA = dkB >> diC  -- signed
+xx(SRA_RI,		sra,	RIRII8),
+xx(SRA_KR,		sra,	RIKIRI),
+xx(ADD_RR,		add,	RIRIRI),		// dA = dB + dkC
+xx(ADD_RK,		add,	RIRIKI),
+xx(ADDI,		add,	RIRIIs),		// dA = dB + C		-- C is a signed 8-bit constant
+xx(SUB_RR,		sub,	RIRIRI),		// dA = dkB - dkC
+xx(SUB_RK,		sub,	RIRIKI),
+xx(SUB_KR,		sub,	RIKIRI),
+xx(MUL_RR,		mul,	RIRIRI),		// dA = dB * dkC
+xx(MUL_RK,		mul,	RIRIKI),
+xx(DIV_RR,		div,	RIRIRI),		// dA = dkB / dkC
+xx(DIV_RK,		div,	RIRIKI),
+xx(DIV_KR,		div,	RIKIRI),
+xx(MOD_RR,		mod,	RIRIRI),		// dA = dkB % dkC
+xx(MOD_RK,		mod,	RIRIKI),
+xx(MOD_KR,		mod,	RIKIRI),
+xx(AND_RR,		and,	RIRIRI),		// dA = dB & dkC
+xx(AND_RK,		and,	RIRIKI),
+xx(OR_RR,		or,		RIRIRI),		// dA = dB | dkC
+xx(OR_RK,		or,		RIRIKI),
+xx(XOR_RR,		xor,	RIRIRI),		// dA = dB ^ dkC
+xx(XOR_RK,		xor,	RIRIKI),
+xx(MIN_RR,		min,	RIRIRI),		// dA = min(dB,dkC)
+xx(MIN_RK,		min,	RIRIKI),
+xx(MAX_RR,		max,	RIRIRI),		// dA = max(dB,dkC)
+xx(MAX_RK,		max,	RIRIKI),
+xx(ABS,			abs,	RIRI),			// dA = abs(dB)
+xx(NEG,			neg,	RIRI),			// dA = -dB
+xx(NOT,			not,	RIRI),			// dA = !dB
+xx(SEXT,		sext,	RIRII8),		// dA = dB, sign extended by shifting left then right by C
+xx(ZAP_R,		zap,	RIRIRI),		// dA = dB, with bytes zeroed where bits in C/dC are one
+xx(ZAP_I,		zap,	RIRII8),
+xx(ZAPNOT_R,	zapnot,	RIRIRI),		// dA = dB, with bytes zeroed where bits in C/dC are zero
+xx(ZAPNOT_I,	zapnot,	RIRII8),
+xx(EQ_R,		eq,		I8RIRI),		// if ((dB == dkC) != A) then pc++
+xx(EQ_K,		eq,		I8RIKI),
+xx(LT_RR,		lt,		I8RIRI),		// if ((dkB < dkC) != A) then pc++
+xx(LT_RK,		lt,		I8RIKI),
+xx(LT_KR,		lt,		I8KIRI),
+xx(LE_RR,		le,		I8RIRI),		// if ((dkB <= dkC) != A) then pc++
+xx(LE_RK,		le,		I8RIKI),
+xx(LE_KR,		le,		I8KIRI),
+xx(LTU_RR,		ltu,	I8RIRI),		// if ((dkB < dkC) != A) then pc++		-- unsigned
+xx(LTU_RK,		ltu,	I8RIKI),
+xx(LTU_KR,		ltu,	I8KIRI),	
+xx(LEU_RR,		leu,	I8RIRI),		// if ((dkB <= dkC) != A) then pc++		-- unsigned
+xx(LEU_RK,		leu,	I8RIKI),
+xx(LEU_KR,		leu,	I8KIRI),
+
+// Double-precision floating point math.
+xx(ADDF_RR,		add,	RFRFRF),		// fA = fB + fkC
+xx(ADDF_RK,		add,	RFRFKF),
+xx(SUBF_RR,		sub,	RFRFRF),		// fA = fkB - fkC
+xx(SUBF_RK,		sub,	RFRFKF),
+xx(SUBF_KR,		sub,	RFKFRF),
+xx(MULF_RR,		mul,	RFRFRF),		// fA = fB * fkC
+xx(MULF_RK,		mul,	RFRFKF),
+xx(DIVF_RR,		div,	RFRFRF),		// fA = fkB / fkC
+xx(DIVF_RK,		div,	RFRFKF),
+xx(DIVF_KR,		div,	RFKFRF),
+xx(MODF_RR,		mod,	RFRFRF),		// fA = fkB % fkC
+xx(MODF_RK,		mod,	RFRFKF),
+xx(MODF_KR,		mod,	RFKFRF),
+xx(POWF_RR,		pow,	RFRFRF),		// fA = fkB ** fkC
+xx(POWF_RK,		pow,	RFRFKF),
+xx(POWF_KR,		pow,	RFKFRF),
+xx(MINF_RR,		min,	RFRFRF),		// fA = min(fB),fkC)
+xx(MINF_RK,		min,	RFRFKF),
+xx(MAXF_RR,		max,	RFRFRF),		// fA = max(fB),fkC)
+xx(MAXF_RK,		max,	RFRFKF),
+xx(FLOP,		flop,	RFRFI8),		// fA = f(fB), where function is selected by C
+xx(EQF_R,		eq,		I8RFRF),		// if ((fB == fkC) != (A & 1)) then pc++
+xx(EQF_K,		eq,		I8RFKF),
+xx(LTF_RR,		lt,		I8RFRF),		// if ((fkB < fkC) != (A & 1)) then pc++
+xx(LTF_RK,		lt,		I8RFKF),
+xx(LTF_KR,		lt,		I8KFRF),
+xx(LEF_RR,		le,		I8RFRF),		// if ((fkb <= fkC) != (A & 1)) then pc++
+xx(LEF_RK,		le,		I8RFKF),
+xx(LEF_KR,		le,		I8KFRF),
+
+// Vector math.
+xx(NEGV,		negv,	RVRV),			// vA = -vB
+xx(ADDV_RR,		addv,	RVRVRV),		// vA = vB + vkC
+xx(ADDV_RK,		addv,	RVRVKV),
+xx(SUBV_RR,		subv,	RVRVRV),		// vA = vkB - vkC
+xx(SUBV_RK,		subv,	RVRVKV),
+xx(SUBV_KR,		subv,	RVKVRV),
+xx(DOTV_RR,		dotv,	RVRVRV),		// va = vB dot vkC
+xx(DOTV_RK,		dotv,	RVRVKV),
+xx(CROSSV_RR,	crossv,	RVRVRV),		// vA = vkB cross vkC
+xx(CROSSV_RK,	crossv,	RVRVKV),
+xx(CROSSV_KR,	crossv,	RVKVRV),
+xx(MULVF_RR,	mulv,	RVRVRV),		// vA = vkB * fkC
+xx(MULVF_RK,	mulv,	RVRVKV),
+xx(MULVF_KR,	mulv,	RVKVRV),
+xx(LENV,		lenv,	RFRV),			// fA = vB.Length
+xx(EQV_R,		eqv,	I8RVRV),		// if ((vB == vkC) != A) then pc++ (inexact if A & 32)
+xx(EQV_K,		eqv,	I8RVKV),
+
+// Pointer math.
+xx(ADDA_RR,		add,	RPRPRI),		// pA = pB + dkC
+xx(ADDA_RK,		add,	RPRPKI),
+xx(SUBA,		sub,	RIRPRP),		// dA = pB - pC
+xx(EQA_R,		eq,		I8RPRP),		// if ((pB == pkC) != A) then pc++
+xx(EQA_K,		eq,		I8RPKP),
+
+#undef xx