From c9b2399cd04bfd8d61573e8ad9328d7863387f21 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@users.noreply.github.com>
Date: Mon, 6 Apr 2020 21:10:07 +0200
Subject: [PATCH] - added a first bunch of ZScript code.

# Conflicts:
#	source/CMakeLists.txt
#	source/common/utility/basics.h
#	source/core/serializer.h
---
 CMakeLists.txt                                |   63 +-
 source/CMakeLists.txt                         |   32 +-
 source/common/audio/sound/s_soundinternal.h   |   13 +-
 source/common/objects/dobjgc.cpp              |    2 +-
 source/common/objects/dobjtype.cpp            |   26 +-
 source/common/objects/dobjtype.h              |    8 +-
 source/common/scripting/core/scopebarrier.cpp |  224 ++
 source/common/scripting/core/scopebarrier.h   |   70 +
 source/common/scripting/core/symbols.cpp      |  605 ++++
 source/common/scripting/core/symbols.h        |  269 ++
 source/common/scripting/core/types.cpp        | 2480 +++++++++++++++++
 source/common/scripting/core/types.h          |  625 +++++
 source/common/scripting/core/vmdisasm.cpp     |  687 +++++
 .../scripting/interface/stringformat.cpp      |  278 ++
 source/common/scripting/jit/jit.cpp           |  564 ++++
 source/common/scripting/jit/jit.h             |    8 +
 source/common/scripting/jit/jit_call.cpp      |  690 +++++
 source/common/scripting/jit/jit_flow.cpp      |  319 +++
 source/common/scripting/jit/jit_load.cpp      |  360 +++
 source/common/scripting/jit/jit_math.cpp      | 1524 ++++++++++
 source/common/scripting/jit/jit_move.cpp      |  272 ++
 source/common/scripting/jit/jit_runtime.cpp   |  970 +++++++
 source/common/scripting/jit/jit_store.cpp     |  176 ++
 source/common/scripting/jit/jitintern.h       |  337 +++
 source/common/scripting/vm/vm.h               |  790 ++++++
 source/common/scripting/vm/vmexec.cpp         |  253 ++
 source/common/scripting/vm/vmexec.h           | 2017 ++++++++++++++
 source/common/scripting/vm/vmframe.cpp        |  771 +++++
 source/common/scripting/vm/vmintern.h         |  483 ++++
 source/common/scripting/vm/vmops.h            |  258 ++
 source/common/utility/basics.h                |   51 +-
 source/core/raze_sound.h                      |   10 -
 source/core/serializer.cpp                    |  110 +-
 source/core/serializer.h                      |   22 +-
 34 files changed, 15258 insertions(+), 109 deletions(-)
 create mode 100644 source/common/scripting/core/scopebarrier.cpp
 create mode 100644 source/common/scripting/core/scopebarrier.h
 create mode 100644 source/common/scripting/core/symbols.cpp
 create mode 100644 source/common/scripting/core/symbols.h
 create mode 100644 source/common/scripting/core/types.cpp
 create mode 100644 source/common/scripting/core/types.h
 create mode 100644 source/common/scripting/core/vmdisasm.cpp
 create mode 100644 source/common/scripting/interface/stringformat.cpp
 create mode 100644 source/common/scripting/jit/jit.cpp
 create mode 100644 source/common/scripting/jit/jit.h
 create mode 100644 source/common/scripting/jit/jit_call.cpp
 create mode 100644 source/common/scripting/jit/jit_flow.cpp
 create mode 100644 source/common/scripting/jit/jit_load.cpp
 create mode 100644 source/common/scripting/jit/jit_math.cpp
 create mode 100644 source/common/scripting/jit/jit_move.cpp
 create mode 100644 source/common/scripting/jit/jit_runtime.cpp
 create mode 100644 source/common/scripting/jit/jit_store.cpp
 create mode 100644 source/common/scripting/jit/jitintern.h
 create mode 100644 source/common/scripting/vm/vm.h
 create mode 100644 source/common/scripting/vm/vmexec.cpp
 create mode 100644 source/common/scripting/vm/vmexec.h
 create mode 100644 source/common/scripting/vm/vmframe.cpp
 create mode 100644 source/common/scripting/vm/vmintern.h
 create mode 100644 source/common/scripting/vm/vmops.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9fdda2df9..7b54851b0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -177,11 +177,12 @@ find_package( ZLIB )
 
 include( TargetArch )
 
-# Things for later. Currently we have no VM and no Vulkan
-#if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" )
-#	set( HAVE_VM_JIT ON )
-#	option (HAVE_VULKAN "Enable Vulkan support" ON)
-#endif()
+target_architecture(TARGET_ARCHITECTURE)
+
+if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" )
+	set( HAVE_VM_JIT ON )
+	#option (HAVE_VULKAN "Enable Vulkan support" ON)
+endif()
 
 # no, we're not using external asmjit for now, we made too many modifications to our's.
 # if the asmjit author uses our changes then we'll update this.
@@ -306,8 +307,8 @@ set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${DEB_C_FLAGS} -D_DEBUG" )
 option(FORCE_INTERNAL_ZLIB "Use internal zlib")
 option(FORCE_INTERNAL_JPEG "Use internal jpeg")
 option(FORCE_INTERNAL_BZIP2 "Use internal bzip2")
-#option(FORCE_INTERNAL_ASMJIT "Use internal asmjit" ON)
-#mark_as_advanced( FORCE_INTERNAL_ASMJIT )
+option(FORCE_INTERNAL_ASMJIT "Use internal asmjit" ON)
+mark_as_advanced( FORCE_INTERNAL_ASMJIT )
 
 if (HAVE_VULKAN)
 	add_subdirectory( libraries/glslang/glslang)
@@ -334,31 +335,31 @@ else()
 	set( ZLIB_LIBRARY z )
 endif()
 
-#if( HAVE_VM_JIT AND UNIX )
-#	check_symbol_exists( "backtrace" "execinfo.h" HAVE_BACKTRACE )
-#	if( NOT HAVE_BACKTRACE )
-#		set( CMAKE_REQUIRED_FLAGS "-lexecinfo" )
-#		check_symbol_exists( "backtrace" "execinfo.h" HAVE_LIBEXECINFO )
-#		if( HAVE_LIBEXECINFO )
-#			set( ALL_C_FLAGS "${ALL_C_FLAGS} -lexecinfo" )
-#		else( HAVE_LIBEXECINFO )
-#			set( HAVE_VM_JIT NO )
-#		endif( HAVE_LIBEXECINFO )
-#	endif( NOT HAVE_BACKTRACE )
-#endif( HAVE_VM_JIT AND UNIX )
+if( HAVE_VM_JIT AND UNIX )
+	check_symbol_exists( "backtrace" "execinfo.h" HAVE_BACKTRACE )
+	if( NOT HAVE_BACKTRACE )
+		set( CMAKE_REQUIRED_FLAGS "-lexecinfo" )
+		check_symbol_exists( "backtrace" "execinfo.h" HAVE_LIBEXECINFO )
+		if( HAVE_LIBEXECINFO )
+			set( ALL_C_FLAGS "${ALL_C_FLAGS} -lexecinfo" )
+		else( HAVE_LIBEXECINFO )
+			set( HAVE_VM_JIT NO )
+		endif( HAVE_LIBEXECINFO )
+	endif( NOT HAVE_BACKTRACE )
+endif( HAVE_VM_JIT AND UNIX )
 
-#if( ${HAVE_VM_JIT} )
-#	if( ASMJIT_FOUND AND NOT FORCE_INTERNAL_ASMJIT )
-#		message( STATUS "Using system asmjit, includes found at ${ASMJIT_INCLUDE_DIR}" )
-#	else()
-#		message( STATUS "Using internal asmjit" )
-#		set( SKIP_INSTALL_ALL TRUE ) # Avoid installing asmjit
-#		add_subdirectory( libraries/asmjit )
-#		set( ASMJIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/asmjit )
-#		set( ASMJIT_LIBRARIES asmjit )
-#		set( ASMJIT_LIBRARY asmjit )
-#	endif()
-#endif()
+if( ${HAVE_VM_JIT} )
+	if( ASMJIT_FOUND AND NOT FORCE_INTERNAL_ASMJIT )
+		message( STATUS "Using system asmjit, includes found at ${ASMJIT_INCLUDE_DIR}" )
+	else()
+		message( STATUS "Using internal asmjit" )
+		set( SKIP_INSTALL_ALL TRUE ) # Avoid installing asmjit alongside zdoom
+		add_subdirectory( libraries/asmjit )
+		set( ASMJIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/asmjit )
+		set( ASMJIT_LIBRARIES asmjit )
+		set( ASMJIT_LIBRARY asmjit )
+	endif()
+endif()
 
 if( JPEG_FOUND AND NOT FORCE_INTERNAL_JPEG )
 	message( STATUS "Using system jpeg library, includes found at ${JPEG_INCLUDE_DIR}" )
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 93b6c92f3..9dbbdd373 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -611,7 +611,11 @@ file( GLOB HEADER_FILES
 	common/textures/*.h
 	common/thirdparty/*.h
 	common/thirdparty/rapidjson/*.h
-	common/thirdparty/math./*h
+	common/thirdparty/math/*h
+	common/scripting/core/*h
+	common/scripting/vm/*h
+	common/scripting/jit/*h
+	common/scripting/interface/*.h
 
 	build/src/*.h
 	platform/win32/*.h
@@ -775,6 +779,21 @@ set (PCH_SOURCES
 	common/objects/dobject.cpp
 	common/objects/dobjgc.cpp
 	common/objects/dobjtype.cpp
+	common/scripting/core/symbols.cpp
+	common/scripting/core/types.cpp
+	common/scripting/core/scopebarrier.cpp
+	common/scripting/core/vmdisasm.cpp
+	common/scripting/vm/vmexec.cpp
+	common/scripting/vm/vmframe.cpp
+	common/scripting/jit/jit.cpp
+	common/scripting/jit/jit_call.cpp
+	common/scripting/jit/jit_flow.cpp
+	common/scripting/jit/jit_load.cpp
+	common/scripting/jit/jit_math.cpp
+	common/scripting/jit/jit_move.cpp
+	common/scripting/jit/jit_runtime.cpp
+	common/scripting/jit/jit_store.cpp
+	common/scripting/interface/stringformat.cpp
 
 	core/utility/stats.cpp
 	
@@ -927,6 +946,11 @@ include_directories(
 	common/utility
 	common/console
 	common/engine
+	common/objects
+	common/scripting/interface
+	common/scripting/core
+	common/scripting/vm
+	common/scripting/jit
 	${CMAKE_BINARY_DIR}/libraries/gdtoa
 
 	${SYSTEM_SOURCES_DIR}
@@ -1042,7 +1066,11 @@ source_group("Common\\Engine" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/c
 source_group("Common\\Objects" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/objects/.+")
 source_group("Common\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/fonts/.+")
 source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+")
-source_group("Common\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/.+")
+source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+")
+source_group("Common\\Scripting" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/.+")
+source_group("Common\\Scripting\\Core" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/core/.+")
+source_group("Common\\Scripting\\VM" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/vm/.+")
+source_group("Common\\Scripting\\JIT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/jit/.+")
 source_group("Common\\Third Party" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/.+")
 source_group("Common\\Third Party\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/math/.+")
 source_group("Common\\Third Party\\RapidJSON" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/rapidjson/.+")
diff --git a/source/common/audio/sound/s_soundinternal.h b/source/common/audio/sound/s_soundinternal.h
index 0d0060e9f..b1c55b308 100644
--- a/source/common/audio/sound/s_soundinternal.h
+++ b/source/common/audio/sound/s_soundinternal.h
@@ -91,8 +91,8 @@ enum
 	ROLLOFF_Custom		// Lookup volume from SNDCURVE
 };
 
-int S_FindSound(const char *logicalname);
-int S_FindSoundByResID(int snd_id);
+inline int S_FindSoundByResID(int ndx);
+inline int S_FindSound(const char* name);
 
 // An index into the S_sfx[] array.
 class FSoundID
@@ -430,3 +430,12 @@ struct FReverbField
 };
 
 
+inline int S_FindSoundByResID(int ndx)
+{
+	return soundEngine->FindSoundByResID(ndx);
+}
+
+inline int S_FindSound(const char* name)
+{
+	return soundEngine->FindSound(name);
+}
diff --git a/source/common/objects/dobjgc.cpp b/source/common/objects/dobjgc.cpp
index 201e69fc7..96ff138c8 100644
--- a/source/common/objects/dobjgc.cpp
+++ b/source/common/objects/dobjgc.cpp
@@ -264,7 +264,7 @@ void MarkArray(DObject **obj, size_t count)
 
 static void MarkRoot()
 {
-	int i;
+	//int i;
 
 	Gray = NULL;
 	// Time to propagate the marks.
diff --git a/source/common/objects/dobjtype.cpp b/source/common/objects/dobjtype.cpp
index a68cb11be..293685f4f 100644
--- a/source/common/objects/dobjtype.cpp
+++ b/source/common/objects/dobjtype.cpp
@@ -42,6 +42,8 @@
 #include "autosegs.h"
 #include "v_text.h"
 #include "c_cvars.h"
+#include "symbols.h"
+#include "types.h"
 
 // MACROS ------------------------------------------------------------------
 
@@ -89,7 +91,6 @@ static const size_t TheEnd = ~(size_t)0;
 
 static void RecurseWriteFields(const PClass *type, FSerializer &ar, const void *addr)
 {
-#if 0
 	if (type != nullptr)
 	{
 		RecurseWriteFields(type->ParentClass, ar, addr);
@@ -113,7 +114,6 @@ static void RecurseWriteFields(const PClass *type, FSerializer &ar, const void *
 			}
 		}
 	}
-#endif
 }
 
 // Same as WriteValue, but does not create a new object in the serializer
@@ -133,7 +133,6 @@ bool PClass::ReadAllFields(FSerializer &ar, void *addr) const
 {
 	bool readsomething = false;
 	bool foundsomething = false;
-#if 0
 	const char *key;
 	key = ar.GetKey();
 	if (strcmp(key, "classtype"))
@@ -175,7 +174,6 @@ bool PClass::ReadAllFields(FSerializer &ar, void *addr) const
 				key+6, TypeName.GetChars());
 		}
 	}
-#endif
 	return readsomething || !foundsomething;
 }
 
@@ -285,7 +283,7 @@ void PClass::StaticShutdown ()
 	for (auto cls : AllClasses)	delete cls;
 	// Unless something went wrong, anything left here should be class and type objects only, which do not own any scripts.
 	bShutdown = true;
-	//TypeTable.Clear();
+	TypeTable.Clear();
 	ClassDataAllocator.FreeAllBlocks();
 	AllClasses.Clear();
 	//PClassActor::AllActorClasses.Clear();
@@ -492,12 +490,10 @@ void PClass::InitializeSpecials(void *addr, void *defaults, TArray<FTypeAndOffse
 		return;
 	}
 	ParentClass->InitializeSpecials(addr, defaults, Inits);
-#if 0
 	for (auto tao : (this->*Inits))
 	{
 		tao.first->InitializeValue((char*)addr + tao.second, defaults == nullptr? nullptr : ((char*)defaults) + tao.second);
 	}
-#endif
 }
 
 //==========================================================================
@@ -517,12 +513,10 @@ void PClass::DestroySpecials(void *addr)
 	}
 	assert(ParentClass != nullptr);
 	ParentClass->DestroySpecials(addr);
-#if 0
 	for (auto tao : SpecialInits)
 	{
 		tao.first->DestroyValue((uint8_t *)addr + tao.second);
 	}
-#endif
 }
 
 //==========================================================================
@@ -536,12 +530,10 @@ void PClass::DestroySpecials(void *addr)
 void PClass::DestroyMeta(void *addr)
 {
 	if (ParentClass != nullptr) ParentClass->DestroyMeta(addr);
-#if 0
 	for (auto tao : MetaInits)
 	{
 		tao.first->DestroyValue((uint8_t *)addr + tao.second);
 	}
-#endif
 }
 
 //==========================================================================
@@ -619,9 +611,7 @@ PClass *PClass::CreateDerivedClass(FName name, unsigned int size)
 	type->Size = size;
 	if (size != TentativeClass)
 	{
-#if 0
 		NewClassType(type);
-#endif
 		type->InitializeDefaults();
 		type->Virtuals = Virtuals;
 	}
@@ -635,7 +625,6 @@ PClass *PClass::CreateDerivedClass(FName name, unsigned int size)
 	return type;
 }
 
-#if 0
 //==========================================================================
 //
 // PClass :: AddField
@@ -674,7 +663,6 @@ PField *PClass::AddField(FName name, PType *type, uint32_t flags)
 	if (field != nullptr) Fields.Push(field);
 	return field;
 }
-#endif
 
 //==========================================================================
 //
@@ -772,13 +760,13 @@ int PClass::FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction
 	}
 	return -1;
 }
+#endif
 
 PSymbol *PClass::FindSymbol(FName symname, bool searchparents) const
 {
 	if (VMType == nullptr) return nullptr;
 	return VMType->Symbols.FindSymbol(symname, searchparents);
 }
-#endif
 
 //==========================================================================
 //
@@ -814,7 +802,6 @@ void PClass::BuildFlatPointers ()
 
 		TArray<size_t> ScriptPointers;
 
-#if 0
 		// Collect all pointers in scripted fields. These are not part of the Pointers list.
 		for (auto field : Fields)
 		{
@@ -823,7 +810,6 @@ void PClass::BuildFlatPointers ()
 				field->Type->SetPointer(Defaults, unsigned(field->Offset), &ScriptPointers);
 			}
 		}
-#endif
 
 		if (Pointers == nullptr && ScriptPointers.Size() == 0)
 		{ // No new pointers: Just use the same FlatPointers as the parent.
@@ -891,7 +877,6 @@ void PClass::BuildArrayPointers()
 		TArray<size_t> ScriptPointers;
 
 		// Collect all arrays to pointers in scripted fields.
-#if 0
 		for (auto field : Fields)
 		{
 			if (!(field->Flags & VARF_Native))
@@ -899,7 +884,6 @@ void PClass::BuildArrayPointers()
 				field->Type->SetPointerArray(Defaults, unsigned(field->Offset), &ScriptPointers);
 			}
 		}
-#endif
 
 		if (ScriptPointers.Size() == 0)
 		{ // No new pointers: Just use the same ArrayPointers as the parent.
@@ -948,7 +932,6 @@ const PClass *PClass::NativeClass() const
 	return cls;
 }
 
-#if 0
 VMFunction *PClass::FindFunction(FName clsname, FName funcname)
 {
 	auto cls = PClass::FindClass(clsname);
@@ -968,6 +951,7 @@ void PClass::FindFunction(VMFunction **pptr, FName clsname, FName funcname)
 	FunctionPtrList.Push(pptr);
 }
 
+#if 0
 unsigned GetVirtualIndex(PClass *cls, const char *funcname)
 {
 	// Look up the virtual function index in the defining class because this may have gotten overloaded in subclasses with something different than a virtual override.
diff --git a/source/common/objects/dobjtype.h b/source/common/objects/dobjtype.h
index e09f7714e..7e3fcec84 100644
--- a/source/common/objects/dobjtype.h
+++ b/source/common/objects/dobjtype.h
@@ -17,14 +17,14 @@ class VMException : public DObject
 
 // An action function -------------------------------------------------------
 
-struct FState;
-struct StateCallData;
 class VMFrameStack;
 struct VMValue;
 struct VMReturn;
 class VMFunction;
 class PClassType;
 struct FNamespaceManager;
+class PSymbol;
+class PField;
 
 enum
 {
@@ -44,9 +44,9 @@ public:
 	void InitializeDefaults();
 #if 0
 	int FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc);
+#endif
 	PSymbol *FindSymbol(FName symname, bool searchparents) const;
 	PField *AddField(FName name, PType *type, uint32_t flags);
-#endif
 
 	static void StaticInit();
 	static void StaticShutdown();
@@ -69,9 +69,7 @@ public:
 	TArray<VMFunction*>	 Virtuals;	// virtual function table
 	TArray<FTypeAndOffset> MetaInits;
 	TArray<FTypeAndOffset> SpecialInits;
-#if 0
 	TArray<PField *> Fields;
-#endif
 	PClassType			*VMType = nullptr;
 
 	void (*ConstructNative)(void *);
diff --git a/source/common/scripting/core/scopebarrier.cpp b/source/common/scripting/core/scopebarrier.cpp
new file mode 100644
index 000000000..f4d584c7b
--- /dev/null
+++ b/source/common/scripting/core/scopebarrier.cpp
@@ -0,0 +1,224 @@
+/*
+** scopebarrier.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright 2017 ZZYZX
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include "dobject.h"
+#include "scopebarrier.h"
+#include "types.h"
+#include "vmintern.h"
+
+
+// Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found.
+int FScopeBarrier::SideFromFlags(int flags)
+{
+	if (flags & VARF_UI)
+		return Side_UI;
+	if (flags & VARF_Play)
+		return Side_Play;
+	if (flags & VARF_VirtualScope)
+		return Side_Virtual;
+	if (flags & VARF_ClearScope)
+		return Side_Clear;
+	return Side_PlainData;
+}
+
+// same as above, but from object flags
+int FScopeBarrier::SideFromObjectFlags(EScopeFlags flags)
+{
+	if (flags & Scope_UI)
+		return Side_UI;
+	if (flags & Scope_Play)
+		return Side_Play;
+	return Side_PlainData;
+}
+
+//
+int FScopeBarrier::FlagsFromSide(int side)
+{
+	switch (side)
+	{
+	case Side_Play:
+		return VARF_Play;
+	case Side_UI:
+		return VARF_UI;
+	case Side_Virtual:
+		return VARF_VirtualScope;
+	case Side_Clear:
+		return VARF_ClearScope;
+	default:
+		return 0;
+	}
+}
+
+EScopeFlags FScopeBarrier::ObjectFlagsFromSide(int side)
+{
+	switch (side)
+	{
+	case Side_Play:
+		return Scope_Play;
+	case Side_UI:
+		return Scope_UI;
+	default:
+		return Scope_All;
+	}
+}
+
+// used for errors
+const char* FScopeBarrier::StringFromSide(int side)
+{
+	switch (side)
+	{
+	case Side_PlainData:
+		return "data";
+	case Side_UI:
+		return "ui";
+	case Side_Play:
+		return "play";
+	case Side_Virtual:
+		return "virtualscope"; // should not happen!
+	case Side_Clear:
+		return "clearscope"; // should not happen!
+	default:
+		return "unknown";
+	}
+}
+
+// this modifies VARF_ flags and sets the side properly.
+int FScopeBarrier::ChangeSideInFlags(int flags, int side)
+{
+	flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope | VARF_ClearScope);
+	flags |= FlagsFromSide(side);
+	return flags;
+}
+
+// this modifies OF_ flags and sets the side properly.
+EScopeFlags FScopeBarrier::ChangeSideInObjectFlags(EScopeFlags flags, int side)
+{
+	int f = int(flags);
+	f &= ~(Scope_UI | Scope_Play);
+	f |= ObjectFlagsFromSide(side);
+	return (EScopeFlags)f;
+}
+
+FScopeBarrier::FScopeBarrier()
+{
+	sidefrom = -1;
+	sidelast = -1;
+	callable = true;
+	readable = true;
+	writable = true;
+}
+
+FScopeBarrier::FScopeBarrier(int flags1, int flags2, const char* name)
+{
+	sidefrom = -1;
+	sidelast = -1;
+	callable = true;
+	readable = true;
+	writable = true;
+
+	AddFlags(flags1, flags2, name);
+}
+
+// AddFlags modifies ALLOWED actions by flags1->flags2.
+// This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed.
+// This struct is used so that the logic is in a single place.
+void FScopeBarrier::AddFlags(int flags1, int flags2, const char* name)
+{
+	// note: if it's already non-readable, don't even try advancing
+	if (!readable)
+		return;
+
+	// we aren't interested in any other flags
+	//  - update: including VARF_VirtualScope. inside the function itself, we treat it as if it's PlainData.
+	flags1 &= VARF_UI | VARF_Play;
+	flags2 &= VARF_UI | VARF_Play | VARF_ReadOnly;
+
+	if (sidefrom < 0) sidefrom = SideFromFlags(flags1);
+	if (sidelast < 0) sidelast = sidefrom;
+
+	// flags1 = what's trying to access
+	// flags2 = what's being accessed
+
+	int sideto = SideFromFlags(flags2);
+
+	// plain data inherits whatever scope modifiers that context or field container has.
+	// i.e. play String bla; is play, and all non-specified methods/fields inside it are play as well.
+	if (sideto != Side_PlainData)
+		sidelast = sideto;
+	else sideto = sidelast;
+
+	if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable
+	{
+		readable = false;
+		if (name) readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom));
+	}
+
+	if (!readable)
+	{
+		writable = false;
+		callable = false;
+		if (name)
+		{
+			writeerror.Format("Can't write %s field %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom));
+			callerror.Format("Can't call %s function %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom));
+		}
+		return;
+	}
+
+	if (writable && (sidefrom != sideto)) // only matching types are writable (plain data implicitly takes context type by default, unless overridden)
+	{
+		writable = false;
+		if (name) writeerror.Format("Can't write %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom));
+	}
+
+	if (callable && (sidefrom != sideto) && !(flags2 & VARF_ReadOnly)) // readonly on methods is used for plain data stuff that can be called from ui/play context.
+	{
+		callable = false;
+		if (name) callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom));
+	}
+}
+
+// these are for vmexec.h
+void FScopeBarrier::ValidateNew(PClass* cls, int outerside)
+{
+	int innerside = FScopeBarrier::SideFromObjectFlags(cls->VMType->ScopeFlags);
+	if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context"
+		ThrowAbortException(X_OTHER, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside));
+}
+
+void FScopeBarrier::ValidateCall(PClass* selftype, VMFunction *calledfunc, int outerside)
+{
+	int innerside = FScopeBarrier::SideFromObjectFlags(selftype->VMType->ScopeFlags);
+	if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData))
+		ThrowAbortException(X_OTHER, "Cannot call %s function %s from %s context", FScopeBarrier::StringFromSide(innerside), calledfunc->PrintableName.GetChars(), FScopeBarrier::StringFromSide(outerside));
+}
\ No newline at end of file
diff --git a/source/common/scripting/core/scopebarrier.h b/source/common/scripting/core/scopebarrier.h
new file mode 100644
index 000000000..fb931d223
--- /dev/null
+++ b/source/common/scripting/core/scopebarrier.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include "zstring.h"
+
+enum EScopeFlags
+{
+	Scope_All = 0,
+	Scope_UI = 1,		// Marks a class that defaults to VARF_UI for its fields/methods
+	Scope_Play = 2,	// Marks a class that defaults to VARF_Play for its fields/methods
+};
+
+class PClass;
+class VMFunction;
+
+//
+// [ZZ] this really should be in codegen.h, but vmexec needs to access it
+struct FScopeBarrier
+{
+	bool callable;
+	bool readable;
+	bool writable;
+
+	// this is the error message
+	FString callerror;
+	FString readerror;
+	FString writeerror;
+
+	// this is used to make the error message.
+	enum Side
+	{
+		Side_PlainData = 0,
+		Side_UI = 1,
+		Side_Play = 2,
+		Side_Virtual = 3, // do NOT change the value
+		Side_Clear = 4
+	};
+	int sidefrom;
+	int sidelast;
+
+	// Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found.
+	static int SideFromFlags(int flags);
+
+	// same as above, but from object flags
+	static int SideFromObjectFlags(EScopeFlags flags);
+
+	//
+	static int FlagsFromSide(int side);
+	static EScopeFlags ObjectFlagsFromSide(int side);
+	
+	// used for errors
+	static const char* StringFromSide(int side);
+
+	// this modifies VARF_ flags and sets the side properly.
+	static int ChangeSideInFlags(int flags, int side);
+	// this modifies OF_ flags and sets the side properly.
+	static EScopeFlags ChangeSideInObjectFlags(EScopeFlags flags, int side);
+	FScopeBarrier();
+	FScopeBarrier(int flags1, int flags2, const char* name);
+
+	// AddFlags modifies ALLOWED actions by flags1->flags2.
+	// This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed.
+	// This struct is used so that the logic is in a single place.
+	void AddFlags(int flags1, int flags2, const char* name);
+
+	// this is called from vmexec.h
+	static void ValidateNew(PClass* cls, int scope);
+	static void ValidateCall(PClass* selftype, VMFunction *calledfunc, int outerside);
+
+};
+
diff --git a/source/common/scripting/core/symbols.cpp b/source/common/scripting/core/symbols.cpp
new file mode 100644
index 000000000..c6e38551f
--- /dev/null
+++ b/source/common/scripting/core/symbols.cpp
@@ -0,0 +1,605 @@
+/*
+** symbols.cpp
+** Implements the symbol types and symbol table
+**
+**---------------------------------------------------------------------------
+** Copyright 1998-2016 Randy Heit
+** Copyright 2006-2017 Christoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include <float.h>
+#include "dobject.h"
+#include "templates.h"
+#include "serializer.h"
+#include "types.h"
+#include "vm.h"
+#include "printf.h"
+
+// PUBLIC DATA DEFINITIONS -------------------------------------------------
+
+FNamespaceManager Namespaces;
+
+// Symbol tables ------------------------------------------------------------
+
+IMPLEMENT_CLASS(PSymbol, true, false);
+IMPLEMENT_CLASS(PSymbolConst, false, false);
+IMPLEMENT_CLASS(PSymbolConstNumeric, false, false);
+IMPLEMENT_CLASS(PSymbolConstString, false, false);
+IMPLEMENT_CLASS(PSymbolTreeNode, false, false)
+IMPLEMENT_CLASS(PSymbolType, false, false)
+IMPLEMENT_CLASS(PFunction, false, false)
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PSymbolConstString::PSymbolConstString(FName name, const FString &str)
+	: PSymbolConst(name, TypeString), Str(str) 
+{
+}
+
+//==========================================================================
+//
+// PFunction :: AddVariant
+//
+// Adds a new variant for this function. Does not check if a matching
+// variant already exists.
+//
+//==========================================================================
+
+unsigned PFunction::AddVariant(PPrototype *proto, TArray<uint32_t> &argflags, TArray<FName> &argnames, VMFunction *impl, int flags, int useflags)
+{
+	Variant variant;
+
+	// I do not think we really want to deal with overloading here...
+	assert(Variants.Size() == 0);
+
+	variant.Flags = flags;
+	variant.UseFlags = useflags;
+	variant.Proto = proto;
+	variant.ArgFlags = std::move(argflags);
+	variant.ArgNames = std::move(argnames);
+	variant.Implementation = impl;
+	if (impl != nullptr) impl->Proto = proto;
+
+	// SelfClass can differ from OwningClass, but this is variant-dependent.
+	// Unlike the owner there can be cases where different variants can have different SelfClasses.
+	// (Of course only if this ever gets enabled...)
+	if (flags & VARF_Method)
+	{
+		assert(proto->ArgumentTypes.Size() > 0);
+		auto selftypeptr = proto->ArgumentTypes[0]->toPointer();
+		assert(selftypeptr != nullptr);
+		variant.SelfClass = selftypeptr->PointedType->toContainer();
+		assert(variant.SelfClass != nullptr);
+	}
+	else
+	{
+		variant.SelfClass = nullptr;
+	}
+
+	return Variants.Push(variant);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+int PFunction::GetImplicitArgs()
+{
+	if (Variants[0].Flags & VARF_Action) return 3;
+	else if (Variants[0].Flags & VARF_Method) return 1;
+	return 0;
+}
+
+/* PField *****************************************************************/
+
+IMPLEMENT_CLASS(PField, false, false)
+
+//==========================================================================
+//
+// PField - Default Constructor
+//
+//==========================================================================
+
+PField::PField()
+: PSymbol(NAME_None), Offset(0), Type(nullptr), Flags(0)
+{
+}
+
+
+PField::PField(FName name, PType *type, uint32_t flags, size_t offset, int bitvalue)
+	: PSymbol(name), Offset(offset), Type(type), Flags(flags)
+{
+	if (bitvalue != 0)
+	{
+		BitValue = 0;
+		unsigned val = bitvalue;
+		while ((val >>= 1)) BitValue++;
+
+		if (type->isInt() && unsigned(BitValue) < 8u * type->Size)
+		{
+			// map to the single bytes in the actual variable. The internal bit instructions operate on 8 bit values.
+#ifndef __BIG_ENDIAN__
+			Offset += BitValue / 8;
+#else
+			Offset += type->Size - 1 - BitValue / 8;
+#endif
+			BitValue &= 7;
+			Type = TypeBool;
+		}
+		else
+		{
+			// Just abort. Bit fields should only be defined internally.
+			I_Error("Trying to create an invalid bit field element: %s", name.GetChars());
+		}
+	}
+	else BitValue = -1;
+}
+
+VersionInfo PField::GetVersion()
+{
+	VersionInfo Highest = { 0,0,0 };
+	if (!(Flags & VARF_Deprecated)) Highest = mVersion;
+	if (Type->mVersion > Highest) Highest = Type->mVersion;
+	return Highest;
+}
+
+/* PProperty *****************************************************************/
+
+IMPLEMENT_CLASS(PProperty, false, false)
+
+//==========================================================================
+//
+// PField - Default Constructor
+//
+//==========================================================================
+
+PProperty::PProperty()
+	: PSymbol(NAME_None)
+{
+}
+
+PProperty::PProperty(FName name, TArray<PField *> &fields)
+	: PSymbol(name)
+{
+	Variables = std::move(fields);
+}
+
+/* PProperty *****************************************************************/
+
+IMPLEMENT_CLASS(PPropFlag, false, false)
+
+//==========================================================================
+//
+// PField - Default Constructor
+//
+//==========================================================================
+
+PPropFlag::PPropFlag()
+	: PSymbol(NAME_None)
+{
+}
+
+PPropFlag::PPropFlag(FName name, PField * field, int bitValue, bool forDecorate)
+	: PSymbol(name)
+{
+	Offset = field;
+	bitval = bitValue;
+	decorateOnly = forDecorate;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PSymbolTable::PSymbolTable()
+: ParentSymbolTable(nullptr)
+{
+}
+
+PSymbolTable::PSymbolTable(PSymbolTable *parent)
+: ParentSymbolTable(parent)
+{
+}
+
+PSymbolTable::~PSymbolTable ()
+{
+	ReleaseSymbols();
+}
+
+//==========================================================================
+//
+// this must explicitly delete all content because the symbols have
+// been released from the GC.
+//
+//==========================================================================
+
+void PSymbolTable::ReleaseSymbols()
+{
+	auto it = GetIterator();
+	MapType::Pair *pair;
+	while (it.NextPair(pair))
+	{
+		delete pair->Value;
+	}
+	Symbols.Clear();
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void PSymbolTable::SetParentTable (PSymbolTable *parent)
+{
+	ParentSymbolTable = parent;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PSymbol *PSymbolTable::FindSymbol (FName symname, bool searchparents) const
+{
+	PSymbol * const *value = Symbols.CheckKey(symname);
+	if (value == nullptr && searchparents && ParentSymbolTable != nullptr)
+	{
+		return ParentSymbolTable->FindSymbol(symname, searchparents);
+	}
+	return value != nullptr ? *value : nullptr;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PSymbol *PSymbolTable::FindSymbolInTable(FName symname, PSymbolTable *&symtable)
+{
+	PSymbol * const *value = Symbols.CheckKey(symname);
+	if (value == nullptr)
+	{
+		if (ParentSymbolTable != nullptr)
+		{
+			return ParentSymbolTable->FindSymbolInTable(symname, symtable);
+		}
+		symtable = nullptr;
+		return nullptr;
+	}
+	symtable = this;
+	return *value;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PSymbol *PSymbolTable::AddSymbol (PSymbol *sym)
+{
+	// Symbols that already exist are not inserted.
+	if (Symbols.CheckKey(sym->SymbolName) != nullptr)
+	{
+		return nullptr;
+	}
+	Symbols.Insert(sym->SymbolName, sym);
+	sym->Release();	// no more GC, please!
+	return sym;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PField *PSymbolTable::AddField(FName name, PType *type, uint32_t flags, unsigned &Size, unsigned *Align)
+{
+	PField *field = Create<PField>(name, type, flags);
+
+	// The new field is added to the end of this struct, alignment permitting.
+	field->Offset = (Size + (type->Align - 1)) & ~(type->Align - 1);
+
+	// Enlarge this struct to enclose the new field.
+	Size = unsigned(field->Offset + type->Size);
+
+	// This struct's alignment is the same as the largest alignment of any of
+	// its fields.
+	if (Align != nullptr)
+	{
+		*Align = MAX(*Align, type->Align);
+	}
+
+	if (AddSymbol(field) == nullptr)
+	{ // name is already in use
+		field->Destroy();
+		return nullptr;
+	}
+	return field;
+}
+
+//==========================================================================
+//
+// PStruct :: AddField
+//
+// Appends a new native field to the struct. Returns either the new field
+// or nullptr if a symbol by that name already exists.
+//
+//==========================================================================
+
+PField *PSymbolTable::AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue)
+{
+	PField *field = Create<PField>(name, type, flags | VARF_Native | VARF_Transient, address, bitvalue);
+
+	if (AddSymbol(field) == nullptr)
+	{ // name is already in use
+		field->Destroy();
+		return nullptr;
+	}
+	return field;
+}
+
+//==========================================================================
+//
+// PClass :: WriteFields
+//
+//==========================================================================
+
+void PSymbolTable::WriteFields(FSerializer &ar, const void *addr, const void *def) const
+{
+	auto it = MapType::ConstIterator(Symbols);
+	MapType::ConstPair *pair;
+
+	while (it.NextPair(pair))
+	{
+		const PField *field = dyn_cast<PField>(pair->Value);
+		// Skip fields without or with native serialization
+		if (field && !(field->Flags & (VARF_Transient | VARF_Meta | VARF_Static)))
+		{
+			// todo: handle defaults in WriteValue
+			//auto defp = def == nullptr ? nullptr : (const uint8_t *)def + field->Offset;
+			field->Type->WriteValue(ar, field->SymbolName.GetChars(), (const uint8_t *)addr + field->Offset);
+		}
+	}
+}
+
+
+//==========================================================================
+//
+// PClass :: ReadFields
+//
+//==========================================================================
+
+bool PSymbolTable::ReadFields(FSerializer &ar, void *addr, const char *TypeName) const
+{
+	bool readsomething = false;
+	bool foundsomething = false;
+	const char *label;
+	while ((label = ar.GetKey()))
+	{
+		foundsomething = true;
+
+		const PSymbol *sym = FindSymbol(FName(label, true), false);
+		if (sym == nullptr)
+		{
+			DPrintf(DMSG_ERROR, "Cannot find field %s in %s\n",
+				label, TypeName);
+		}
+		else if (!sym->IsKindOf(RUNTIME_CLASS(PField)))
+		{
+			DPrintf(DMSG_ERROR, "Symbol %s in %s is not a field\n",
+				label, TypeName);
+		}
+		else if ((static_cast<const PField *>(sym)->Flags & (VARF_Transient | VARF_Meta)))
+		{
+			DPrintf(DMSG_ERROR, "Symbol %s in %s is not a serializable field\n",
+				label, TypeName);
+		}
+		else
+		{
+			readsomething |= static_cast<const PField *>(sym)->Type->ReadValue(ar, nullptr,
+				(uint8_t *)addr + static_cast<const PField *>(sym)->Offset);
+		}
+	}
+	return readsomething || !foundsomething;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void PSymbolTable::RemoveSymbol(PSymbol *sym)
+{
+	auto mysym = Symbols.CheckKey(sym->SymbolName);
+	if (mysym == nullptr || *mysym != sym) return;
+	Symbols.Remove(sym->SymbolName);
+	delete sym;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void PSymbolTable::ReplaceSymbol(PSymbol *newsym)
+{
+	// If a symbol with a matching name exists, take its place and return it.
+	PSymbol **symslot = Symbols.CheckKey(newsym->SymbolName);
+	if (symslot != nullptr)
+	{
+		PSymbol *oldsym = *symslot;
+		delete oldsym;
+		*symslot = newsym;
+	}
+	// Else, just insert normally and return nullptr since there was no
+	// symbol to replace.
+	newsym->Release();	// no more GC, please!
+	Symbols.Insert(newsym->SymbolName, newsym);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PNamespace::PNamespace(int filenum, PNamespace *parent)
+{
+	Parent = parent;
+	if (parent) Symbols.SetParentTable(&parent->Symbols);
+	FileNum = filenum;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+FNamespaceManager::FNamespaceManager()
+{
+	GlobalNamespace = nullptr;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PNamespace *FNamespaceManager::NewNamespace(int filenum)
+{
+	PNamespace *parent = nullptr;
+	// The parent will be the last namespace with this or a lower filenum.
+	// This ensures that DECORATE won't see the symbols of later files.
+	for (int i = AllNamespaces.Size() - 1; i >= 0; i--)
+	{
+		if (AllNamespaces[i]->FileNum <= filenum)
+		{
+			parent = AllNamespaces[i];
+			break;
+		}
+	}
+	auto newns = new PNamespace(filenum, parent);
+	AllNamespaces.Push(newns);
+	return newns;
+}
+
+//==========================================================================
+//
+// Deallocate the entire namespace manager.
+//
+//==========================================================================
+
+void FNamespaceManager::ReleaseSymbols()
+{
+	for (auto ns : AllNamespaces)
+	{
+		delete ns;
+	}
+	GlobalNamespace = nullptr;
+	AllNamespaces.Clear();
+}
+
+//==========================================================================
+//
+// removes all symbols from the symbol tables.
+// After running the compiler these are not needed anymore.
+// Only the namespaces themselves are kept because the type table references them.
+//
+//==========================================================================
+
+int FNamespaceManager::RemoveSymbols()
+{
+	int count = 0;
+	for (auto ns : AllNamespaces)
+	{
+		count += ns->Symbols.Symbols.CountUsed();
+		ns->Symbols.ReleaseSymbols();
+	}
+	return count;
+}
+
+//==========================================================================
+//
+// Clean out all compiler-only data from the symbol tables
+//
+//==========================================================================
+
+void RemoveUnusedSymbols()
+{
+	int count = Namespaces.RemoveSymbols();
+
+	// We do not need any non-field and non-function symbols in structs and classes anymore.
+	// struct/class fields and functions are still needed so that the game can access the script data,
+	// but all the rest serves no purpose anymore and can be entirely removed.
+	for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i)
+	{
+		for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr; ty = ty->HashNext)
+		{
+			if (ty->isContainer())
+			{
+				auto it = ty->Symbols.GetIterator();
+				PSymbolTable::MapType::Pair *pair;
+				while (it.NextPair(pair))
+				{
+					if (   !pair->Value->IsKindOf(RUNTIME_CLASS(PField))
+						&& !pair->Value->IsKindOf(RUNTIME_CLASS(PFunction))
+						&& !pair->Value->IsKindOf(RUNTIME_CLASS(PPropFlag)) )
+					{
+						ty->Symbols.RemoveSymbol(pair->Value);
+						count++;
+					}
+				}
+			}
+		}
+	}
+	DPrintf(DMSG_SPAMMY, "%d symbols removed after compilation\n", count);
+}
diff --git a/source/common/scripting/core/symbols.h b/source/common/scripting/core/symbols.h
new file mode 100644
index 000000000..2ae194e89
--- /dev/null
+++ b/source/common/scripting/core/symbols.h
@@ -0,0 +1,269 @@
+// Note: This must not be included by anything but dobject.h!
+#pragma once
+
+#include "sc_man.h"
+
+class VMFunction;
+class PType;
+class PPrototype;
+struct ZCC_TreeNode;
+class PContainerType;
+
+// Symbol information -------------------------------------------------------
+
+class PTypeBase
+{
+public:
+	// Allocate everything on the global memory arena because all subtypes of this 
+	// will live until the end of the game.
+	void *operator new(size_t size)
+	{
+		return ClassDataAllocator.Alloc(size);
+	}
+
+	void operator delete(void *)
+	{}
+};
+
+class PSymbol : public DObject
+{
+	DECLARE_ABSTRACT_CLASS(PSymbol, DObject);
+public:
+	FName SymbolName;
+	VersionInfo mVersion = { 0,0,0 };
+
+protected:
+	PSymbol(FName name) { SymbolName = name; }
+};
+
+// A VM function ------------------------------------------------------------
+
+// A symbol for a type ------------------------------------------------------
+
+class PSymbolType : public PSymbol
+{
+	DECLARE_CLASS(PSymbolType, PSymbol);
+public:
+	PType *Type;
+
+	PSymbolType(FName name, class PType *ty) : PSymbol(name), Type(ty) {}
+	PSymbolType() : PSymbol(NAME_None) {}
+};
+
+// A symbol for a compiler tree node ----------------------------------------
+
+class PSymbolTreeNode : public PSymbol
+{
+	DECLARE_CLASS(PSymbolTreeNode, PSymbol);
+public:
+	struct ZCC_TreeNode *Node;
+
+	PSymbolTreeNode(FName name, struct ZCC_TreeNode *node) : PSymbol(name), Node(node) {}
+	PSymbolTreeNode() : PSymbol(NAME_None) {}
+};
+
+// Struct/class fields ------------------------------------------------------
+
+// A PField describes a symbol that takes up physical space in the struct.
+class PField : public PSymbol
+{
+	DECLARE_CLASS(PField, PSymbol);
+	HAS_OBJECT_POINTERS
+public:
+	PField(FName name, PType *type, uint32_t flags = 0, size_t offset = 0, int bitvalue = 0);
+	VersionInfo GetVersion();
+
+	size_t Offset;
+	PType *Type;
+	uint32_t Flags;
+	int BitValue;
+	FString DeprecationMessage;
+protected:
+	PField();
+};
+
+// Properties ------------------------------------------------------
+
+// For setting properties in class defaults.
+class PProperty : public PSymbol
+{
+	DECLARE_CLASS(PProperty, PSymbol);
+public:
+	PProperty(FName name, TArray<PField *> &variables);
+
+	TArray<PField *> Variables;
+
+protected:
+	PProperty();
+};
+
+class PPropFlag : public PSymbol
+{
+	DECLARE_CLASS(PPropFlag, PSymbol);
+public:
+	PPropFlag(FName name, PField *offset, int bitval, bool decorateonly);
+
+	PField *Offset;
+	int bitval;
+	bool decorateOnly;
+
+protected:
+	PPropFlag();
+};
+
+// A constant value ---------------------------------------------------------
+
+class PSymbolConst : public PSymbol
+{
+	DECLARE_CLASS(PSymbolConst, PSymbol);
+public:
+	PType *ValueType;
+
+	PSymbolConst(FName name, PType *type=NULL) : PSymbol(name), ValueType(type) {}
+	PSymbolConst() : PSymbol(NAME_None), ValueType(NULL) {}
+};
+
+// A constant numeric value -------------------------------------------------
+
+class PSymbolConstNumeric : public PSymbolConst
+{
+	DECLARE_CLASS(PSymbolConstNumeric, PSymbolConst);
+public:
+	union
+	{
+		int Value;
+		double Float;
+		void *Pad;
+	};
+
+	PSymbolConstNumeric(FName name, PType *type=NULL) : PSymbolConst(name, type) {}
+	PSymbolConstNumeric(FName name, PType *type, int val) : PSymbolConst(name, type), Value(val) {}
+	PSymbolConstNumeric(FName name, PType *type, unsigned int val) : PSymbolConst(name, type), Value((int)val) {}
+	PSymbolConstNumeric(FName name, PType *type, double val) : PSymbolConst(name, type), Float(val) {}
+	PSymbolConstNumeric() {}
+};
+
+// A constant string value --------------------------------------------------
+
+class PSymbolConstString : public PSymbolConst
+{
+	DECLARE_CLASS(PSymbolConstString, PSymbolConst);
+public:
+	FString Str;
+
+	PSymbolConstString(FName name, const FString &str);
+	PSymbolConstString() {}
+};
+
+
+// A function for the VM --------------------------------------------------
+
+// TBD: Should we really support overloading?
+class PFunction : public PSymbol
+{
+	DECLARE_CLASS(PFunction, PSymbol);
+public:
+	struct Variant
+	{
+		PPrototype *Proto;
+		VMFunction *Implementation;
+		TArray<uint32_t> ArgFlags;		// Should be the same length as Proto->ArgumentTypes
+		TArray<FName> ArgNames;		// we need the names to access them later when the function gets compiled.
+		uint32_t Flags;
+		int UseFlags;
+		PContainerType *SelfClass;
+		FString DeprecationMessage;
+	};
+	TArray<Variant> Variants;
+	PContainerType *OwningClass = nullptr;
+
+	unsigned AddVariant(PPrototype *proto, TArray<uint32_t> &argflags, TArray<FName> &argnames, VMFunction *impl, int flags, int useflags);
+	int GetImplicitArgs();
+
+	PFunction(PContainerType *owner = nullptr, FName name = NAME_None) : PSymbol(name), OwningClass(owner) {}
+};
+
+// A symbol table -----------------------------------------------------------
+
+struct PSymbolTable
+{
+	PSymbolTable();
+	PSymbolTable(PSymbolTable *parent);
+	~PSymbolTable();
+
+	// Sets the table to use for searches if this one doesn't contain the
+	// requested symbol.
+	void SetParentTable (PSymbolTable *parent);
+	PSymbolTable *GetParentTable() const
+	{
+		return ParentSymbolTable;
+	}
+
+	// Finds a symbol in the table, optionally searching parent tables
+	// as well.
+	PSymbol *FindSymbol (FName symname, bool searchparents) const;
+
+	// Like FindSymbol with searchparents set true, but also returns the
+	// specific symbol table the symbol was found in.
+	PSymbol *FindSymbolInTable(FName symname, PSymbolTable *&symtable);
+
+
+	// Places the symbol in the table and returns a pointer to it or NULL if
+	// a symbol with the same name is already in the table. This symbol is
+	// not copied and will be freed when the symbol table is destroyed.
+	PSymbol *AddSymbol (PSymbol *sym);
+	PField *AddField(FName name, PType *type, uint32_t flags, unsigned &Size, unsigned *Align = nullptr);
+	PField *AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue);
+	bool ReadFields(FSerializer &ar, void *addr, const char *TypeName) const;
+	void WriteFields(FSerializer &ar, const void *addr, const void *def = nullptr) const;
+
+	// Similar to AddSymbol but always succeeds. Returns the symbol that used
+	// to be in the table with this name, if any.
+	void ReplaceSymbol(PSymbol *sym);
+
+	void RemoveSymbol(PSymbol *sym);
+
+	// Frees all symbols from this table.
+	void ReleaseSymbols();
+
+	typedef TMap<FName, PSymbol *> MapType;
+
+	MapType::Iterator GetIterator()
+	{
+		return MapType::Iterator(Symbols);
+	}
+
+private:
+
+	PSymbolTable *ParentSymbolTable;
+	MapType Symbols;
+
+	friend class DObject;
+	friend struct FNamespaceManager;
+};
+
+// Namespaces --------------------------------------------------
+
+class PNamespace : public PTypeBase
+{
+public:
+	PSymbolTable Symbols;
+	PNamespace *Parent;
+	int FileNum;	// This is for blocking DECORATE access to later files.
+
+	PNamespace(int filenum, PNamespace *parent);
+};
+
+struct FNamespaceManager
+{
+	PNamespace *GlobalNamespace;
+	TArray<PNamespace *> AllNamespaces;
+
+	FNamespaceManager();
+	PNamespace *NewNamespace(int filenum);
+	void ReleaseSymbols();
+	int RemoveSymbols();
+};
+
+extern FNamespaceManager Namespaces;
+void RemoveUnusedSymbols();
diff --git a/source/common/scripting/core/types.cpp b/source/common/scripting/core/types.cpp
new file mode 100644
index 000000000..302127989
--- /dev/null
+++ b/source/common/scripting/core/types.cpp
@@ -0,0 +1,2480 @@
+/*
+** types.cpp
+** Implements the VM type hierarchy
+**
+**---------------------------------------------------------------------------
+** Copyright 2008-2016 Randy Heit
+** Copyright 2016-2017 Cheistoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include "vmintern.h"
+#include "s_soundinternal.h"
+//#include "dthinker.h"
+#include "types.h"
+#include "printf.h"
+#include "textureid.h"
+
+
+FTypeTable TypeTable;
+
+PErrorType *TypeError;
+PErrorType *TypeAuto;
+PVoidType *TypeVoid;
+PInt *TypeSInt8,  *TypeUInt8;
+PInt *TypeSInt16, *TypeUInt16;
+PInt *TypeSInt32, *TypeUInt32;
+PBool *TypeBool;
+PFloat *TypeFloat32, *TypeFloat64;
+PString *TypeString;
+PName *TypeName;
+PSound *TypeSound;
+PColor *TypeColor;
+PTextureID *TypeTextureID;
+PPointer *TypeFont;
+PStateLabel *TypeStateLabel;
+PStruct *TypeVector2;
+PStruct *TypeVector3;
+PStruct *TypeColorStruct;
+PStruct *TypeStringStruct;
+PPointer *TypeNullPtr;
+PPointer *TypeVoidPtr;
+
+
+// CODE --------------------------------------------------------------------
+
+void DumpTypeTable()
+{
+	int used = 0;
+	int min = INT_MAX;
+	int max = 0;
+	int all = 0;
+	int lens[10] = {0};
+	for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i)
+	{
+		int len = 0;
+		Printf("%4zu:", i);
+		for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr; ty = ty->HashNext)
+		{
+			Printf(" -> %s", ty->DescriptiveName());
+			len++;
+			all++;
+		}
+		if (len != 0)
+		{
+			used++;
+			if (len < min)
+				min = len;
+			if (len > max)
+				max = len;
+		}
+		if (len < (int)countof(lens))
+		{
+			lens[len]++;
+		}
+		Printf("\n");
+	}
+	Printf("Used buckets: %d/%lu (%.2f%%) for %d entries\n", used, countof(TypeTable.TypeHash), double(used)/countof(TypeTable.TypeHash)*100, all);
+	Printf("Min bucket size: %d\n", min);
+	Printf("Max bucket size: %d\n", max);
+	Printf("Avg bucket size: %.2f\n", double(all) / used);
+	int j,k;
+	for (k = countof(lens)-1; k > 0; --k)
+		if (lens[k])
+			break;
+	for (j = 0; j <= k; ++j)
+		Printf("Buckets of len %d: %d (%.2f%%)\n", j, lens[j], j!=0?double(lens[j])/used*100:-1.0);
+}
+
+/* PType ******************************************************************/
+
+//==========================================================================
+//
+// PType Parameterized Constructor
+//
+//==========================================================================
+
+PType::PType(unsigned int size, unsigned int align)
+: Size(size), Align(align), HashNext(nullptr)
+{
+	mDescriptiveName = "Type";
+	loadOp = OP_NOP;
+	storeOp = OP_NOP;
+	moveOp = OP_NOP;
+	RegType = REGT_NIL;
+	RegCount = 1;
+}
+
+//==========================================================================
+//
+// PType Destructor
+//
+//==========================================================================
+
+PType::~PType()
+{
+}
+
+//==========================================================================
+//
+// PType :: WriteValue
+//
+//==========================================================================
+
+void PType::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	assert(0 && "Cannot write value for this type");
+}
+
+//==========================================================================
+//
+// PType :: ReadValue
+//
+//==========================================================================
+
+bool PType::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	assert(0 && "Cannot read value for this type");
+	return false;
+}
+
+//==========================================================================
+//
+// PType :: SetDefaultValue
+//
+//==========================================================================
+
+void PType::SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *stroffs)
+{
+}
+
+//==========================================================================
+//
+// PType :: SetDefaultValue
+//
+//==========================================================================
+
+void PType::SetPointer(void *base, unsigned offset, TArray<size_t> *stroffs)
+{
+}
+
+void PType::SetPointerArray(void *base, unsigned offset, TArray<size_t> *stroffs)
+{
+}
+
+//==========================================================================
+//
+// PType :: InitializeValue
+//
+//==========================================================================
+
+void PType::InitializeValue(void *addr, const void *def) const
+{
+}
+
+//==========================================================================
+//
+// PType :: DestroyValue
+//
+//==========================================================================
+
+void PType::DestroyValue(void *addr) const
+{
+}
+
+//==========================================================================
+//
+// PType :: SetValue
+//
+//==========================================================================
+
+void PType::SetValue(void *addr, int val)
+{
+	assert(0 && "Cannot set int value for this type");
+}
+
+void PType::SetValue(void *addr, double val)
+{
+	assert(0 && "Cannot set float value for this type");
+}
+
+//==========================================================================
+//
+// PType :: GetValue
+//
+//==========================================================================
+
+int PType::GetValueInt(void *addr) const
+{
+	assert(0 && "Cannot get value for this type");
+	return 0;
+}
+
+double PType::GetValueFloat(void *addr) const
+{
+	assert(0 && "Cannot get value for this type");
+	return 0;
+}
+
+//==========================================================================
+//
+// PType :: IsMatch
+//
+//==========================================================================
+
+bool PType::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	return false;
+}
+
+//==========================================================================
+//
+// PType :: GetTypeIDs
+//
+//==========================================================================
+
+void PType::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = 0;
+	id2 = 0;
+}
+
+//==========================================================================
+//
+// PType :: GetTypeIDs
+//
+//==========================================================================
+
+const char *PType::DescriptiveName() const
+{
+	return mDescriptiveName.GetChars();
+}
+
+//==========================================================================
+//
+// PType :: StaticInit												STATIC
+//
+//==========================================================================
+
+void PType::StaticInit()
+{
+	// Create types and add them type the type table.
+	TypeTable.AddType(TypeError = new PErrorType, NAME_None);
+	TypeTable.AddType(TypeAuto = new PErrorType(2), NAME_None);
+	TypeTable.AddType(TypeVoid = new PVoidType, NAME_Void);
+	TypeTable.AddType(TypeSInt8 = new PInt(1, false), NAME_Int);
+	TypeTable.AddType(TypeUInt8 = new PInt(1, true), NAME_Int);
+	TypeTable.AddType(TypeSInt16 = new PInt(2, false), NAME_Int);
+	TypeTable.AddType(TypeUInt16 = new PInt(2, true), NAME_Int);
+	TypeTable.AddType(TypeSInt32 = new PInt(4, false), NAME_Int);
+	TypeTable.AddType(TypeUInt32 = new PInt(4, true), NAME_Int);
+	TypeTable.AddType(TypeBool = new PBool, NAME_Bool);
+	TypeTable.AddType(TypeFloat32 = new PFloat(4), NAME_Float);
+	TypeTable.AddType(TypeFloat64 = new PFloat(8), NAME_Float);
+	TypeTable.AddType(TypeString = new PString, NAME_String);
+	TypeTable.AddType(TypeName = new PName, NAME_Name);
+	TypeTable.AddType(TypeSound = new PSound, NAME_Sound);
+	TypeTable.AddType(TypeColor = new PColor, NAME_Color);
+	TypeTable.AddType(TypeStateLabel = new PStateLabel, NAME_Label);
+	TypeTable.AddType(TypeNullPtr = new PPointer, NAME_Pointer);
+	TypeTable.AddType(TypeTextureID = new PTextureID, NAME_TextureID);
+
+	TypeVoidPtr = NewPointer(TypeVoid, false);
+	TypeColorStruct = NewStruct("@ColorStruct", nullptr);	//This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value.
+	TypeStringStruct = NewStruct("Stringstruct", nullptr, true);
+	TypeFont = NewPointer(NewStruct("Font", nullptr, true));
+#ifdef __BIG_ENDIAN__
+	TypeColorStruct->AddField(NAME_a, TypeUInt8);
+	TypeColorStruct->AddField(NAME_r, TypeUInt8);
+	TypeColorStruct->AddField(NAME_g, TypeUInt8);
+	TypeColorStruct->AddField(NAME_b, TypeUInt8);
+#else
+	TypeColorStruct->AddField(NAME_b, TypeUInt8);
+	TypeColorStruct->AddField(NAME_g, TypeUInt8);
+	TypeColorStruct->AddField(NAME_r, TypeUInt8);
+	TypeColorStruct->AddField(NAME_a, TypeUInt8);
+#endif
+
+	TypeVector2 = new PStruct(NAME_Vector2, nullptr);
+	TypeVector2->AddField(NAME_X, TypeFloat64);
+	TypeVector2->AddField(NAME_Y, TypeFloat64);
+	TypeTable.AddType(TypeVector2, NAME_Struct);
+	TypeVector2->loadOp = OP_LV2;
+	TypeVector2->storeOp = OP_SV2;
+	TypeVector2->moveOp = OP_MOVEV2;
+	TypeVector2->RegType = REGT_FLOAT;
+	TypeVector2->RegCount = 2;
+
+	TypeVector3 = new PStruct(NAME_Vector3, nullptr);
+	TypeVector3->AddField(NAME_X, TypeFloat64);
+	TypeVector3->AddField(NAME_Y, TypeFloat64);
+	TypeVector3->AddField(NAME_Z, TypeFloat64);
+	// allow accessing xy as a vector2. This is not supposed to be serialized so it's marked transient
+	TypeVector3->Symbols.AddSymbol(Create<PField>(NAME_XY, TypeVector2, VARF_Transient, 0));
+	TypeTable.AddType(TypeVector3, NAME_Struct);
+	TypeVector3->loadOp = OP_LV3;
+	TypeVector3->storeOp = OP_SV3;
+	TypeVector3->moveOp = OP_MOVEV3;
+	TypeVector3->RegType = REGT_FLOAT;
+	TypeVector3->RegCount = 3;
+
+
+
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_sByte, TypeSInt8));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Byte, TypeUInt8));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Short, TypeSInt16));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_uShort, TypeUInt16));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Int, TypeSInt32));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_uInt, TypeUInt32));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Bool, TypeBool));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Float, TypeFloat64));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Double, TypeFloat64));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Float32, TypeFloat32));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Float64, TypeFloat64));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_String, TypeString));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Name, TypeName));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Sound, TypeSound));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Color, TypeColor));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Vector2, TypeVector2));
+	Namespaces.GlobalNamespace->Symbols.AddSymbol(Create<PSymbolType>(NAME_Vector3, TypeVector3));
+}
+
+
+/* PBasicType *************************************************************/
+
+//==========================================================================
+//
+// PBasicType Parameterized Constructor
+//
+//==========================================================================
+
+PBasicType::PBasicType(unsigned int size, unsigned int align)
+: PType(size, align)
+{
+	mDescriptiveName = "BasicType";
+	Flags |= TYPE_Scalar;
+}
+
+/* PCompoundType **********************************************************/
+
+//==========================================================================
+//
+// PBasicType Parameterized Constructor
+//
+//==========================================================================
+
+PCompoundType::PCompoundType(unsigned int size, unsigned int align)
+	: PType(size, align)
+{
+	mDescriptiveName = "CompoundType";
+}
+
+/* PContainerType *************************************************************/
+
+//==========================================================================
+//
+// PContainerType :: IsMatch
+//
+//==========================================================================
+
+bool PContainerType::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	const PTypeBase *outer = (const PTypeBase *)id1;
+	FName name = (ENamedName)(intptr_t)id2;
+	
+	return Outer == outer && TypeName == name;
+}
+
+//==========================================================================
+//
+// PContainerType :: GetTypeIDs
+//
+//==========================================================================
+
+void PContainerType::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)Outer;
+	id2 = TypeName.GetIndex();
+}
+
+/* PInt *******************************************************************/
+
+//==========================================================================
+//
+// PInt Parameterized Constructor
+//
+//==========================================================================
+
+PInt::PInt(unsigned int size, bool unsign, bool compatible)
+: PBasicType(size, size), Unsigned(unsign), IntCompatible(compatible)
+{
+	mDescriptiveName.Format("%cInt%d", unsign? 'U':'S', size);
+	Flags |= TYPE_Int;
+
+	MemberOnly = (size < 4);
+	if (!unsign)
+	{
+		int maxval = (1u << ((8 * size) - 1)) - 1; // compute as unsigned to prevent overflow before -1
+		int minval = -maxval - 1;
+		Symbols.AddSymbol(Create<PSymbolConstNumeric>(NAME_Min, this, minval));
+		Symbols.AddSymbol(Create<PSymbolConstNumeric>(NAME_Max, this, maxval));
+	}
+	else
+	{
+		Symbols.AddSymbol(Create<PSymbolConstNumeric>(NAME_Min, this, 0u));
+		Symbols.AddSymbol(Create<PSymbolConstNumeric>(NAME_Max, this, (1u << ((8 * size) - 1))));
+	}
+	SetOps();
+}
+
+void PInt::SetOps()
+{
+	moveOp = OP_MOVE;
+	RegType = REGT_INT;
+	if (Size == 4)
+	{
+		storeOp = OP_SW;
+		loadOp = OP_LW;
+	}
+	else if (Size == 1)
+	{
+		storeOp = OP_SB;
+		loadOp = Unsigned ? OP_LBU : OP_LB;
+	}
+	else if (Size == 2)
+	{
+		storeOp = OP_SH;
+		loadOp = Unsigned ? OP_LHU : OP_LH;
+	}
+	else
+	{
+		assert(0 && "Unhandled integer size");
+		storeOp = OP_NOP;
+	}
+}
+
+//==========================================================================
+//
+// PInt :: WriteValue
+//
+//==========================================================================
+
+void PInt::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	if (Size == 8 && Unsigned)
+	{
+		// this is a special case that cannot be represented by an int64_t.
+		uint64_t val = *(uint64_t*)addr;
+		ar(key, val);
+	}
+	else
+	{
+		int64_t val;
+		switch (Size)
+		{
+		case 1:
+			val = Unsigned ? *(uint8_t*)addr : *(int8_t*)addr;
+			break;
+
+		case 2:
+			val = Unsigned ? *(uint16_t*)addr : *(int16_t*)addr;
+			break;
+
+		case 4:
+			val = Unsigned ? *(uint32_t*)addr : *(int32_t*)addr;
+			break;
+
+		case 8:
+			val = *(int64_t*)addr;
+			break;
+
+		default:
+			return;	// something invalid
+		}
+		ar(key, val);
+	}
+}
+
+//==========================================================================
+//
+// PInt :: ReadValue
+//
+//==========================================================================
+
+bool PInt::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	NumericValue val;
+
+	ar(key, val);
+	if (val.type == NumericValue::NM_invalid) return false;	// not found or usable
+	if (val.type == NumericValue::NM_float) val.signedval = (int64_t)val.floatval;
+
+	// No need to check the unsigned state here. Downcasting to smaller types will yield the same result for both.
+	switch (Size)
+	{
+	case 1:
+		*(uint8_t*)addr = (uint8_t)val.signedval;
+		break;
+
+	case 2:
+		*(uint16_t*)addr = (uint16_t)val.signedval;
+		break;
+
+	case 4:
+		*(uint32_t*)addr = (uint32_t)val.signedval;
+		break;
+
+	case 8:
+		*(uint64_t*)addr = (uint64_t)val.signedval;
+		break;
+
+	default:
+		return false;	// something invalid
+	}
+
+	return true;
+}
+
+//==========================================================================
+//
+// PInt :: SetValue
+//
+//==========================================================================
+
+void PInt::SetValue(void *addr, int val)
+{
+	assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address");
+	if (Size == 4)
+	{
+		*(int *)addr = val;
+	}
+	else if (Size == 1)
+	{
+		*(uint8_t *)addr = val;
+	}
+	else if (Size == 2)
+	{
+		*(uint16_t *)addr = val;
+	}
+	else if (Size == 8)
+	{
+		*(uint64_t *)addr = val;
+	}
+	else
+	{
+		assert(0 && "Unhandled integer size");
+	}
+}
+
+void PInt::SetValue(void *addr, double val)
+{
+	SetValue(addr, (int)val);
+}
+
+//==========================================================================
+//
+// PInt :: GetValueInt
+//
+//==========================================================================
+
+int PInt::GetValueInt(void *addr) const
+{
+	assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address");
+	if (Size == 4)
+	{
+		return *(int *)addr;
+	}
+	else if (Size == 1)
+	{
+		return Unsigned ? *(uint8_t *)addr : *(int8_t *)addr;
+	}
+	else if (Size == 2)
+	{
+		return Unsigned ? *(uint16_t *)addr : *(int16_t *)addr;
+	}
+	else if (Size == 8)
+	{ // truncated output
+		return (int)*(uint64_t *)addr;
+	}
+	else
+	{
+		assert(0 && "Unhandled integer size");
+		return 0;
+	}
+}
+
+//==========================================================================
+//
+// PInt :: GetValueFloat
+//
+//==========================================================================
+
+double PInt::GetValueFloat(void *addr) const
+{
+	return GetValueInt(addr);
+}
+
+//==========================================================================
+//
+// PInt :: GetStoreOp
+//
+//==========================================================================
+
+/* PBool ******************************************************************/
+
+//==========================================================================
+//
+// PInt :: SetValue
+//
+//==========================================================================
+
+void PBool::SetValue(void *addr, int val)
+{
+	*(bool*)addr = !!val;
+}
+
+void PBool::SetValue(void *addr, double val)
+{
+	*(bool*)addr = val != 0.;
+}
+
+int PBool::GetValueInt(void *addr) const
+{
+	return *(bool *)addr;
+}
+
+double PBool::GetValueFloat(void *addr) const
+{
+	return *(bool *)addr;
+}
+
+//==========================================================================
+//
+// PBool Default Constructor
+//
+//==========================================================================
+
+PBool::PBool()
+: PInt(sizeof(bool), true)
+{
+	mDescriptiveName = "Bool";
+	MemberOnly = false;
+	Flags |= TYPE_IntNotInt;
+}
+
+/* PFloat *****************************************************************/
+
+//==========================================================================
+//
+// PFloat Parameterized Constructor
+//
+//==========================================================================
+
+PFloat::PFloat(unsigned int size)
+: PBasicType(size, size)
+{
+	mDescriptiveName.Format("Float%d", size);
+	Flags |= TYPE_Float;
+	if (size == 8)
+	{
+		if (sizeof(void*) == 4)
+		{
+			// Some ABIs for 32-bit platforms define alignment of double type as 4 bytes
+			// Intel POSIX (System V ABI) and PowerPC Macs are examples of those
+			struct AlignmentCheck { uint8_t i; double d; };
+			Align = static_cast<unsigned int>(offsetof(AlignmentCheck, d));
+		}
+
+		SetDoubleSymbols();
+	}
+	else
+	{
+		assert(size == 4);
+		MemberOnly = true;
+		SetSingleSymbols();
+	}
+	SetOps();
+}
+
+//==========================================================================
+//
+// PFloat :: SetDoubleSymbols
+//
+// Setup constant values for 64-bit floats.
+//
+//==========================================================================
+
+void PFloat::SetDoubleSymbols()
+{
+	static const SymbolInitF symf[] =
+	{
+		{ NAME_Min_Normal,		DBL_MIN },
+		{ NAME_Max,				DBL_MAX },
+		{ NAME_Epsilon,			DBL_EPSILON },
+		{ NAME_NaN,				std::numeric_limits<double>::quiet_NaN() },
+		{ NAME_Infinity,		std::numeric_limits<double>::infinity() },
+		{ NAME_Min_Denormal,	std::numeric_limits<double>::denorm_min() }
+	};
+	static const SymbolInitI symi[] =
+	{
+		{ NAME_Dig,				DBL_DIG },
+		{ NAME_Min_Exp,			DBL_MIN_EXP },
+		{ NAME_Max_Exp,			DBL_MAX_EXP },
+		{ NAME_Mant_Dig,		DBL_MANT_DIG },
+		{ NAME_Min_10_Exp,		DBL_MIN_10_EXP },
+		{ NAME_Max_10_Exp,		DBL_MAX_10_EXP }
+	};
+	SetSymbols(symf, countof(symf));
+	SetSymbols(symi, countof(symi));
+}
+
+//==========================================================================
+//
+// PFloat :: SetSingleSymbols
+//
+// Setup constant values for 32-bit floats.
+//
+//==========================================================================
+
+void PFloat::SetSingleSymbols()
+{
+	static const SymbolInitF symf[] =
+	{
+		{ NAME_Min_Normal,		FLT_MIN },
+		{ NAME_Max,				FLT_MAX },
+		{ NAME_Epsilon,			FLT_EPSILON },
+		{ NAME_NaN,				std::numeric_limits<float>::quiet_NaN() },
+		{ NAME_Infinity,		std::numeric_limits<float>::infinity() },
+		{ NAME_Min_Denormal,	std::numeric_limits<float>::denorm_min() }
+	};
+	static const SymbolInitI symi[] =
+	{
+		{ NAME_Dig,				FLT_DIG },
+		{ NAME_Min_Exp,			FLT_MIN_EXP },
+		{ NAME_Max_Exp,			FLT_MAX_EXP },
+		{ NAME_Mant_Dig,		FLT_MANT_DIG },
+		{ NAME_Min_10_Exp,		FLT_MIN_10_EXP },
+		{ NAME_Max_10_Exp,		FLT_MAX_10_EXP }
+	};
+	SetSymbols(symf, countof(symf));
+	SetSymbols(symi, countof(symi));
+}
+
+//==========================================================================
+//
+// PFloat :: SetSymbols
+//
+//==========================================================================
+
+void PFloat::SetSymbols(const PFloat::SymbolInitF *sym, size_t count)
+{
+	for (size_t i = 0; i < count; ++i)
+	{
+		Symbols.AddSymbol(Create<PSymbolConstNumeric>(sym[i].Name, this, sym[i].Value));
+	}
+}
+
+void PFloat::SetSymbols(const PFloat::SymbolInitI *sym, size_t count)
+{
+	for (size_t i = 0; i < count; ++i)
+	{
+		Symbols.AddSymbol(Create<PSymbolConstNumeric>(sym[i].Name, this, sym[i].Value));
+	}
+}
+
+//==========================================================================
+//
+// PFloat :: WriteValue
+//
+//==========================================================================
+
+void PFloat::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	if (Size == 8)
+	{
+		ar(key, *(double*)addr);
+	}
+	else
+	{
+		ar(key, *(float*)addr);
+	}
+}
+
+//==========================================================================
+//
+// PFloat :: ReadValue
+//
+//==========================================================================
+
+bool PFloat::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	NumericValue val;
+
+	ar(key, val);
+	if (val.type == NumericValue::NM_invalid) return false;	// not found or usable
+	else if (val.type == NumericValue::NM_signed) val.floatval = (double)val.signedval;
+	else if (val.type == NumericValue::NM_unsigned) val.floatval = (double)val.unsignedval;
+
+	if (Size == 8)
+	{
+		*(double*)addr = val.floatval;
+	}
+	else
+	{
+		*(float*)addr = (float)val.floatval;
+	}
+	return true;
+}
+
+//==========================================================================
+//
+// PFloat :: SetValue
+//
+//==========================================================================
+
+void PFloat::SetValue(void *addr, int val)
+{
+	return SetValue(addr, (double)val);
+}
+
+void PFloat::SetValue(void *addr, double val)
+{
+	assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address");
+	if (Size == 4)
+	{
+		*(float *)addr = (float)val;
+	}
+	else
+	{
+		assert(Size == 8);
+		*(double *)addr = val;
+	}
+}
+
+//==========================================================================
+//
+// PFloat :: GetValueInt
+//
+//==========================================================================
+
+int PFloat::GetValueInt(void *addr) const
+{
+	return xs_ToInt(GetValueFloat(addr));
+}
+
+//==========================================================================
+//
+// PFloat :: GetValueFloat
+//
+//==========================================================================
+
+double PFloat::GetValueFloat(void *addr) const
+{
+	assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address");
+	if (Size == 4)
+	{
+		return *(float *)addr;
+	}
+	else
+	{
+		assert(Size == 8);
+		return *(double *)addr;
+	}
+}
+
+//==========================================================================
+//
+// PFloat :: GetStoreOp
+//
+//==========================================================================
+
+void PFloat::SetOps()
+{
+	if (Size == 4)
+	{
+		storeOp = OP_SSP;
+		loadOp = OP_LSP;
+	}
+	else
+	{
+		assert(Size == 8);
+		storeOp = OP_SDP;
+		loadOp = OP_LDP;
+	}
+	moveOp = OP_MOVEF;
+	RegType = REGT_FLOAT;
+}
+
+/* PString ****************************************************************/
+
+//==========================================================================
+//
+// PString Default Constructor
+//
+//==========================================================================
+
+PString::PString()
+: PBasicType(sizeof(FString), alignof(FString))
+{
+	mDescriptiveName = "String";
+	storeOp = OP_SS;
+	loadOp = OP_LS;
+	moveOp = OP_MOVES;
+	RegType = REGT_STRING;
+
+}
+
+//==========================================================================
+//
+// PString :: WriteValue
+//
+//==========================================================================
+
+void PString::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	ar(key, *(FString*)addr);
+}
+
+//==========================================================================
+//
+// PString :: ReadValue
+//
+//==========================================================================
+
+bool PString::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	const char *cptr;
+	ar.StringPtr(key, cptr);
+	if (cptr == nullptr)
+	{
+		return false;
+	}
+	else
+	{
+		*(FString*)addr = cptr;
+		return true;
+	}
+}
+
+//==========================================================================
+//
+// PString :: SetDefaultValue
+//
+//==========================================================================
+
+void PString::SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special)
+{
+	if (base != nullptr) new((uint8_t *)base + offset) FString;
+	if (special != nullptr)
+	{
+		special->Push(std::make_pair(this, offset));
+	}
+}
+
+//==========================================================================
+//
+// PString :: InitializeValue
+//
+//==========================================================================
+
+void PString::InitializeValue(void *addr, const void *def) const
+{
+	if (def != nullptr)
+	{
+		new(addr) FString(*(FString *)def);
+	}
+	else
+	{
+		new(addr) FString;
+	}
+}
+
+//==========================================================================
+//
+// PString :: DestroyValue
+//
+//==========================================================================
+
+void PString::DestroyValue(void *addr) const
+{
+	((FString *)addr)->~FString();
+}
+
+/* PName ******************************************************************/
+
+//==========================================================================
+//
+// PName Default Constructor
+//
+//==========================================================================
+
+PName::PName()
+: PInt(sizeof(FName), true, false)
+{
+	mDescriptiveName = "Name";
+	Flags |= TYPE_IntNotInt;
+	assert(sizeof(FName) == alignof(FName));
+}
+
+//==========================================================================
+//
+// PName :: WriteValue
+//
+//==========================================================================
+
+void PName::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	const char *cptr = ((const FName*)addr)->GetChars();
+	ar.StringPtr(key, cptr);
+}
+
+//==========================================================================
+//
+// PName :: ReadValue
+//
+//==========================================================================
+
+bool PName::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	const char *cptr;
+	ar.StringPtr(key, cptr);
+	if (cptr == nullptr)
+	{
+		return false;
+	}
+	else
+	{
+		*(FName*)addr = FName(cptr);
+		return true;
+	}
+}
+
+/* PTextureID ******************************************************************/
+
+//==========================================================================
+//
+// PTextureID Default Constructor
+//
+//==========================================================================
+
+PTextureID::PTextureID()
+	: PInt(sizeof(FTextureID), true, false)
+{
+	mDescriptiveName = "TextureID";
+	Flags |= TYPE_IntNotInt;
+	assert(sizeof(FTextureID) == alignof(FTextureID));
+}
+
+//==========================================================================
+//
+// PTextureID :: WriteValue
+//
+//==========================================================================
+
+void PTextureID::WriteValue(FSerializer &ar, const char *key, const void *addr) const
+{
+	FTextureID val = *(FTextureID*)addr;
+	ar(key, val);
+}
+
+//==========================================================================
+//
+// PTextureID :: ReadValue
+//
+//==========================================================================
+
+bool PTextureID::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	FTextureID val;
+	ar(key, val);
+	*(FTextureID*)addr = val;
+	return true;
+}
+
+/* PSound *****************************************************************/
+
+//==========================================================================
+//
+// PSound Default Constructor
+//
+//==========================================================================
+
+PSound::PSound()
+: PInt(sizeof(FSoundID), true)
+{
+	mDescriptiveName = "Sound";
+	Flags |= TYPE_IntNotInt;
+	assert(sizeof(FSoundID) == alignof(FSoundID));
+}
+
+//==========================================================================
+//
+// PSound :: WriteValue
+//
+//==========================================================================
+
+void PSound::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	const char *cptr = soundEngine->GetSoundName(*(const FSoundID *)addr);
+	ar.StringPtr(key, cptr);
+}
+
+//==========================================================================
+//
+// PSound :: ReadValue
+//
+//==========================================================================
+
+bool PSound::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	const char *cptr;
+	ar.StringPtr(key, cptr);
+	if (cptr == nullptr)
+	{
+		return false;
+	}
+	else
+	{
+		*(FSoundID *)addr = FSoundID(cptr);
+		return true;
+	}
+}
+
+/* PColor *****************************************************************/
+
+//==========================================================================
+//
+// PColor Default Constructor
+//
+//==========================================================================
+
+PColor::PColor()
+: PInt(sizeof(PalEntry), true)
+{
+	mDescriptiveName = "Color";
+	Flags |= TYPE_IntNotInt;
+	assert(sizeof(PalEntry) == alignof(PalEntry));
+}
+
+/* PStateLabel *****************************************************************/
+
+//==========================================================================
+//
+// PStateLabel Default Constructor
+//
+//==========================================================================
+
+PStateLabel::PStateLabel()
+	: PInt(sizeof(int), false, false)
+{
+	Flags |= TYPE_IntNotInt;
+	mDescriptiveName = "StateLabel";
+}
+
+/* PPointer ***************************************************************/
+
+//==========================================================================
+//
+// PPointer - Default Constructor
+//
+//==========================================================================
+
+PPointer::PPointer()
+: PBasicType(sizeof(void *), alignof(void *)), PointedType(nullptr), IsConst(false)
+{
+	mDescriptiveName = "NullPointer";
+	loadOp = OP_LP;
+	storeOp = OP_SP;
+	moveOp = OP_MOVEA;
+	RegType = REGT_POINTER;
+	Flags |= TYPE_Pointer;
+}
+
+//==========================================================================
+//
+// PPointer - Parameterized Constructor
+//
+//==========================================================================
+
+PPointer::PPointer(PType *pointsat, bool isconst)
+: PBasicType(sizeof(void *), alignof(void *)), PointedType(pointsat), IsConst(isconst)
+{
+	if (pointsat != nullptr)
+	{
+		mDescriptiveName.Format("Pointer<%s%s>", pointsat->DescriptiveName(), isconst ? "readonly " : "");
+		mVersion = pointsat->mVersion;
+	}
+	else
+	{
+		mDescriptiveName = "Pointer";
+		mVersion = 0;
+	}
+	loadOp = OP_LP;
+	storeOp = OP_SP;
+	moveOp = OP_MOVEA;
+	RegType = REGT_POINTER;
+	Flags |= TYPE_Pointer;
+}
+
+//==========================================================================
+//
+// PPointer :: IsMatch
+//
+//==========================================================================
+
+bool PPointer::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	assert(id2 == 0 || id2 == 1);
+	PType *pointat = (PType *)id1;
+
+	return pointat == PointedType && (!!id2) == IsConst;
+}
+
+//==========================================================================
+//
+// PPointer :: GetTypeIDs
+//
+//==========================================================================
+
+void PPointer::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)PointedType;
+	id2 = 0;
+}
+
+//==========================================================================
+//
+// PPointer :: WriteValue
+//
+//==========================================================================
+
+void PPointer::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	if (writer != nullptr)
+	{
+		writer(ar, key, addr);
+	}
+	else
+	{
+		I_Error("Attempt to save pointer to unhandled type %s", PointedType->DescriptiveName());
+	}
+}
+
+//==========================================================================
+//
+// PPointer :: ReadValue
+//
+//==========================================================================
+
+bool PPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	if (reader != nullptr)
+	{
+		return reader(ar, key, addr);
+	}
+	return false;
+}
+
+/* PObjectPointer **********************************************************/
+
+//==========================================================================
+//
+// PPointer :: GetStoreOp
+//
+//==========================================================================
+
+PObjectPointer::PObjectPointer(PClass *cls, bool isconst)
+	: PPointer(cls->VMType, isconst)
+{
+	loadOp = OP_LO;
+	Flags |= TYPE_ObjectPointer;
+	// Non-destroyed thinkers are always guaranteed to be linked into the thinker chain so we don't need the write barrier for them.
+	//if (cls && !cls->IsDescendantOf(RUNTIME_CLASS(DThinker))) storeOp = OP_SO;
+}
+
+//==========================================================================
+//
+// PPointer :: SetPointer
+//
+//==========================================================================
+
+void PObjectPointer::SetPointer(void *base, unsigned offset, TArray<size_t> *special)
+{
+	// Add to the list of pointers for this class.
+	special->Push(offset);
+}
+
+//==========================================================================
+//
+// PPointer :: WriteValue
+//
+//==========================================================================
+
+void PObjectPointer::WriteValue(FSerializer &ar, const char *key, const void *addr) const
+{
+	ar(key, *(DObject **)addr);
+}
+
+//==========================================================================
+//
+// PPointer :: ReadValue
+//
+//==========================================================================
+
+bool PObjectPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	bool res;
+	::Serialize(ar, key, *(DObject **)addr, nullptr, &res);
+	return res;
+}
+
+//==========================================================================
+//
+// NewPointer
+//
+// Returns a PPointer to an object of the specified type
+//
+//==========================================================================
+
+PPointer *NewPointer(PType *type, bool isconst)
+{
+	auto cp = PType::toClass(type);
+	if (cp) return NewPointer(cp->Descriptor, isconst);
+
+	size_t bucket;
+	PType *ptype = TypeTable.FindType(NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, &bucket);
+	if (ptype == nullptr)
+	{
+		ptype = new PPointer(type, isconst);
+		TypeTable.AddType(ptype, NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, bucket);
+	}
+	return static_cast<PPointer *>(ptype);
+}
+
+PPointer *NewPointer(PClass *cls, bool isconst)
+{
+	assert(cls->VMType != nullptr);
+
+	auto type = cls->VMType;
+	size_t bucket;
+	PType *ptype = TypeTable.FindType(NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, &bucket);
+	if (ptype == nullptr)
+	{
+		ptype = new PObjectPointer(cls, isconst);
+		TypeTable.AddType(ptype, NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, bucket);
+	}
+	return static_cast<PPointer *>(ptype);
+}
+
+
+
+/* PClassPointer **********************************************************/
+
+//==========================================================================
+//
+// PClassPointer - Parameterized Constructor
+//
+//==========================================================================
+
+PClassPointer::PClassPointer(PClass *restrict)
+: PPointer(restrict->VMType), ClassRestriction(restrict)
+{
+	if (restrict) mDescriptiveName.Format("ClassPointer<%s>", restrict->TypeName.GetChars());
+	else mDescriptiveName = "ClassPointer";
+	loadOp = OP_LP;
+	storeOp = OP_SP;
+	Flags |= TYPE_ClassPointer;
+	mVersion = restrict->VMType->mVersion;
+}
+
+//==========================================================================
+//
+// PPointer :: WriteValue
+//
+//==========================================================================
+
+void PClassPointer::WriteValue(FSerializer &ar, const char *key, const void *addr) const
+{
+	ar(key, *(PClass **)addr);
+}
+
+//==========================================================================
+//
+// PPointer :: ReadValue
+//
+//==========================================================================
+
+bool PClassPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	::Serialize(ar, key, *(PClass **)addr, (PClass**)nullptr);
+	return false;
+}
+
+//==========================================================================
+//
+// PClassPointer - isCompatible
+//
+//==========================================================================
+
+bool PClassPointer::isCompatible(PType *type)
+{
+	auto other = PType::toClassPointer(type);
+	return (other != nullptr && other->ClassRestriction->IsDescendantOf(ClassRestriction));
+}
+
+//==========================================================================
+//
+// PClassPointer :: SetPointer
+//
+//==========================================================================
+
+void PClassPointer::SetPointer(void *base, unsigned offset, TArray<size_t> *special)
+{
+}
+
+//==========================================================================
+//
+// PClassPointer :: IsMatch
+//
+//==========================================================================
+
+bool PClassPointer::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	const PClass *classat = (const PClass *)id2;
+	return classat == ClassRestriction;
+}
+
+//==========================================================================
+//
+// PClassPointer :: GetTypeIDs
+//
+//==========================================================================
+
+void PClassPointer::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = 0;
+	id2 = (intptr_t)ClassRestriction;
+}
+
+//==========================================================================
+//
+// NewClassPointer
+//
+// Returns a PClassPointer for the restricted type.
+//
+//==========================================================================
+
+PClassPointer *NewClassPointer(PClass *restrict)
+{
+	size_t bucket;
+	PType *ptype = TypeTable.FindType(NAME_Class, 0, (intptr_t)restrict, &bucket);
+	if (ptype == nullptr)
+	{
+		ptype = new PClassPointer(restrict);
+		TypeTable.AddType(ptype, NAME_Class, 0, (intptr_t)restrict, bucket);
+	}
+	return static_cast<PClassPointer *>(ptype);
+}
+
+/* PEnum ******************************************************************/
+
+//==========================================================================
+//
+// PEnum - Parameterized Constructor
+//
+//==========================================================================
+
+PEnum::PEnum(FName name, PTypeBase *outer)
+: PInt(4, false), Outer(outer), EnumName(name)
+{
+	Flags |= TYPE_IntNotInt;
+	mDescriptiveName.Format("Enum<%s>", name.GetChars());
+}
+
+//==========================================================================
+//
+// NewEnum
+//
+// Returns a PEnum for the given name and container, making sure not to
+// create duplicates.
+//
+//==========================================================================
+
+PEnum *NewEnum(FName name, PTypeBase *outer)
+{
+	size_t bucket;
+	if (outer == nullptr) outer = Namespaces.GlobalNamespace;
+	PType *etype = TypeTable.FindType(NAME_Enum, (intptr_t)outer, name.GetIndex(), &bucket);
+	if (etype == nullptr)
+	{
+		etype = new PEnum(name, outer);
+		TypeTable.AddType(etype, NAME_Enum, (intptr_t)outer, name.GetIndex(), bucket);
+	}
+	return static_cast<PEnum *>(etype);
+}
+
+/* PArray *****************************************************************/
+
+//==========================================================================
+//
+// PArray - Parameterized Constructor
+//
+//==========================================================================
+
+PArray::PArray(PType *etype, unsigned int ecount)
+: ElementType(etype), ElementCount(ecount)
+{
+	mDescriptiveName.Format("Array<%s>[%d]", etype->DescriptiveName(), ecount);
+
+	Align = etype->Align;
+	// Since we are concatenating elements together, the element size should
+	// also be padded to the nearest alignment.
+	ElementSize = (etype->Size + (etype->Align - 1)) & ~(etype->Align - 1);
+	Size = ElementSize * ecount;
+	Flags |= TYPE_Array;
+}
+
+//==========================================================================
+//
+// PArray :: IsMatch
+//
+//==========================================================================
+
+bool PArray::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	const PType *elemtype = (const PType *)id1;
+	unsigned int count = (unsigned int)(intptr_t)id2;
+
+	return elemtype == ElementType && count == ElementCount;
+}
+
+//==========================================================================
+//
+// PArray :: GetTypeIDs
+//
+//==========================================================================
+
+void PArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)ElementType;
+	id2 = ElementCount;
+}
+
+//==========================================================================
+//
+// PArray :: WriteValue
+//
+//==========================================================================
+
+void PArray::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	if (ar.BeginArray(key))
+	{
+		const uint8_t *addrb = (const uint8_t *)addr;
+		for (unsigned i = 0; i < ElementCount; ++i)
+		{
+			ElementType->WriteValue(ar, nullptr, addrb);
+			addrb += ElementSize;
+		}
+		ar.EndArray();
+	}
+}
+
+//==========================================================================
+//
+// PArray :: ReadValue
+//
+//==========================================================================
+
+bool PArray::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	if (ar.BeginArray(key))
+	{
+		bool readsomething = false;
+		unsigned count = ar.ArraySize();
+		unsigned loop = std::min(count, ElementCount);
+		uint8_t *addrb = (uint8_t *)addr;
+		for(unsigned i=0;i<loop;i++)
+		{
+			readsomething |= ElementType->ReadValue(ar, nullptr, addrb);
+			addrb += ElementSize;
+		}
+		if (loop < count)
+		{
+			DPrintf(DMSG_WARNING, "Array on disk (%u) is bigger than in memory (%u)\n",
+				count, ElementCount);
+		}
+		ar.EndArray();
+		return readsomething;
+	}
+	return false;
+}
+
+//==========================================================================
+//
+// PArray :: SetDefaultValue
+//
+//==========================================================================
+
+void PArray::SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special)
+{
+	for (unsigned i = 0; i < ElementCount; ++i)
+	{
+		ElementType->SetDefaultValue(base, offset + i*ElementSize, special);
+	}
+}
+
+//==========================================================================
+//
+// PArray :: SetDefaultValue
+//
+//==========================================================================
+
+void PArray::SetPointer(void *base, unsigned offset, TArray<size_t> *special)
+{
+	for (unsigned i = 0; i < ElementCount; ++i)
+	{
+		ElementType->SetPointer(base, offset + i*ElementSize, special);
+	}
+}
+
+//==========================================================================
+//
+// PArray :: SetPointerArray
+//
+//==========================================================================
+
+void PArray::SetPointerArray(void *base, unsigned offset, TArray<size_t> *special)
+{
+	if (ElementType->isStruct())
+	{
+		for (unsigned int i = 0; i < ElementCount; ++i)
+		{
+			ElementType->SetPointerArray(base, offset + ElementSize * i, special);
+		}
+	}
+}
+
+//==========================================================================
+//
+// NewArray
+//
+// Returns a PArray for the given type and size, making sure not to create
+// duplicates.
+//
+//==========================================================================
+
+PArray *NewArray(PType *type, unsigned int count)
+{
+	size_t bucket;
+	PType *atype = TypeTable.FindType(NAME_Array, (intptr_t)type, count, &bucket);
+	if (atype == nullptr)
+	{
+		atype = new PArray(type, count);
+		TypeTable.AddType(atype, NAME_Array, (intptr_t)type, count, bucket);
+	}
+	return (PArray *)atype;
+}
+
+/* PArray *****************************************************************/
+
+//==========================================================================
+//
+// PArray - Parameterized Constructor
+//
+//==========================================================================
+
+PStaticArray::PStaticArray(PType *etype)
+	: PArray(etype, 0)
+{
+	mDescriptiveName.Format("ResizableArray<%s>", etype->DescriptiveName());
+}
+
+//==========================================================================
+//
+// PArray :: IsMatch
+//
+//==========================================================================
+
+bool PStaticArray::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	const PType *elemtype = (const PType *)id1;
+	unsigned int count = (unsigned int)(intptr_t)id2;
+
+	return elemtype == ElementType && count == 0;
+}
+
+//==========================================================================
+//
+// PArray :: GetTypeIDs
+//
+//==========================================================================
+
+void PStaticArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)ElementType;
+	id2 = 0;
+}
+
+//==========================================================================
+//
+// NewStaticArray
+//
+// Returns a PArray for the given type and size, making sure not to create
+// duplicates.
+//
+//==========================================================================
+
+PStaticArray *NewStaticArray(PType *type)
+{
+	size_t bucket;
+	PType *atype = TypeTable.FindType(NAME_StaticArray, (intptr_t)type, 0, &bucket);
+	if (atype == nullptr)
+	{
+		atype = new PStaticArray(type);
+		TypeTable.AddType(atype, NAME_StaticArray, (intptr_t)type, 0, bucket);
+	}
+	return (PStaticArray *)atype;
+}
+
+/* PDynArray **************************************************************/
+
+//==========================================================================
+//
+// PDynArray - Parameterized Constructor
+//
+//==========================================================================
+
+PDynArray::PDynArray(PType *etype,PStruct *backing)
+: ElementType(etype), BackingType(backing)
+{
+	mDescriptiveName.Format("DynArray<%s>", etype->DescriptiveName());
+	Size = sizeof(FArray);
+	Align = alignof(FArray);
+}
+
+//==========================================================================
+//
+// PDynArray :: IsMatch
+//
+//==========================================================================
+
+bool PDynArray::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	assert(id2 == 0);
+	const PType *elemtype = (const PType *)id1;
+
+	return elemtype == ElementType;
+}
+
+//==========================================================================
+//
+// PDynArray :: GetTypeIDs
+//
+//==========================================================================
+
+void PDynArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)ElementType;
+	id2 = 0;
+}
+
+//==========================================================================
+//
+// PDynArray :: InitializeValue
+//
+//==========================================================================
+
+void PDynArray::InitializeValue(void *addr, const void *deff) const
+{
+	const FArray *def = (const FArray*)deff;
+	FArray *aray = (FArray*)addr;
+
+	if (def == nullptr || def->Count == 0)
+	{
+		// Empty arrays do not need construction.
+		*aray = { nullptr, 0, 0 };
+	}
+	else if (ElementType->GetRegType() != REGT_STRING)
+	{
+		// These are just integral values which can be done without any constructor hackery.
+		size_t blocksize = ElementType->Size * def->Count;
+		aray->Array = M_Malloc(blocksize);
+		memcpy(aray->Array, def->Array, blocksize);
+		aray->Most = aray->Count = def->Count;
+	}
+	else
+	{
+		// non-empty string arrays require explicit construction.
+		new(addr) TArray<FString>(*(TArray<FString>*)def);
+	}
+}
+
+//==========================================================================
+//
+// PDynArray :: DestroyValue
+//
+//==========================================================================
+
+void PDynArray::DestroyValue(void *addr) const
+{
+	FArray *aray = (FArray*)addr;
+
+	if (aray->Array != nullptr)
+	{
+		if (ElementType->GetRegType() != REGT_STRING)
+		{
+			M_Free(aray->Array);
+		}
+		else
+		{
+			// Damn those cursed strings again. :(
+			((TArray<FString>*)addr)->~TArray<FString>();
+		}
+	}
+	aray->Count = aray->Most = 0;
+	aray->Array = nullptr;
+}
+
+//==========================================================================
+//
+// PDynArray :: SetDefaultValue
+//
+//==========================================================================
+
+void PDynArray::SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special)
+{
+	if (base != nullptr) memset((char*)base + offset, 0, sizeof(FArray));	// same as constructing an empty array.
+	if (special != nullptr)
+	{
+		special->Push(std::make_pair(this, offset));
+	}
+}
+
+//==========================================================================
+//
+// PDynArray :: SetPointer
+//
+//==========================================================================
+
+void PDynArray::SetPointerArray(void *base, unsigned offset, TArray<size_t> *special)
+{
+	if (ElementType->isObjectPointer())
+	{
+		// Add to the list of pointer arrays for this class.
+		special->Push(offset);
+	}
+}
+
+//==========================================================================
+//
+// PDynArray :: WriteValue
+//
+//==========================================================================
+
+void PDynArray::WriteValue(FSerializer &ar, const char *key, const void *addr) const
+{
+	FArray *aray = (FArray*)addr;
+	// We may skip an empty array only if it gets stored under a named key.
+	// If no name is given, i.e. it's part of an outer array's element list, even empty arrays must be stored,
+	// because otherwise the array would lose its entry.
+	if (aray->Count > 0 || key == nullptr)	
+	{
+		if (ar.BeginArray(key))
+		{
+			const uint8_t *addrb = (const uint8_t *)aray->Array;
+			for (unsigned i = 0; i < aray->Count; ++i)
+			{
+				ElementType->WriteValue(ar, nullptr, addrb);
+				addrb += ElementType->Size;
+			}
+			ar.EndArray();
+		}
+	}
+}
+
+//==========================================================================
+//
+// PDynArray :: ReadValue
+//
+//==========================================================================
+
+bool PDynArray::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	FArray *aray = (FArray*)addr;
+	DestroyValue(addr);	// note that even after calling this we still got a validly constructed empty array.
+
+	if (ar.BeginArray(key))
+	{
+		bool readsomething = false;
+		unsigned count = ar.ArraySize();
+
+		size_t blocksize = ElementType->Size * count;
+		aray->Array = M_Malloc(blocksize);
+		memset(aray->Array, 0, blocksize);
+		aray->Most = aray->Count = count;
+
+		uint8_t *addrb = (uint8_t *)aray->Array;
+		for (unsigned i = 0; i<count; i++)
+		{
+			// Strings must be constructed first.
+			if (ElementType->GetRegType() == REGT_STRING) new(addrb) FString;
+			readsomething |= ElementType->ReadValue(ar, nullptr, addrb);
+			addrb += ElementType->Size;
+		}
+		ar.EndArray();
+		return readsomething;
+	}
+	return false;
+}
+
+//==========================================================================
+//
+// NewDynArray
+//
+// Creates a new DynArray of the given type, making sure not to create a
+// duplicate.
+//
+//==========================================================================
+
+PDynArray *NewDynArray(PType *type)
+{
+	size_t bucket;
+	PType *atype = TypeTable.FindType(NAME_DynArray, (intptr_t)type, 0, &bucket);
+	if (atype == nullptr)
+	{
+		FString backingname;
+
+		switch (type->GetRegType())
+		{
+		case REGT_INT:
+			backingname.Format("DynArray_I%d", type->Size * 8);
+			break;
+
+		case REGT_FLOAT:
+			backingname.Format("DynArray_F%d", type->Size * 8);
+			break;
+
+		case REGT_STRING:
+			backingname = "DynArray_String";
+			break;
+
+		case REGT_POINTER:
+			if (type->isObjectPointer())
+				backingname = "DynArray_Obj";
+			else
+				backingname = "DynArray_Ptr";
+			break;
+
+		default:
+			I_Error("Unsupported dynamic array requested");
+			break;
+		}
+
+		auto backing = NewStruct(backingname, nullptr, true);
+		atype = new PDynArray(type, backing);
+		TypeTable.AddType(atype, NAME_DynArray, (intptr_t)type, 0, bucket);
+	}
+	return (PDynArray *)atype;
+}
+
+/* PMap *******************************************************************/
+
+//==========================================================================
+//
+// PMap - Parameterized Constructor
+//
+//==========================================================================
+
+PMap::PMap(PType *keytype, PType *valtype)
+: KeyType(keytype), ValueType(valtype)
+{
+	mDescriptiveName.Format("Map<%s, %s>", keytype->DescriptiveName(), valtype->DescriptiveName());
+	Size = sizeof(FMap);
+	Align = alignof(FMap);
+}
+
+//==========================================================================
+//
+// PMap :: IsMatch
+//
+//==========================================================================
+
+bool PMap::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	const PType *keyty = (const PType *)id1;
+	const PType *valty = (const PType *)id2;
+
+	return keyty == KeyType && valty == ValueType;
+}
+
+//==========================================================================
+//
+// PMap :: GetTypeIDs
+//
+//==========================================================================
+
+void PMap::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)KeyType;
+	id2 = (intptr_t)ValueType;
+}
+
+//==========================================================================
+//
+// NewMap
+//
+// Returns a PMap for the given key and value types, ensuring not to create
+// duplicates.
+//
+//==========================================================================
+
+PMap *NewMap(PType *keytype, PType *valuetype)
+{
+	size_t bucket;
+	PType *maptype = TypeTable.FindType(NAME_Map, (intptr_t)keytype, (intptr_t)valuetype, &bucket);
+	if (maptype == nullptr)
+	{
+		maptype = new PMap(keytype, valuetype);
+		TypeTable.AddType(maptype, NAME_Map, (intptr_t)keytype, (intptr_t)valuetype, bucket);
+	}
+	return (PMap *)maptype;
+}
+
+/* PStruct ****************************************************************/
+
+//==========================================================================
+//
+// PStruct - Parameterized Constructor
+//
+//==========================================================================
+
+PStruct::PStruct(FName name, PTypeBase *outer, bool isnative)
+: PContainerType(name, outer)
+{
+	mDescriptiveName.Format("%sStruct<%s>", isnative? "Native" : "", name.GetChars());
+	Size = 0;
+	isNative = isnative;
+}
+
+//==========================================================================
+//
+// PStruct :: SetDefaultValue
+//
+//==========================================================================
+
+void PStruct::SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special)
+{
+	auto it = Symbols.GetIterator();
+	PSymbolTable::MapType::Pair *pair;
+	while (it.NextPair(pair))
+	{
+		auto field = dyn_cast<PField>(pair->Value);
+		if (field && !(field->Flags & VARF_Transient))
+		{
+			field->Type->SetDefaultValue(base, unsigned(offset + field->Offset), special);
+		}
+	}
+}
+
+//==========================================================================
+//
+// PStruct :: SetPointer
+//
+//==========================================================================
+
+void PStruct::SetPointer(void *base, unsigned offset, TArray<size_t> *special)
+{
+	auto it = Symbols.GetIterator();
+	PSymbolTable::MapType::Pair *pair;
+	while (it.NextPair(pair))
+	{
+		auto field = dyn_cast<PField>(pair->Value);
+		if (field && !(field->Flags & VARF_Transient))
+		{
+			field->Type->SetPointer(base, unsigned(offset + field->Offset), special);
+		}
+	}
+}
+
+//==========================================================================
+//
+// PStruct :: SetPointerArray
+//
+//==========================================================================
+
+void PStruct::SetPointerArray(void *base, unsigned offset, TArray<size_t> *special)
+{
+	auto it = Symbols.GetIterator();
+	PSymbolTable::MapType::Pair *pair;
+	while (it.NextPair(pair))
+	{
+		auto field = dyn_cast<PField>(pair->Value);
+		if (field && !(field->Flags & VARF_Transient))
+		{
+			field->Type->SetPointerArray(base, unsigned(offset + field->Offset), special);
+		}
+	}
+}
+
+//==========================================================================
+//
+// PStruct :: WriteValue
+//
+//==========================================================================
+
+void PStruct::WriteValue(FSerializer &ar, const char *key,const void *addr) const
+{
+	if (ar.BeginObject(key))
+	{
+		Symbols.WriteFields(ar, addr);
+		ar.EndObject();
+	}
+}
+
+//==========================================================================
+//
+// PStruct :: ReadValue
+//
+//==========================================================================
+
+bool PStruct::ReadValue(FSerializer &ar, const char *key, void *addr) const
+{
+	if (ar.BeginObject(key))
+	{
+		bool ret = Symbols.ReadFields(ar, addr, DescriptiveName());
+		ar.EndObject();
+		return ret;
+	}
+	return false;
+}
+
+//==========================================================================
+//
+// PStruct :: AddField
+//
+// Appends a new field to the end of a struct. Returns either the new field
+// or nullptr if a symbol by that name already exists.
+//
+//==========================================================================
+
+PField *PStruct::AddField(FName name, PType *type, uint32_t flags)
+{
+	assert(type->Size > 0);
+	return Symbols.AddField(name, type, flags, Size, &Align);
+}
+
+//==========================================================================
+//
+// PStruct :: AddField
+//
+// Appends a new native field to the struct. Returns either the new field
+// or nullptr if a symbol by that name already exists.
+//
+//==========================================================================
+
+PField *PStruct::AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue)
+{
+	return Symbols.AddNativeField(name, type, address, flags, bitvalue);
+}
+
+//==========================================================================
+//
+// NewStruct
+// Returns a PStruct for the given name and container, making sure not to
+// create duplicates.
+//
+//==========================================================================
+
+PStruct *NewStruct(FName name, PTypeBase *outer, bool native)
+{
+	size_t bucket;
+	if (outer == nullptr) outer = Namespaces.GlobalNamespace;
+	PType *stype = TypeTable.FindType(NAME_Struct, (intptr_t)outer, name.GetIndex(), &bucket);
+	if (stype == nullptr)
+	{
+		stype = new PStruct(name, outer, native);
+		TypeTable.AddType(stype, NAME_Struct, (intptr_t)outer, name.GetIndex(), bucket);
+	}
+	return static_cast<PStruct *>(stype);
+}
+
+
+/* PPrototype *************************************************************/
+
+//==========================================================================
+//
+// PPrototype - Parameterized Constructor
+//
+//==========================================================================
+
+PPrototype::PPrototype(const TArray<PType *> &rettypes, const TArray<PType *> &argtypes)
+: ArgumentTypes(argtypes), ReturnTypes(rettypes)
+{
+}
+
+//==========================================================================
+//
+// PPrototype :: IsMatch
+//
+//==========================================================================
+
+bool PPrototype::IsMatch(intptr_t id1, intptr_t id2) const
+{
+	const TArray<PType *> *args = (const TArray<PType *> *)id1;
+	const TArray<PType *> *rets = (const TArray<PType *> *)id2;
+
+	return *args == ArgumentTypes && *rets == ReturnTypes;
+}
+
+//==========================================================================
+//
+// PPrototype :: GetTypeIDs
+//
+//==========================================================================
+
+void PPrototype::GetTypeIDs(intptr_t &id1, intptr_t &id2) const
+{
+	id1 = (intptr_t)&ArgumentTypes;
+	id2 = (intptr_t)&ReturnTypes;
+}
+
+//==========================================================================
+//
+// NewPrototype
+//
+// Returns a PPrototype for the given return and argument types, making sure
+// not to create duplicates.
+//
+//==========================================================================
+
+PPrototype *NewPrototype(const TArray<PType *> &rettypes, const TArray<PType *> &argtypes)
+{
+	size_t bucket;
+	PType *proto = TypeTable.FindType(NAME_Prototype, (intptr_t)&argtypes, (intptr_t)&rettypes, &bucket);
+	if (proto == nullptr)
+	{
+		proto = new PPrototype(rettypes, argtypes);
+		TypeTable.AddType(proto, NAME_Prototype, (intptr_t)&argtypes, (intptr_t)&rettypes, bucket);
+	}
+	return static_cast<PPrototype *>(proto);
+}
+
+/* PClass *****************************************************************/
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PClassType::PClassType(PClass *cls)
+{
+	assert(cls->VMType == nullptr);
+	Descriptor = cls;
+	TypeName = cls->TypeName;
+	if (cls->ParentClass != nullptr)
+	{
+		ParentType = cls->ParentClass->VMType;
+		assert(ParentType != nullptr);
+		Symbols.SetParentTable(&ParentType->Symbols);
+		ScopeFlags = ParentType->ScopeFlags;
+	}
+	cls->VMType = this;
+	mDescriptiveName.Format("Class<%s>", cls->TypeName.GetChars());
+}
+
+//==========================================================================
+//
+// PClass :: AddField
+//
+//==========================================================================
+
+PField *PClassType::AddField(FName name, PType *type, uint32_t flags)
+{
+	return Descriptor->AddField(name, type, flags);
+}
+
+//==========================================================================
+//
+// PClass :: AddNativeField
+//
+//==========================================================================
+
+PField *PClassType::AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue)
+{
+	auto field = Symbols.AddNativeField(name, type, address, flags, bitvalue);
+	if (field != nullptr) Descriptor->Fields.Push(field);
+	return field;
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+PClassType *NewClassType(PClass *cls)
+{
+	size_t bucket;
+	PType *ptype = TypeTable.FindType(NAME_Object, 0, cls->TypeName.GetIndex(), &bucket);
+	if (ptype == nullptr)
+	{
+		ptype = new PClassType(cls);
+		TypeTable.AddType(ptype, NAME_Object, 0, cls->TypeName.GetIndex(), bucket);
+	}
+	return static_cast<PClassType *>(ptype);
+}
+
+
+/* FTypeTable **************************************************************/
+
+//==========================================================================
+//
+// FTypeTable :: FindType
+//
+//==========================================================================
+
+PType *FTypeTable::FindType(FName type_name, intptr_t parm1, intptr_t parm2, size_t *bucketnum)
+{
+	size_t bucket = Hash(type_name, parm1, parm2) % HASH_SIZE;
+	if (bucketnum != nullptr)
+	{
+		*bucketnum = bucket;
+	}
+	for (PType *type = TypeHash[bucket]; type != nullptr; type = type->HashNext)
+	{
+		if (type->TypeTableType == type_name && type->IsMatch(parm1, parm2))
+		{
+			return type;
+		}
+	}
+	return nullptr;
+}
+
+//==========================================================================
+//
+// FTypeTable :: AddType - Fully Parameterized Version
+//
+//==========================================================================
+
+void FTypeTable::AddType(PType *type, FName type_name, intptr_t parm1, intptr_t parm2, size_t bucket)
+{
+#ifdef _DEBUG
+	size_t bucketcheck;
+	assert(FindType(type_name, parm1, parm2, &bucketcheck) == nullptr && "Type must not be inserted more than once");
+	assert(bucketcheck == bucket && "Passed bucket was wrong");
+#endif
+	type->TypeTableType = type_name;
+	type->HashNext = TypeHash[bucket];
+	TypeHash[bucket] = type;
+}
+
+//==========================================================================
+//
+// FTypeTable :: AddType - Simple Version
+//
+//==========================================================================
+
+void FTypeTable::AddType(PType *type, FName type_name)
+{
+	intptr_t parm1, parm2;
+	size_t bucket;
+
+	// Type table stuff id only needed to let all classes hash to the same group. For all other types this is pointless.
+	type->TypeTableType = type_name;
+	type->GetTypeIDs(parm1, parm2);
+	bucket = Hash(type_name, parm1, parm2) % HASH_SIZE;
+	assert(FindType(type_name, parm1, parm2, nullptr) == nullptr && "Type must not be inserted more than once");
+
+	type->HashNext = TypeHash[bucket];
+	TypeHash[bucket] = type;
+}
+
+//==========================================================================
+//
+// FTypeTable :: Hash												STATIC
+//
+//==========================================================================
+
+size_t FTypeTable::Hash(FName p1, intptr_t p2, intptr_t p3)
+{
+	size_t i1 = (size_t)p1.GetIndex();
+
+	// Swap the high and low halves of i1. The compiler should be smart enough
+	// to transform this into a ROR or ROL.
+	i1 = (i1 >> (sizeof(size_t)*4)) | (i1 << (sizeof(size_t)*4));
+
+	if (p1 != NAME_Prototype)
+	{
+		size_t i2 = (size_t)p2;
+		size_t i3 = (size_t)p3;
+		return (~i1 ^ i2) + i3 * 961748927;	// i3 is multiplied by a prime
+	}
+	else
+	{ // Prototypes need to hash the TArrays at p2 and p3
+		const TArray<PType *> *a2 = (const TArray<PType *> *)p2;
+		const TArray<PType *> *a3 = (const TArray<PType *> *)p3;
+		for (unsigned i = 0; i < a2->Size(); ++i)
+		{
+			i1 = (i1 * 961748927) + (size_t)((*a2)[i]);
+		}
+		for (unsigned i = 0; i < a3->Size(); ++i)
+		{
+			i1 = (i1 * 961748927) + (size_t)((*a3)[i]);
+		}
+		return i1;
+	}
+}
+
+//==========================================================================
+//
+// FTypeTable :: Clear
+//
+//==========================================================================
+
+void FTypeTable::Clear()
+{
+	for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i)
+	{
+		for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr;)
+		{
+			auto next = ty->HashNext;
+			delete ty;
+			ty = next;
+		}
+	}
+	memset(TypeHash, 0, sizeof(TypeHash));
+}
+
+#include "c_dispatch.h"
+CCMD(typetable)
+{
+	DumpTypeTable();
+}
+
diff --git a/source/common/scripting/core/types.h b/source/common/scripting/core/types.h
new file mode 100644
index 000000000..e40e8edf3
--- /dev/null
+++ b/source/common/scripting/core/types.h
@@ -0,0 +1,625 @@
+#pragma once
+
+#include "dobject.h"
+#include "serializer.h"
+#include "symbols.h"
+#include "scopebarrier.h"
+
+// Variable/parameter/field flags -------------------------------------------
+
+// Making all these different storage types use a common set of flags seems
+// like the simplest thing to do.
+
+enum
+{
+	VARF_Optional		= (1<<0),	// func param is optional
+	VARF_Method			= (1<<1),	// func has an implied self parameter
+	VARF_Action			= (1<<2),	// func has implied owner and state parameters
+	VARF_Native			= (1<<3),	// func is native code, field is natively defined
+	VARF_ReadOnly		= (1<<4),	// field is read only, do not write to it
+	VARF_Private		= (1<<5),	// field is private to containing class
+	VARF_Protected		= (1<<6),	// field is only accessible by containing class and children.
+	VARF_Deprecated		= (1<<7),	// Deprecated fields should output warnings when used.
+	VARF_Virtual		= (1<<8),	// function is virtual
+	VARF_Final			= (1<<9),	// Function may not be overridden in subclasses
+	VARF_In				= (1<<10),
+	VARF_Out			= (1<<11),
+	VARF_Implicit		= (1<<12),	// implicitly created parameters (i.e. do not compare types when checking function signatures)
+	VARF_Static			= (1<<13),
+	VARF_InternalAccess	= (1<<14),	// overrides VARF_ReadOnly for internal script code.
+	VARF_Override		= (1<<15),	// overrides a virtual function from the parent class.
+	VARF_Ref			= (1<<16),	// argument is passed by reference.
+	VARF_Transient		= (1<<17),  // don't auto serialize field.
+	VARF_Meta			= (1<<18),	// static class data (by necessity read only.)
+	VARF_VarArg			= (1<<19),  // [ZZ] vararg: don't typecheck values after ... in function signature
+	VARF_UI				= (1<<20),  // [ZZ] ui: object is ui-scope only (can't modify playsim)
+	VARF_Play			= (1<<21),  // [ZZ] play: object is playsim-scope only (can't access ui)
+	VARF_VirtualScope	= (1<<22),  // [ZZ] virtualscope: object should use the scope of the particular class it's being used with (methods only)
+	VARF_ClearScope		= (1<<23),  // [ZZ] clearscope: this method ignores the member access chain that leads to it and is always plain data.
+};
+
+// Basic information shared by all types ------------------------------------
+
+// Only one copy of a type is ever instantiated at one time.
+// - Enums, classes, and structs are defined by their names and outer classes.
+// - Pointers are uniquely defined by the type they point at.
+// - ClassPointers are also defined by their class restriction.
+// - Arrays are defined by their element type and count.
+// - DynArrays are defined by their element type.
+// - Maps are defined by their key and value types.
+// - Prototypes are defined by the argument and return types.
+// - Functions are defined by their names and outer objects.
+// In table form:
+//                  Outer  Name  Type  Type2  Count
+//   Enum             *      *
+//   Class            *      *
+//   Struct           *      *
+//   Function         *      *
+//   Pointer                       *
+//   ClassPointer                  +      *
+//   Array                         *            *
+//   DynArray                      *
+//   Map                           *      *
+//   Prototype                     *+     *+
+
+class PContainerType;
+class PPointer;
+class PClassPointer;
+class PArray;
+class PStruct;
+class PClassType;
+
+struct ZCC_ExprConstant;
+class PType : public PTypeBase
+{
+protected:
+
+	enum ETypeFlags
+	{
+		TYPE_Scalar = 1,
+		TYPE_Container = 2,
+		TYPE_Int = 4,
+		TYPE_IntNotInt = 8,				// catch-all for subtypes that are not being checked by type directly.
+		TYPE_Float = 16,
+		TYPE_Pointer = 32,
+		TYPE_ObjectPointer = 64,
+		TYPE_ClassPointer = 128,
+		TYPE_Array = 256,
+
+		TYPE_IntCompatible = TYPE_Int | TYPE_IntNotInt,	// must be the combination of all flags that are subtypes of int and can be cast to an int.
+	};
+
+public:
+	FName TypeTableType;			// The type to use for hashing into the type table
+	unsigned int	Size;			// this type's size
+	unsigned int	Align;			// this type's preferred alignment
+	unsigned int	Flags = 0;		// What is this type?
+	PType			*HashNext;		// next type in this type table
+	PSymbolTable	Symbols;
+	bool			MemberOnly = false;		// type may only be used as a struct/class member but not as a local variable or function argument.
+	FString			mDescriptiveName;
+	VersionInfo		mVersion = { 0,0,0 };
+	uint8_t loadOp, storeOp, moveOp, RegType, RegCount;
+	EScopeFlags ScopeFlags = (EScopeFlags)0;
+
+	PType(unsigned int size = 1, unsigned int align = 1);
+	virtual ~PType();
+	virtual bool isNumeric() { return false; }
+
+	// Writes the value of a variable of this type at (addr) to an archive, preceded by
+	// a tag indicating its type. The tag is there so that variable types can be changed
+	// without completely breaking savegames, provided that the change isn't between
+	// totally unrelated types.
+	virtual void WriteValue(FSerializer &ar, const char *key,const void *addr) const;
+
+	// Returns true if the stored value was compatible. False otherwise.
+	// If the value was incompatible, then the memory at *addr is unchanged.
+	virtual bool ReadValue(FSerializer &ar, const char *key,void *addr) const;
+
+	// Sets the default value for this type at (base + offset)
+	// If the default value is binary 0, then this function doesn't need
+	// to do anything, because PClass::Extend() takes care of that.
+	//
+	// The stroffs array is so that types that need special initialization
+	// and destruction (e.g. strings) can add their offsets to it for special
+	// initialization when the object is created and destruction when the
+	// object is destroyed.
+	virtual void SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special=NULL);
+	virtual void SetPointer(void *base, unsigned offset, TArray<size_t> *ptrofs = NULL);
+	virtual void SetPointerArray(void *base, unsigned offset, TArray<size_t> *ptrofs = NULL);
+
+	// Initialize the value, if needed (e.g. strings)
+	virtual void InitializeValue(void *addr, const void *def) const;
+
+	// Destroy the value, if needed (e.g. strings)
+	virtual void DestroyValue(void *addr) const;
+
+	// Sets the value of a variable of this type at (addr)
+	virtual void SetValue(void *addr, int val);
+	virtual void SetValue(void *addr, double val);
+
+	// Gets the value of a variable of this type at (addr)
+	virtual int GetValueInt(void *addr) const;
+	virtual double GetValueFloat(void *addr) const;
+
+	// Gets the opcode to store from a register to memory
+	int GetStoreOp() const
+	{
+		return storeOp;
+	}
+
+	// Gets the opcode to load from memory to a register
+	int GetLoadOp() const
+	{
+		return loadOp;
+	}
+
+	// Gets the opcode to move from register to another register
+	int GetMoveOp() const
+	{
+		return moveOp;
+	}
+
+	// Gets the register type for this type
+	int GetRegType() const
+	{
+		return RegType;
+	}
+
+	int GetRegCount() const
+	{
+		return RegCount;
+	}
+	// Returns true if this type matches the two identifiers. Referring to the
+	// above table, any type is identified by at most two characteristics. Each
+	// type that implements this function will cast these to the appropriate type.
+	// It is up to the caller to make sure they are the correct types. There is
+	// only one prototype for this function in order to simplify type table
+	// management.
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+
+	// Get the type IDs used by IsMatch
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+
+	const char *DescriptiveName() const;
+
+	static void StaticInit();
+
+	bool isScalar() const { return !!(Flags & TYPE_Scalar); }
+	bool isContainer() const { return !!(Flags & TYPE_Container); }
+	bool isInt() const { return (Flags & TYPE_IntCompatible) == TYPE_Int; }
+	bool isIntCompatible() const { return !!(Flags & TYPE_IntCompatible); }
+	bool isFloat() const { return !!(Flags & TYPE_Float); }
+	bool isPointer() const { return !!(Flags & TYPE_Pointer); }
+	bool isRealPointer() const { return (Flags & (TYPE_Pointer|TYPE_ClassPointer)) == TYPE_Pointer; }	// This excludes class pointers which use their PointedType differently
+	bool isObjectPointer() const { return !!(Flags & TYPE_ObjectPointer); }
+	bool isClassPointer() const { return !!(Flags & TYPE_ClassPointer); }
+	bool isEnum() const { return TypeTableType == NAME_Enum; }
+	bool isArray() const { return !!(Flags & TYPE_Array); }
+	bool isStaticArray() const { return TypeTableType == NAME_StaticArray; }
+	bool isDynArray() const { return TypeTableType == NAME_DynArray; }
+	bool isStruct() const { return TypeTableType == NAME_Struct; }
+	bool isClass() const { return TypeTableType == NAME_Object; }
+	bool isPrototype() const { return TypeTableType == NAME_Prototype; }
+
+	PContainerType *toContainer() { return isContainer() ? (PContainerType*)this : nullptr; }
+	PPointer *toPointer() { return isPointer() ? (PPointer*)this : nullptr; }
+	static PClassPointer *toClassPointer(PType *t) { return t && t->isClassPointer() ? (PClassPointer*)t : nullptr; }
+	static PClassType *toClass(PType *t) { return t && t->isClass() ? (PClassType*)t : nullptr; }
+};
+
+// Not-really-a-type types --------------------------------------------------
+
+class PErrorType : public PType
+{
+public:
+	PErrorType(int which = 1) : PType(0, which) {}
+};
+
+class PVoidType : public PType
+{
+public:
+	PVoidType() : PType(0, 1) {}
+};
+
+// Some categorization typing -----------------------------------------------
+
+class PBasicType : public PType
+{
+protected:
+	PBasicType(unsigned int size = 1, unsigned int align = 1);
+};
+
+class PCompoundType : public PType
+{
+protected:
+	PCompoundType(unsigned int size = 1, unsigned int align = 1);
+};
+
+class PContainerType : public PCompoundType
+{
+public:
+	PTypeBase		*Outer = nullptr;			// object this type is contained within
+	FName			TypeName = NAME_None;		// this type's name
+
+	PContainerType()
+	{
+		mDescriptiveName = "ContainerType";
+		Flags |= TYPE_Container;
+	}
+	PContainerType(FName name, PTypeBase *outer) : Outer(outer), TypeName(name) 
+	{
+		mDescriptiveName = name.GetChars();
+		Flags |= TYPE_Container;
+	}
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+	virtual PField *AddField(FName name, PType *type, uint32_t flags = 0) = 0;
+	virtual PField *AddNativeField(FName name, PType *type, size_t address, uint32_t flags = 0, int bitvalue = 0) = 0;
+};
+
+// Basic types --------------------------------------------------------------
+
+class PInt : public PBasicType
+{
+public:
+	PInt(unsigned int size, bool unsign, bool compatible = true);
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+
+	virtual void SetValue(void *addr, int val);
+	virtual void SetValue(void *addr, double val);
+	virtual int GetValueInt(void *addr) const;
+	virtual double GetValueFloat(void *addr) const;
+	virtual bool isNumeric() override { return IntCompatible; }
+
+	bool Unsigned;
+	bool IntCompatible;
+protected:
+	void SetOps();
+};
+
+class PBool : public PInt
+{
+public:
+	PBool();
+	virtual void SetValue(void *addr, int val);
+	virtual void SetValue(void *addr, double val);
+	virtual int GetValueInt(void *addr) const;
+	virtual double GetValueFloat(void *addr) const;
+};
+
+class PFloat : public PBasicType
+{
+public:
+	PFloat(unsigned int size = 8);
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+
+	virtual void SetValue(void *addr, int val);
+	virtual void SetValue(void *addr, double val);
+	virtual int GetValueInt(void *addr) const;
+	virtual double GetValueFloat(void *addr) const;
+	virtual bool isNumeric() override { return true; }
+protected:
+	void SetOps();
+private:
+	struct SymbolInitF
+	{
+		ENamedName Name;
+		double Value;
+	};
+	struct SymbolInitI
+	{
+		ENamedName Name;
+		int Value;
+	};
+
+	void SetSingleSymbols();
+	void SetDoubleSymbols();
+	void SetSymbols(const SymbolInitF *syminit, size_t count);
+	void SetSymbols(const SymbolInitI *syminit, size_t count);
+};
+
+class PString : public PBasicType
+{
+public:
+	PString();
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+	void SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special=NULL) override;
+	void InitializeValue(void *addr, const void *def) const override;
+	void DestroyValue(void *addr) const override;
+};
+
+// Variations of integer types ----------------------------------------------
+
+class PName : public PInt
+{
+public:
+	PName();
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+};
+
+class PSound : public PInt
+{
+public:
+	PSound();
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+};
+
+class PTextureID : public PInt
+{
+public:
+	PTextureID();
+
+	void WriteValue(FSerializer &ar, const char *key, const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key, void *addr) const override;
+};
+
+class PColor : public PInt
+{
+public:
+	PColor();
+};
+
+class PStateLabel : public PInt
+{
+public:
+	PStateLabel();
+};
+
+// Pointers -----------------------------------------------------------------
+
+class PPointer : public PBasicType
+{
+
+public:
+	typedef void(*WriteHandler)(FSerializer &ar, const char *key, const void *addr);
+	typedef bool(*ReadHandler)(FSerializer &ar, const char *key, void *addr);
+
+	PPointer();
+	PPointer(PType *pointsat, bool isconst = false);
+
+	PType *PointedType;
+	bool IsConst;
+
+	WriteHandler writer = nullptr;
+	ReadHandler reader = nullptr;
+
+	void InstallHandlers(WriteHandler w, ReadHandler r)
+	{
+		writer = w;
+		reader = r;
+	}
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+
+protected:
+	void SetOps();
+};
+
+class PObjectPointer : public PPointer
+{
+public:
+	PObjectPointer(PClass *pointedtype = nullptr, bool isconst = false);
+
+	void WriteValue(FSerializer &ar, const char *key, const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key, void *addr) const override;
+	void SetPointer(void *base, unsigned offset, TArray<size_t> *special = NULL) override;
+	PClass *PointedClass() const;
+};
+
+
+class PClassPointer : public PPointer
+{
+public:
+	PClassPointer(class PClass *restrict = nullptr);
+
+	class PClass *ClassRestriction;
+
+	bool isCompatible(PType *type);
+	void WriteValue(FSerializer &ar, const char *key, const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key, void *addr) const override;
+
+	void SetPointer(void *base, unsigned offset, TArray<size_t> *special = NULL) override;
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+};
+
+// Compound types -----------------------------------------------------------
+
+class PEnum : public PInt
+{
+public:
+	PEnum(FName name, PTypeBase *outer);
+
+	PTypeBase *Outer;
+	FName EnumName;
+};
+
+class PArray : public PCompoundType
+{
+public:
+	PArray(PType *etype, unsigned int ecount);
+
+	PType *ElementType;
+	unsigned int ElementCount;
+	unsigned int ElementSize;
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+
+	void SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *special) override;
+	void SetPointer(void *base, unsigned offset, TArray<size_t> *special) override;
+	void SetPointerArray(void *base, unsigned offset, TArray<size_t> *ptrofs = NULL) override;
+};
+
+class PStaticArray : public PArray
+{
+public:
+	PStaticArray(PType *etype);
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+};
+
+class PDynArray : public PCompoundType
+{
+public:
+	PDynArray(PType *etype, PStruct *backing);
+
+	PType *ElementType;
+	PStruct *BackingType;
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+
+	void WriteValue(FSerializer &ar, const char *key, const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key, void *addr) const override;
+	void SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *specials) override;
+	void InitializeValue(void *addr, const void *def) const override;
+	void DestroyValue(void *addr) const override;
+	void SetPointerArray(void *base, unsigned offset, TArray<size_t> *ptrofs = NULL) override;
+};
+
+class PMap : public PCompoundType
+{
+public:
+	PMap(PType *keytype, PType *valtype);
+
+	PType *KeyType;
+	PType *ValueType;
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+};
+
+class PStruct : public PContainerType
+{
+public:
+	PStruct(FName name, PTypeBase *outer, bool isnative = false);
+
+	bool isNative;
+	// Some internal structs require explicit construction and destruction of fields the VM cannot handle directly so use these two functions for it.
+	VMFunction *mConstructor = nullptr;
+	VMFunction *mDestructor = nullptr;
+
+	virtual PField *AddField(FName name, PType *type, uint32_t flags=0);
+	virtual PField *AddNativeField(FName name, PType *type, size_t address, uint32_t flags = 0, int bitvalue = 0);
+
+	void WriteValue(FSerializer &ar, const char *key,const void *addr) const override;
+	bool ReadValue(FSerializer &ar, const char *key,void *addr) const override;
+	void SetDefaultValue(void *base, unsigned offset, TArray<FTypeAndOffset> *specials) override;
+	void SetPointer(void *base, unsigned offset, TArray<size_t> *specials) override;
+	void SetPointerArray(void *base, unsigned offset, TArray<size_t> *special) override;
+};
+
+class PPrototype : public PCompoundType
+{
+public:
+	PPrototype(const TArray<PType *> &rettypes, const TArray<PType *> &argtypes);
+
+	TArray<PType *> ArgumentTypes;
+	TArray<PType *> ReturnTypes;
+
+	virtual bool IsMatch(intptr_t id1, intptr_t id2) const;
+	virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const;
+};
+
+
+// Meta-info for every class derived from DObject ---------------------------
+
+class PClassType : public PContainerType
+{
+public:
+	PClass *Descriptor;
+	PClassType *ParentType;
+
+	PClassType(PClass *cls = nullptr);
+	PField *AddField(FName name, PType *type, uint32_t flags = 0) override;
+	PField *AddNativeField(FName name, PType *type, size_t address, uint32_t flags = 0, int bitvalue = 0) override;
+};
+
+
+inline PClass *PObjectPointer::PointedClass() const
+{
+	return static_cast<PClassType*>(PointedType)->Descriptor;
+}
+
+// Returns a type from the TypeTable. Will create one if it isn't present.
+PMap *NewMap(PType *keytype, PType *valuetype);
+PArray *NewArray(PType *type, unsigned int count);
+PStaticArray *NewStaticArray(PType *type);
+PDynArray *NewDynArray(PType *type);
+PPointer *NewPointer(PType *type, bool isconst = false);
+PPointer *NewPointer(PClass *type, bool isconst = false);
+PClassPointer *NewClassPointer(PClass *restrict);
+PEnum *NewEnum(FName name, PTypeBase *outer);
+PStruct *NewStruct(FName name, PTypeBase *outer, bool native = false);
+PPrototype *NewPrototype(const TArray<PType *> &rettypes, const TArray<PType *> &argtypes);
+PClassType *NewClassType(PClass *cls);
+
+// Built-in types -----------------------------------------------------------
+
+extern PErrorType *TypeError;
+extern PErrorType *TypeAuto;
+extern PVoidType *TypeVoid;
+extern PInt *TypeSInt8,  *TypeUInt8;
+extern PInt *TypeSInt16, *TypeUInt16;
+extern PInt *TypeSInt32, *TypeUInt32;
+extern PBool *TypeBool;
+extern PFloat *TypeFloat32, *TypeFloat64;
+extern PString *TypeString;
+extern PName *TypeName;
+extern PSound *TypeSound;
+extern PColor *TypeColor;
+extern PTextureID *TypeTextureID;
+extern PStruct *TypeVector2;
+extern PStruct *TypeVector3;
+extern PStruct *TypeColorStruct;
+extern PStruct *TypeStringStruct;
+extern PPointer *TypeFont;
+extern PStateLabel *TypeStateLabel;
+extern PPointer *TypeNullPtr;
+extern PPointer *TypeVoidPtr;
+
+inline FString &DObject::StringVar(FName field)
+{
+	return *(FString*)ScriptVar(field, TypeString);
+}
+
+// Type tables --------------------------------------------------------------
+
+struct FTypeTable
+{
+	enum { HASH_SIZE = 1021 };
+
+	PType *TypeHash[HASH_SIZE];
+
+	PType *FindType(FName type_name, intptr_t parm1, intptr_t parm2, size_t *bucketnum);
+	void AddType(PType *type, FName type_name, intptr_t parm1, intptr_t parm2, size_t bucket);
+	void AddType(PType *type, FName type_name);
+	void Clear();
+
+	static size_t Hash(FName p1, intptr_t p2, intptr_t p3);
+};
+
+
+extern FTypeTable TypeTable;
+
diff --git a/source/common/scripting/core/vmdisasm.cpp b/source/common/scripting/core/vmdisasm.cpp
new file mode 100644
index 000000000..b3dbece6e
--- /dev/null
+++ b/source/common/scripting/core/vmdisasm.cpp
@@ -0,0 +1,687 @@
+/*
+** vmdisasm.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright -2016 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include "dobject.h"
+#include "c_console.h"
+#include "templates.h"
+#include "vmintern.h"
+#include "printf.h"
+
+#define NOP		MODE_AUNUSED | MODE_BUNUSED | MODE_CUNUSED
+
+#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 RPKP	MODE_AP | MODE_BKP | 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 I8I16	MODE_AIMMZ | MODE_BCIMMZ
+#define __BCP	MODE_PARAM24
+#define RPI8	MODE_AP | MODE_BIMMZ | MODE_CUNUSED
+#define KPI8	MODE_AKP | MODE_BIMMZ | MODE_CUNUSED
+#define RPI8I8	MODE_AP | MODE_BIMMZ | MODE_CIMMZ
+#define RPRPI8	MODE_AP | MODE_BP | 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 CASTB	MODE_AI | 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 RFRII8	MODE_AF | MODE_BI | MODE_CIMMZ
+#define RPRII8	MODE_AP | MODE_BI | MODE_CIMMZ
+#define RSRII8	MODE_AS | 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 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 RVRVRF	MODE_AV | MODE_BV | MODE_CF
+#define RVRVKF	MODE_AV | MODE_BV | MODE_CKF
+#define RVKVRF	MODE_AV | MODE_BKV | MODE_CF
+#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
+
+#define CIRR	MODE_ACMP | MODE_BI | MODE_CI
+#define CIRK	MODE_ACMP | MODE_BI | MODE_CKI
+#define CIKR	MODE_ACMP | MODE_BKI | MODE_CI
+#define CFRR	MODE_ACMP | MODE_BF | MODE_CF
+#define CFRK	MODE_ACMP | MODE_BF | MODE_CKF
+#define CFKR	MODE_ACMP | MODE_BKF | MODE_CF
+#define CVRR	MODE_ACMP | MODE_BV | MODE_CV
+#define CVRK	MODE_ACMP | MODE_BV | MODE_CKV
+#define CPRR	MODE_ACMP | MODE_BP | MODE_CP
+#define CPRK	MODE_ACMP | MODE_BP | MODE_CKP
+
+const VMOpInfo OpInfo[NUM_OPS] =
+{
+#define xx(op, name, mode, alt, kreg, ktype)	{ #name, mode },
+#include "vmops.h"
+};
+
+static const char *const FlopNames[] =
+{
+	"abs",
+	"neg",
+	"exp",
+	"log",
+	"log10",
+	"sqrt",
+	"ceil",
+	"floor",
+
+	"acos rad",
+	"asin rad",
+	"atan rad",
+	"cos rad",
+	"sin rad",
+	"tan rad",
+
+	"acos deg",
+	"asin deg",
+	"atan deg",
+	"cos deg",
+	"sin deg",
+	"tan deg",
+
+	"cosh",
+	"sinh",
+	"tanh",
+
+	"round",
+};
+
+static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func);
+
+static int printf_wrapper(FILE *f, const char *fmt, ...)
+{
+	va_list argptr;
+	int count;
+
+	va_start(argptr, fmt);
+	if (f == NULL)
+	{
+		count = VPrintf(PRINT_HIGH, fmt, argptr);
+	}
+	else
+	{
+		count = vfprintf(f, fmt, argptr);
+	}
+	va_end(argptr);
+	return count;
+}
+
+void VMDumpConstants(FILE *out, const VMScriptFunction *func)
+{
+	char tmp[30];
+	int i, j, k, kk;
+
+	if (func->KonstD != NULL && func->NumKonstD != 0)
+	{
+		printf_wrapper(out, "\nConstant integers:\n");
+		kk = (func->NumKonstD + 3) / 4;
+		for (i = 0; i < kk; ++i)
+		{
+			for (j = 0, k = i; j < 4 && k < func->NumKonstD; j++, k += kk)
+			{
+				mysnprintf(tmp, countof(tmp), "%3d. %d", k, func->KonstD[k]);
+				printf_wrapper(out, "%-20s", tmp);
+			}
+			printf_wrapper(out, "\n");
+		}
+	}
+	if (func->KonstF != NULL && func->NumKonstF != 0)
+	{
+		printf_wrapper(out, "\nConstant floats:\n");
+		kk = (func->NumKonstF + 3) / 4;
+		for (i = 0; i < kk; ++i)
+		{
+			for (j = 0, k = i; j < 4 && k < func->NumKonstF; j++, k += kk)
+			{
+				mysnprintf(tmp, countof(tmp), "%3d. %.16f", k, func->KonstF[k]);
+				printf_wrapper(out, "%-20s", tmp);
+			}
+			printf_wrapper(out, "\n");
+		}
+	}
+	if (func->KonstA != NULL && func->NumKonstA != 0)
+	{
+		printf_wrapper(out, "\nConstant addresses:\n");
+		kk = (func->NumKonstA + 3) / 4;
+		for (i = 0; i < kk; ++i)
+		{
+			for (j = 0, k = i; j < 4 && k < func->NumKonstA; j++, k += kk)
+			{
+				mysnprintf(tmp, countof(tmp), "%3d. %p", k, func->KonstA[k].v);
+				printf_wrapper(out, "%-22s", tmp);
+			}
+			printf_wrapper(out, "\n");
+		}
+	}
+	if (func->KonstS != NULL && func->NumKonstS != 0)
+	{
+		printf_wrapper(out, "\nConstant strings:\n");
+		for (i = 0; i < func->NumKonstS; ++i)
+		{
+			printf_wrapper(out, "%3d. %s\n", i, func->KonstS[i].GetChars());
+		}
+	}
+}
+
+void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func)
+{
+	VMFunction *callfunc;
+	const char *name;
+	int col;
+	int mode;
+	int a;
+	bool cmp;
+	char cmpname[8];
+
+	for (int i = 0; i < codesize; ++i)
+	{
+		name = OpInfo[code[i].op].Name;
+		mode = OpInfo[code[i].op].Mode;
+		a = code[i].a;
+		cmp = (mode & MODE_ATYPE) == MODE_ACMP;
+
+		// String comparison encodes everything in a single instruction.
+		if (code[i].op == OP_CMPS)
+		{
+			switch (a & CMP_METHOD_MASK)
+			{
+			case CMP_EQ:	name = "beq";	break;
+			case CMP_LT:	name = "blt";	break;
+			case CMP_LE:	name = "ble";	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;
+			cmp = true;
+		}
+		if (code[i].op == OP_PARAM && code[i].a & REGT_ADDROF)
+		{
+			name = "parama";
+		}
+		if (cmp)
+		{ // Comparison instruction. Modify name for inverted test.
+			if (!(a & CMP_CHECK))
+			{
+				strcpy(cmpname, name);
+				if (name[1] == 'e')
+				{ // eq -> ne
+					cmpname[1] = 'n', cmpname[2] = 'e';
+				}
+				else if (name[2] == 't')
+				{ // lt -> ge
+					cmpname[1] = 'g', cmpname[2] = 'e';
+				}
+				else
+				{ // le -> gt
+					cmpname[1] = 'g', cmpname[2] = 't';
+				}
+				name = cmpname;
+			}
+		}
+		printf_wrapper(out, "%08x: %02x%02x%02x%02x %-8s", i << 2, code[i].op, code[i].a, code[i].b, code[i].c, name);
+		col = 0;
+		switch (code[i].op)
+		{
+		case OP_JMP:
+		//case OP_TRY:
+			col = printf_wrapper(out, "%08x", (i + 1 + code[i].i24) << 2);
+			break;
+
+		case OP_PARAMI:
+			col = printf_wrapper(out, "%d", code[i].i24);
+			break;
+
+		case OP_CALL_K:
+		{
+			callfunc = (VMFunction *)func->KonstA[code[i].a].o;
+			col = printf_wrapper(out, "[%p],%d", callfunc, code[i].b);
+			if (code[i].op == OP_CALL_K)
+			{
+				col += printf_wrapper(out, ",%d", code[i].c);
+			}
+			break;
+		}
+
+		case OP_PARAM:
+		{
+			col = print_reg(out, col, code[i].i24 & 0xffffff, MODE_PARAM24, 16, func);
+			break;
+		}
+
+		case OP_RESULT:
+		{
+			// Default handling for this broke after changing OP_PARAM...
+			col = print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func);
+			break;
+		}
+
+		case OP_RET:
+			if (code[i].b != REGT_NIL)
+			{
+				if (a == RET_FINAL)
+				{
+					col = print_reg(out, 0, code[i].i16u, MODE_PARAM, 16, func);
+				}
+				else
+				{
+					col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
+					col += print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func);
+					if (a & RET_FINAL)
+					{
+						col += printf_wrapper(out, " [final]");
+					}
+				}
+			}
+			break;
+
+		case OP_RETI:
+			if (a == RET_FINAL)
+			{
+				col = printf_wrapper(out, "%d", code[i].i16);
+			}
+			else
+			{
+				col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
+				col += print_reg(out, col, code[i].i16, MODE_IMMS, 16, func);
+				if (a & RET_FINAL)
+				{
+					col += printf_wrapper(out, " [final]");
+				}
+			}
+			break;
+
+		case OP_FLOP:
+			col = printf_wrapper(out, "f%d,f%d,%d", code[i].a, code[i].b, code[i].c);
+			if (code[i].c < countof(FlopNames))
+			{
+				col += printf_wrapper(out, " [%s]", FlopNames[code[i].c]);
+			}
+			break;
+
+		default:
+
+
+			if ((mode & MODE_BCTYPE) == MODE_BCCAST)
+			{
+				switch (code[i].c)
+				{
+				case CASTB_I:
+					mode = MODE_AI | MODE_BI | MODE_CUNUSED;
+					break;
+				case CASTB_A:
+					mode = MODE_AI | MODE_BP | MODE_CUNUSED;
+					break;
+				case CAST_I2F:
+				case CAST_U2F:
+					mode = MODE_AF | MODE_BI | MODE_CUNUSED;
+					break;
+				case CAST_Co2S:
+				case CAST_So2S:
+				case CAST_N2S:
+				case CAST_I2S:
+				case CAST_U2S:
+					mode = MODE_AS | MODE_BI | MODE_CUNUSED;
+					break;
+				case CAST_F2I:
+				case CAST_F2U:
+				case CASTB_F:
+					mode = MODE_AI | MODE_BF | MODE_CUNUSED;
+					break;
+				case CAST_F2S:
+				case CAST_V22S:
+				case CAST_V32S:
+					mode = MODE_AS | MODE_BF | MODE_CUNUSED;
+					break;
+				case CAST_P2S:
+					mode = MODE_AS | MODE_BP | MODE_CUNUSED;
+					break;
+				case CAST_S2Co:
+				case CAST_S2So:
+				case CAST_S2N:
+				case CAST_S2I:
+				case CASTB_S:
+					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(out, 0, a, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
+			if ((mode & MODE_BCTYPE) == MODE_BCTHROW)
+			{
+				if (code[i].a == 0)
+				{
+					mode = (MODE_BP | MODE_CUNUSED);
+				}
+				else if (code[i].a == 1)
+				{
+					mode = (MODE_BKP | MODE_CUNUSED);
+				}
+				else
+				{
+					mode = (MODE_BCJOINT | MODE_BCIMMS);
+				}
+			}
+			else if ((mode & MODE_BCTYPE) == MODE_BCCATCH)
+			{
+				switch (code[i].a)
+				{
+				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(out, col, code[i].i16u, (mode & MODE_BCTYPE) >> MODE_BCSHIFT, 16, func);
+			}
+			else
+			{
+				col += print_reg(out, col, code[i].b, (mode & MODE_BTYPE) >> MODE_BSHIFT, 24, func);
+				col += print_reg(out, col, code[i].c, (mode & MODE_CTYPE) >> MODE_CSHIFT, 24, func);
+			}
+			break;
+		}
+		if (cmp && i + 1 < codesize)
+		{
+			if (code[i+1].op != OP_JMP)
+			{ // comparison instructions must be followed by jump
+				col += printf_wrapper(out, " => *!*!*!*\n");
+			}
+			else
+			{ 
+				col += printf_wrapper(out, " => %08x", (i + 2 + code[i+1].i24) << 2);
+			}
+		}
+		if (col > 30)
+		{
+			col = 30;
+		}
+		printf_wrapper(out, "%*c", 30 - col, ';');
+		if (!cmp && (code[i].op == OP_JMP || /*code[i].op == OP_TRY ||*/ code[i].op == OP_PARAMI))
+		{
+			printf_wrapper(out, "%d\n", code[i].i24);
+		}
+		else
+		{
+			printf_wrapper(out, "%d,%d,%d", code[i].a, code[i].b, code[i].c);
+			if (cmp && i + 1 < codesize && code[i+1].op == OP_JMP)
+			{
+				printf_wrapper(out, ",%d\n", code[++i].i24);
+			}
+			else if (code[i].op == OP_CALL_K)
+			{
+				printf_wrapper(out, "  [%s]\n", callfunc->PrintableName.GetChars());
+			}
+			else
+			{
+				printf_wrapper(out, "\n");
+			}
+		}
+	}
+}
+
+static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func)
+{
+	if (mode == MODE_UNUSED || mode == MODE_CMP)
+	{
+		return 0;
+	}
+	if (col > 0)
+	{
+		col = printf_wrapper(out, ",");
+	}
+	switch(mode)
+	{
+	case MODE_I:
+		return col+printf_wrapper(out, "d%d", arg);
+	case MODE_F:
+		return col+printf_wrapper(out, "f%d", arg);
+	case MODE_S:
+		return col+printf_wrapper(out, "s%d", arg);
+	case MODE_P:
+		return col+printf_wrapper(out, "a%d", arg);
+	case MODE_V:
+		return col+printf_wrapper(out, "v%d", arg);
+
+	case MODE_KI:
+		if (func != NULL)
+		{
+			return col+printf_wrapper(out, "%d", func->KonstD[arg]);
+		}
+		return printf_wrapper(out, "kd%d", arg);
+	case MODE_KF:
+		if (func != NULL)
+		{
+			return col+printf_wrapper(out, "%#g", func->KonstF[arg]);
+		}
+		return col+printf_wrapper(out, "kf%d", arg);
+	case MODE_KS:
+		if (func != NULL)
+		{
+			return col+printf_wrapper(out, "\"%.27s\"", func->KonstS[arg].GetChars());
+		}
+		return col+printf_wrapper(out, "ks%d", arg);
+	case MODE_KP:
+		if (func != NULL)
+		{
+			return col+printf_wrapper(out, "%p", func->KonstA[arg]);
+		}
+		return col+printf_wrapper(out, "ka%d", arg);
+	case MODE_KV:
+		if (func != NULL)
+		{
+			return col+printf_wrapper(out, "(%f,%f,%f)", func->KonstF[arg], func->KonstF[arg+1], func->KonstF[arg+2]);
+		}
+		return col+printf_wrapper(out, "kv%d", arg);
+
+	case MODE_IMMS:
+		return col+printf_wrapper(out, "%d", (arg << immshift) >> immshift);
+
+	case MODE_IMMZ:
+		return col+printf_wrapper(out, "%d", arg);
+
+	case MODE_PARAM:
+	case MODE_PARAM24:
+	{
+		int regtype, regnum;
+#ifdef __BIG_ENDIAN__
+		if (mode == MODE_PARAM)
+		{
+			regtype = (arg >> 8) & 255;
+			regnum = arg & 255;
+		}
+		else
+		{
+			regtype = (arg >> 16) & 255;
+			regnum = arg & 65535;
+		}
+#else
+		if (mode == MODE_PARAM)
+		{
+			regtype = arg & 255;
+			regnum = (arg >> 8) & 255;
+		}
+		else
+		{
+			regtype = arg & 255;
+			regnum = (arg >> 8) & 65535;
+		}
+#endif
+			switch (regtype & (REGT_TYPE | REGT_KONST | REGT_MULTIREG))
+			{
+			case REGT_INT:
+				return col+printf_wrapper(out, "d%d", regnum);
+			case REGT_FLOAT:
+				return col+printf_wrapper(out, "f%d", regnum);
+			case REGT_STRING:
+				return col+printf_wrapper(out, "s%d", regnum);
+			case REGT_POINTER:
+				return col+printf_wrapper(out, "a%d", regnum);
+			case REGT_FLOAT | REGT_MULTIREG2:
+				return col+printf_wrapper(out, "v%d.2", regnum);
+			case REGT_FLOAT | REGT_MULTIREG3:
+				return col+printf_wrapper(out, "v%d.3", regnum);
+			case REGT_INT | REGT_KONST:
+				return col+print_reg(out, 0, regnum, MODE_KI, 0, func);
+			case REGT_FLOAT | REGT_KONST:
+				return col+print_reg(out, 0, regnum, MODE_KF, 0, func);
+			case REGT_STRING | REGT_KONST:
+				return col+print_reg(out, 0, regnum, MODE_KS, 0, func);
+			case REGT_POINTER | REGT_KONST:
+				return col+print_reg(out, 0, regnum, MODE_KP, 0, func);
+			case REGT_FLOAT | REGT_MULTIREG | REGT_KONST:
+				return col+print_reg(out, 0, regnum, MODE_KV, 0, func);
+			default:
+				if (regtype == REGT_NIL)
+				{
+					return col+printf_wrapper(out, "nil");
+				}
+				return col+printf_wrapper(out, "param[t=%d,%c,%c,n=%d]",
+					regtype & REGT_TYPE,
+					regtype & REGT_KONST ? 'k' : 'r',
+					regtype & REGT_MULTIREG ? 'm' : 's',
+					regnum);
+			}
+		}
+
+	default:
+		return col+printf_wrapper(out, "$%d", arg);
+	}
+	return col;
+}
+
+//==========================================================================
+//
+// Do some postprocessing after everything has been defined
+//
+//==========================================================================
+
+void DumpFunction(FILE *dump, VMScriptFunction *sfunc, const char *label, int labellen)
+{
+	const char *marks = "=======================================================";
+	fprintf(dump, "\n%.*s %s %.*s", MAX(3, 38 - labellen / 2), marks, label, MAX(3, 38 - labellen / 2), marks);
+	fprintf(dump, "\nInteger regs: %-3d  Float regs: %-3d  Address regs: %-3d  String regs: %-3d\nStack size: %d\n",
+		sfunc->NumRegD, sfunc->NumRegF, sfunc->NumRegA, sfunc->NumRegS, sfunc->MaxParam);
+	VMDumpConstants(dump, sfunc);
+	fprintf(dump, "\nDisassembly @ %p:\n", sfunc->Code);
+	VMDisasm(dump, sfunc->Code, sfunc->CodeSize, sfunc);
+}
+
diff --git a/source/common/scripting/interface/stringformat.cpp b/source/common/scripting/interface/stringformat.cpp
new file mode 100644
index 000000000..589698ee2
--- /dev/null
+++ b/source/common/scripting/interface/stringformat.cpp
@@ -0,0 +1,278 @@
+/*
+** thingdef_data.cpp
+**
+** DECORATE data tables
+**
+**---------------------------------------------------------------------------
+** Copyright 2002-2008 Christoph Oelckers
+** Copyright 2004-2008 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be
+**    covered by the terms of the GNU General Public License as published by
+**    the Free Software Foundation; either version 2 of the License, or (at
+**    your option) any later version.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include "zstring.h"
+#include "vm.h"
+#include "gstrings.h"
+#include "v_font.h"
+#include "types.h"
+
+
+
+FString FStringFormat(VM_ARGS, int offset)
+{
+	PARAM_VA_POINTER(va_reginfo)	// Get the hidden type information array
+	assert(va_reginfo[offset] == REGT_STRING);
+
+	FString fmtstring = param[offset].s().GetChars();
+
+	param += offset;
+	numparam -= offset;
+	va_reginfo += offset;
+
+	// note: we don't need a real printf format parser.
+	//       enough to simply find the subtitution tokens and feed them to the real printf after checking types.
+	//       https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification
+	FString output;
+	bool in_fmt = false;
+	FString fmt_current;
+	int argnum = 1;
+	int argauto = 1;
+	// % = starts
+	//  [0-9], -, +, \s, 0, #, . continue
+	//  %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate
+	// various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM.
+	// the only combination that is parsed locally is %n$...
+	bool haveargnums = false;
+	for (size_t i = 0; i < fmtstring.Len(); i++)
+	{
+		char c = fmtstring[i];
+		if (in_fmt)
+		{
+			if (c == '*' && (fmt_current.Len() == 1 || (fmt_current.Len() == 2 && fmt_current[1] == '0')))
+			{
+				fmt_current += c;
+			}
+			else if ((c >= '0' && c <= '9') ||
+				c == '-' || c == '+' || (c == ' ' && fmt_current.Back() != ' ') || c == '#' || c == '.')
+			{
+				fmt_current += c;
+			}
+			else if (c == '$') // %number$format
+			{
+				if (!haveargnums && argauto > 1)
+					ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
+				FString argnumstr = fmt_current.Mid(1);
+				if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars());
+				auto argnum64 = argnumstr.ToLong();
+				if (argnum64 < 1 || argnum64 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum64, numparam);
+				fmt_current = "%";
+				haveargnums = true;
+				argnum = int(argnum64);
+			}
+			else
+			{
+				fmt_current += c;
+
+				switch (c)
+				{
+					// string
+				case 's':
+				{
+					if (argnum < 0 && haveargnums)
+						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
+					in_fmt = false;
+					// fail if something was found, but it's not a string
+					if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
+					if (va_reginfo[argnum] != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars());
+					// append
+					output.AppendFormat(fmt_current.GetChars(), param[argnum].s().GetChars());
+					if (!haveargnums) argnum = ++argauto;
+					else argnum = -1;
+					break;
+				}
+
+				// pointer
+				case 'p':
+				{
+					if (argnum < 0 && haveargnums)
+						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
+					in_fmt = false;
+					// fail if something was found, but it's not a string
+					if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
+					if (va_reginfo[argnum] != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars());
+					// append
+					output.AppendFormat(fmt_current.GetChars(), param[argnum].a);
+					if (!haveargnums) argnum = ++argauto;
+					else argnum = -1;
+					break;
+				}
+
+				// int formats (including char)
+				case 'd':
+				case 'i':
+				case 'u':
+				case 'x':
+				case 'X':
+				case 'o':
+				case 'c':
+				case 'B':
+				{
+					if (argnum < 0 && haveargnums)
+						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
+					in_fmt = false;
+					// append
+					if (fmt_current[1] == '*' || fmt_current[2] == '*')
+					{
+						// fail if something was found, but it's not an int
+						if (argnum+1 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
+						if (va_reginfo[argnum] != REGT_INT &&
+							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
+						if (va_reginfo[argnum+1] != REGT_INT &&
+							va_reginfo[argnum+1] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
+
+						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]), param[argnum + 1].ToInt(va_reginfo[argnum + 1]));
+						argauto++;
+					}
+					else
+					{
+						// fail if something was found, but it's not an int
+						if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
+						if (va_reginfo[argnum] != REGT_INT &&
+							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
+						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]));
+					}
+					if (!haveargnums) argnum = ++argauto;
+					else argnum = -1;
+					break;
+				}
+
+				// double formats
+				case 'f':
+				case 'F':
+				case 'e':
+				case 'E':
+				case 'g':
+				case 'G':
+				case 'a':
+				case 'A':
+				{
+					if (argnum < 0 && haveargnums)
+						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
+					in_fmt = false;
+					if (fmt_current[1] == '*' || fmt_current[2] == '*')
+					{
+						// fail if something was found, but it's not an int
+						if (argnum + 1 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
+						if (va_reginfo[argnum] != REGT_INT &&
+							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
+						if (va_reginfo[argnum + 1] != REGT_INT &&
+							va_reginfo[argnum + 1] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
+
+						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]), param[argnum + 1].ToDouble(va_reginfo[argnum + 1]));
+						argauto++;
+					}
+					else
+					{
+						// fail if something was found, but it's not a float
+						if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
+						if (va_reginfo[argnum] != REGT_INT &&
+							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
+						// append
+						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToDouble(va_reginfo[argnum]));
+					}
+					if (!haveargnums) argnum = ++argauto;
+					else argnum = -1;
+					break;
+				}
+
+				default:
+					// invalid character
+					output += fmt_current;
+					in_fmt = false;
+					break;
+				}
+			}
+		}
+		else
+		{
+			if (c == '%')
+			{
+				if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%')
+				{
+					output += '%';
+					i++;
+				}
+				else
+				{
+					in_fmt = true;
+					fmt_current = "%";
+				}
+			}
+			else
+			{
+				output += c;
+			}
+		}
+	}
+
+	return output;
+}
+
+DEFINE_ACTION_FUNCTION(FStringStruct, Format)
+{
+	PARAM_PROLOGUE;
+	FString s = FStringFormat(VM_ARGS_NAMES);
+	ACTION_RETURN_STRING(s);
+}
+
+DEFINE_ACTION_FUNCTION(FStringStruct, AppendFormat)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FString);
+	// first parameter is the self pointer
+	FString s = FStringFormat(VM_ARGS_NAMES, 1);
+	(*self) += s;
+	return 0;
+}
+
+DEFINE_ACTION_FUNCTION(FStringStruct, AppendCharacter)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FString);
+	PARAM_INT(c);
+	self->AppendCharacter(c);
+	return 0;
+}
+
+DEFINE_ACTION_FUNCTION(FStringStruct, DeleteLastCharacter)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FString);
+	self->DeleteLastCharacter();
+	return 0;
+}
diff --git a/source/common/scripting/jit/jit.cpp b/source/common/scripting/jit/jit.cpp
new file mode 100644
index 000000000..aa48585af
--- /dev/null
+++ b/source/common/scripting/jit/jit.cpp
@@ -0,0 +1,564 @@
+
+#include "jit.h"
+#include "jitintern.h"
+#include "printf.h"
+
+extern PString *TypeString;
+extern PStruct *TypeVector2;
+extern PStruct *TypeVector3;
+
+static void OutputJitLog(const asmjit::StringLogger &logger);
+
+JitFuncPtr JitCompile(VMScriptFunction *sfunc)
+{
+#if 0
+	if (strcmp(sfunc->PrintableName.GetChars(), "StatusScreen.drawNum") != 0)
+		return nullptr;
+#endif
+
+	using namespace asmjit;
+	StringLogger logger;
+	try
+	{
+		ThrowingErrorHandler errorHandler;
+		CodeHolder code;
+		code.init(GetHostCodeInfo());
+		code.setErrorHandler(&errorHandler);
+		code.setLogger(&logger);
+
+		JitCompiler compiler(&code, sfunc);
+		return reinterpret_cast<JitFuncPtr>(AddJitFunction(&code, &compiler));
+	}
+	catch (const CRecoverableError &e)
+	{
+		OutputJitLog(logger);
+		Printf("%s: Unexpected JIT error: %s\n",sfunc->PrintableName.GetChars(), e.what());
+		return nullptr;
+	}
+}
+
+void JitDumpLog(FILE *file, VMScriptFunction *sfunc)
+{
+	using namespace asmjit;
+	StringLogger logger;
+	try
+	{
+		ThrowingErrorHandler errorHandler;
+		CodeHolder code;
+		code.init(GetHostCodeInfo());
+		code.setErrorHandler(&errorHandler);
+		code.setLogger(&logger);
+
+		JitCompiler compiler(&code, sfunc);
+		compiler.Codegen();
+
+		fwrite(logger.getString(), logger.getLength(), 1, file);
+	}
+	catch (const std::exception &e)
+	{
+		fwrite(logger.getString(), logger.getLength(), 1, file);
+
+		FString err;
+		err.Format("Unexpected JIT error: %s\n", e.what());
+		fwrite(err.GetChars(), err.Len(), 1, file);
+		fclose(file);
+
+		I_FatalError("Unexpected JIT error: %s\n", e.what());
+	}
+}
+
+static void OutputJitLog(const asmjit::StringLogger &logger)
+{
+	// Write line by line since I_FatalError seems to cut off long strings
+	const char *pos = logger.getString();
+	const char *end = pos;
+	while (*end)
+	{
+		if (*end == '\n')
+		{
+			FString substr(pos, (int)(ptrdiff_t)(end - pos));
+			Printf("%s\n", substr.GetChars());
+			pos = end + 1;
+		}
+		end++;
+	}
+	if (pos != end)
+		Printf("%s\n", pos);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static const char *OpNames[NUM_OPS] =
+{
+#define xx(op, name, mode, alt, kreg, ktype)	#op,
+#include "vmops.h"
+#undef xx
+};
+
+asmjit::CCFunc *JitCompiler::Codegen()
+{
+	Setup();
+
+	int lastLine = -1;
+
+	pc = sfunc->Code;
+	auto end = pc + sfunc->CodeSize;
+	while (pc != end)
+	{
+		int i = (int)(ptrdiff_t)(pc - sfunc->Code);
+		op = pc->op;
+
+		int curLine = sfunc->PCToLine(pc);
+		if (curLine != lastLine)
+		{
+			lastLine = curLine;
+
+			auto label = cc.newLabel();
+			cc.bind(label);
+
+			JitLineInfo info;
+			info.Label = label;
+			info.LineNumber = curLine;
+			LineInfo.Push(info);
+		}
+
+		if (op != OP_PARAM && op != OP_PARAMI && op != OP_VTBL)
+		{
+			FString lineinfo;
+			lineinfo.Format("; line %d: %02x%02x%02x%02x %s", curLine, pc->op, pc->a, pc->b, pc->c, OpNames[op]);
+			cc.comment("", 0);
+			cc.comment(lineinfo.GetChars(), lineinfo.Len());
+		}
+
+		labels[i].cursor = cc.getCursor();
+		ResetTemp();
+		EmitOpcode();
+
+		pc++;
+	}
+
+	BindLabels();
+
+	cc.endFunc();
+	cc.finalize();
+
+	auto code = cc.getCode ();
+	for (unsigned int j = 0; j < LineInfo.Size (); j++)
+	{
+		auto info = LineInfo[j];
+
+		if (!code->isLabelValid (info.Label))
+		{
+			continue;
+		}
+
+		info.InstructionIndex = code->getLabelOffset (info.Label);
+
+		LineInfo[j] = info;
+	}
+
+	std::stable_sort(LineInfo.begin(), LineInfo.end(), [](const JitLineInfo &a, const JitLineInfo &b) { return a.InstructionIndex < b.InstructionIndex; });
+
+	return func;
+}
+
+void JitCompiler::EmitOpcode()
+{
+	switch (op)
+	{
+		#define xx(op, name, mode, alt, kreg, ktype)	case OP_##op: Emit##op(); break;
+		#include "vmops.h"
+		#undef xx
+
+	default:
+		I_FatalError("JIT error: Unknown VM opcode %d\n", op);
+		break;
+	}
+}
+
+void JitCompiler::BindLabels()
+{
+	asmjit::CBNode *cursor = cc.getCursor();
+	unsigned int size = labels.Size();
+	for (unsigned int i = 0; i < size; i++)
+	{
+		const OpcodeLabel &label = labels[i];
+		if (label.inUse)
+		{
+			cc.setCursor(label.cursor);
+			cc.bind(label.label);
+		}
+	}
+	cc.setCursor(cursor);
+}
+
+void JitCompiler::CheckVMFrame()
+{
+	if (!vmframeAllocated)
+	{
+		auto cursor = cc.getCursor();
+		cc.setCursor(vmframeCursor);
+
+		auto vmstack = cc.newStack(sfunc->StackSize, 16, "vmstack");
+		vmframe = cc.newIntPtr("vmframe");
+		cc.lea(vmframe, vmstack);
+
+		cc.setCursor(cursor);
+		vmframeAllocated = true;
+	}
+}
+
+asmjit::X86Gp JitCompiler::GetCallReturns()
+{
+	if (!callReturnsAllocated)
+	{
+		auto cursor = cc.getCursor();
+		cc.setCursor(callReturnsCursor);
+		auto stackalloc = cc.newStack(sizeof(VMReturn) * MAX_RETURNS, alignof(VMReturn), "stackalloc");
+		callReturns = cc.newIntPtr("callReturns");
+		cc.lea(callReturns, stackalloc);
+		cc.setCursor(cursor);
+		callReturnsAllocated = true;
+	}
+	return callReturns;
+}
+
+void JitCompiler::Setup()
+{
+	using namespace asmjit;
+
+	ResetTemp();
+
+	static const char *marks = "=======================================================";
+	cc.comment("", 0);
+	cc.comment(marks, 56);
+
+	FString funcname;
+	funcname.Format("Function: %s", sfunc->PrintableName.GetChars());
+	cc.comment(funcname.GetChars(), funcname.Len());
+
+	cc.comment(marks, 56);
+	cc.comment("", 0);
+
+	auto unusedFunc = cc.newIntPtr("func"); // VMFunction*
+	args = cc.newIntPtr("args"); // VMValue *params
+	numargs = cc.newInt32("numargs"); // int numargs
+	ret = cc.newIntPtr("ret"); // VMReturn *ret
+	numret = cc.newInt32("numret"); // int numret
+
+	func = cc.addFunc(FuncSignature5<int, VMFunction *, void *, int, void *, int>());
+	cc.setArg(0, unusedFunc);
+	cc.setArg(1, args);
+	cc.setArg(2, numargs);
+	cc.setArg(3, ret);
+	cc.setArg(4, numret);
+
+	callReturnsCursor = cc.getCursor();
+
+	konstd = sfunc->KonstD;
+	konstf = sfunc->KonstF;
+	konsts = sfunc->KonstS;
+	konsta = sfunc->KonstA;
+
+	labels.Resize(sfunc->CodeSize);
+
+	CreateRegisters();
+	IncrementVMCalls();
+	SetupFrame();
+}
+
+void JitCompiler::SetupFrame()
+{
+	// the VM version reads this from the stack, but it is constant data
+	offsetParams = ((int)sizeof(VMFrame) + 15) & ~15;
+	offsetF = offsetParams + (int)(sfunc->MaxParam * sizeof(VMValue));
+	offsetS = offsetF + (int)(sfunc->NumRegF * sizeof(double));
+	offsetA = offsetS + (int)(sfunc->NumRegS * sizeof(FString));
+	offsetD = offsetA + (int)(sfunc->NumRegA * sizeof(void*));
+	offsetExtra = (offsetD + (int)(sfunc->NumRegD * sizeof(int32_t)) + 15) & ~15;
+
+	if (sfunc->SpecialInits.Size() == 0 && sfunc->NumRegS == 0)
+	{
+		SetupSimpleFrame();
+	}
+	else
+	{
+		SetupFullVMFrame();
+	}
+}
+
+void JitCompiler::SetupSimpleFrame()
+{
+	using namespace asmjit;
+
+	// This is a simple frame with no constructors or destructors. Allocate it on the stack ourselves.
+
+	vmframeCursor = cc.getCursor();
+
+	int argsPos = 0;
+	int regd = 0, regf = 0, rega = 0;
+	for (unsigned int i = 0; i < sfunc->Proto->ArgumentTypes.Size(); i++)
+	{
+		const PType *type = sfunc->Proto->ArgumentTypes[i];
+		if (sfunc->ArgFlags.Size() && sfunc->ArgFlags[i] & (VARF_Out | VARF_Ref))
+		{
+			cc.mov(regA[rega++], x86::ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, a)));
+		}
+		else if (type == TypeVector2)
+		{
+			cc.movsd(regF[regf++], x86::qword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, f)));
+			cc.movsd(regF[regf++], x86::qword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, f)));
+		}
+		else if (type == TypeVector3)
+		{
+			cc.movsd(regF[regf++], x86::qword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, f)));
+			cc.movsd(regF[regf++], x86::qword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, f)));
+			cc.movsd(regF[regf++], x86::qword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, f)));
+		}
+		else if (type == TypeFloat64)
+		{
+			cc.movsd(regF[regf++], x86::qword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, f)));
+		}
+		else if (type == TypeString)
+		{
+			I_FatalError("JIT: Strings are not supported yet for simple frames");
+		}
+		else if (type->isIntCompatible())
+		{
+			cc.mov(regD[regd++], x86::dword_ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, i)));
+		}
+		else
+		{
+			cc.mov(regA[rega++], x86::ptr(args, argsPos++ * sizeof(VMValue) + offsetof(VMValue, a)));
+		}
+	}
+
+	if (sfunc->NumArgs != argsPos || regd > sfunc->NumRegD || regf > sfunc->NumRegF || rega > sfunc->NumRegA)
+		I_FatalError("JIT: sfunc->NumArgs != argsPos || regd > sfunc->NumRegD || regf > sfunc->NumRegF || rega > sfunc->NumRegA");
+
+	for (int i = regd; i < sfunc->NumRegD; i++)
+		cc.xor_(regD[i], regD[i]);
+
+	for (int i = regf; i < sfunc->NumRegF; i++)
+		cc.xorpd(regF[i], regF[i]);
+
+	for (int i = rega; i < sfunc->NumRegA; i++)
+		cc.xor_(regA[i], regA[i]);
+}
+
+static VMFrameStack *CreateFullVMFrame(VMScriptFunction *func, VMValue *args, int numargs)
+{
+	VMFrameStack *stack = &GlobalVMStack;
+	VMFrame *newf = stack->AllocFrame(func);
+	VMFillParams(args, newf, numargs);
+	return stack;
+}
+
+void JitCompiler::SetupFullVMFrame()
+{
+	using namespace asmjit;
+
+	stack = cc.newIntPtr("stack");
+	auto allocFrame = CreateCall<VMFrameStack *, VMScriptFunction *, VMValue *, int>(CreateFullVMFrame);
+	allocFrame->setRet(0, stack);
+	allocFrame->setArg(0, imm_ptr(sfunc));
+	allocFrame->setArg(1, args);
+	allocFrame->setArg(2, numargs);
+
+	vmframe = cc.newIntPtr("vmframe");
+	cc.mov(vmframe, x86::ptr(stack)); // stack->Blocks
+	cc.mov(vmframe, x86::ptr(vmframe, VMFrameStack::OffsetLastFrame())); // Blocks->LastFrame
+	vmframeAllocated = true;
+
+	for (int i = 0; i < sfunc->NumRegD; i++)
+		cc.mov(regD[i], x86::dword_ptr(vmframe, offsetD + i * sizeof(int32_t)));
+
+	for (int i = 0; i < sfunc->NumRegF; i++)
+		cc.movsd(regF[i], x86::qword_ptr(vmframe, offsetF + i * sizeof(double)));
+
+	for (int i = 0; i < sfunc->NumRegS; i++)
+		cc.lea(regS[i], x86::ptr(vmframe, offsetS + i * sizeof(FString)));
+
+	for (int i = 0; i < sfunc->NumRegA; i++)
+		cc.mov(regA[i], x86::ptr(vmframe, offsetA + i * sizeof(void*)));
+}
+
+static void PopFullVMFrame(VMFrameStack *stack)
+{
+	stack->PopFrame();
+}
+
+void JitCompiler::EmitPopFrame()
+{
+	if (sfunc->SpecialInits.Size() != 0 || sfunc->NumRegS != 0)
+	{
+		auto popFrame = CreateCall<void, VMFrameStack *>(PopFullVMFrame);
+		popFrame->setArg(0, stack);
+	}
+}
+
+void JitCompiler::IncrementVMCalls()
+{
+	// VMCalls[0]++
+	auto vmcallsptr = newTempIntPtr();
+	auto vmcalls = newTempInt32();
+	cc.mov(vmcallsptr, asmjit::imm_ptr(VMCalls));
+	cc.mov(vmcalls, asmjit::x86::dword_ptr(vmcallsptr));
+	cc.add(vmcalls, (int)1);
+	cc.mov(asmjit::x86::dword_ptr(vmcallsptr), vmcalls);
+}
+
+void JitCompiler::CreateRegisters()
+{
+	regD.Resize(sfunc->NumRegD);
+	regF.Resize(sfunc->NumRegF);
+	regA.Resize(sfunc->NumRegA);
+	regS.Resize(sfunc->NumRegS);
+
+	for (int i = 0; i < sfunc->NumRegD; i++)
+	{
+		regname.Format("regD%d", i);
+		regD[i] = cc.newInt32(regname.GetChars());
+	}
+
+	for (int i = 0; i < sfunc->NumRegF; i++)
+	{
+		regname.Format("regF%d", i);
+		regF[i] = cc.newXmmSd(regname.GetChars());
+	}
+
+	for (int i = 0; i < sfunc->NumRegS; i++)
+	{
+		regname.Format("regS%d", i);
+		regS[i] = cc.newIntPtr(regname.GetChars());
+	}
+
+	for (int i = 0; i < sfunc->NumRegA; i++)
+	{
+		regname.Format("regA%d", i);
+		regA[i] = cc.newIntPtr(regname.GetChars());
+	}
+}
+
+void JitCompiler::EmitNullPointerThrow(int index, EVMAbortException reason)
+{
+	auto label = EmitThrowExceptionLabel(reason);
+	cc.test(regA[index], regA[index]);
+	cc.je(label);
+}
+
+void JitCompiler::ThrowException(int reason)
+{
+	ThrowAbortException((EVMAbortException)reason, nullptr);
+}
+
+void JitCompiler::EmitThrowException(EVMAbortException reason)
+{
+	auto call = CreateCall<void, int>(&JitCompiler::ThrowException);
+	call->setArg(0, asmjit::imm(reason));
+}
+
+asmjit::Label JitCompiler::EmitThrowExceptionLabel(EVMAbortException reason)
+{
+	auto label = cc.newLabel();
+	auto cursor = cc.getCursor();
+	cc.bind(label);
+	EmitThrowException(reason);
+	cc.setCursor(cursor);
+
+	JitLineInfo info;
+	info.Label = label;
+	info.LineNumber = sfunc->PCToLine(pc);
+	LineInfo.Push(info);
+
+	return label;
+}
+
+asmjit::X86Gp JitCompiler::CheckRegD(int r0, int r1)
+{
+	if (r0 != r1)
+	{
+		return regD[r0];
+	}
+	else
+	{
+		auto copy = newTempInt32();
+		cc.mov(copy, regD[r0]);
+		return copy;
+	}
+}
+
+asmjit::X86Xmm JitCompiler::CheckRegF(int r0, int r1)
+{
+	if (r0 != r1)
+	{
+		return regF[r0];
+	}
+	else
+	{
+		auto copy = newTempXmmSd();
+		cc.movsd(copy, regF[r0]);
+		return copy;
+	}
+}
+
+asmjit::X86Xmm JitCompiler::CheckRegF(int r0, int r1, int r2)
+{
+	if (r0 != r1 && r0 != r2)
+	{
+		return regF[r0];
+	}
+	else
+	{
+		auto copy = newTempXmmSd();
+		cc.movsd(copy, regF[r0]);
+		return copy;
+	}
+}
+
+asmjit::X86Xmm JitCompiler::CheckRegF(int r0, int r1, int r2, int r3)
+{
+	if (r0 != r1 && r0 != r2 && r0 != r3)
+	{
+		return regF[r0];
+	}
+	else
+	{
+		auto copy = newTempXmmSd();
+		cc.movsd(copy, regF[r0]);
+		return copy;
+	}
+}
+
+asmjit::X86Gp JitCompiler::CheckRegS(int r0, int r1)
+{
+	if (r0 != r1)
+	{
+		return regS[r0];
+	}
+	else
+	{
+		auto copy = newTempIntPtr();
+		cc.mov(copy, regS[r0]);
+		return copy;
+	}
+}
+
+asmjit::X86Gp JitCompiler::CheckRegA(int r0, int r1)
+{
+	if (r0 != r1)
+	{
+		return regA[r0];
+	}
+	else
+	{
+		auto copy = newTempIntPtr();
+		cc.mov(copy, regA[r0]);
+		return copy;
+	}
+}
+
+void JitCompiler::EmitNOP()
+{
+	cc.nop();
+}
diff --git a/source/common/scripting/jit/jit.h b/source/common/scripting/jit/jit.h
new file mode 100644
index 000000000..faaf65112
--- /dev/null
+++ b/source/common/scripting/jit/jit.h
@@ -0,0 +1,8 @@
+
+#pragma once
+
+#include "vmintern.h"
+
+JitFuncPtr JitCompile(VMScriptFunction *func);
+void JitDumpLog(FILE *file, VMScriptFunction *func);
+FString JitCaptureStackTrace(int framesToSkip, bool includeNativeFrames);
diff --git a/source/common/scripting/jit/jit_call.cpp b/source/common/scripting/jit/jit_call.cpp
new file mode 100644
index 000000000..ee35fc139
--- /dev/null
+++ b/source/common/scripting/jit/jit_call.cpp
@@ -0,0 +1,690 @@
+
+#include "jitintern.h"
+#include <map>
+#include <memory>
+
+void JitCompiler::EmitPARAM()
+{
+	ParamOpcodes.Push(pc);
+}
+
+void JitCompiler::EmitPARAMI()
+{
+	ParamOpcodes.Push(pc);
+}
+
+void JitCompiler::EmitRESULT()
+{
+	// 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.
+}
+
+void JitCompiler::EmitVTBL()
+{
+	// This instruction is handled in the CALL/CALL_K instruction following it
+}
+
+void JitCompiler::EmitVtbl(const VMOP *op)
+{
+	int a = op->a;
+	int b = op->b;
+	int c = op->c;
+
+	auto label = EmitThrowExceptionLabel(X_READ_NIL);
+	cc.test(regA[b], regA[b]);
+	cc.jz(label);
+
+	cc.mov(regA[a], asmjit::x86::qword_ptr(regA[b], myoffsetof(DObject, Class)));
+	cc.mov(regA[a], asmjit::x86::qword_ptr(regA[a], myoffsetof(PClass, Virtuals) + myoffsetof(FArray, Array)));
+	cc.mov(regA[a], asmjit::x86::qword_ptr(regA[a], c * (int)sizeof(void*)));
+}
+
+void JitCompiler::EmitCALL()
+{
+	EmitVMCall(regA[A], nullptr);
+	pc += C; // Skip RESULTs
+}
+
+void JitCompiler::EmitCALL_K()
+{
+	VMFunction *target = static_cast<VMFunction*>(konsta[A].v);
+
+	VMNativeFunction *ntarget = nullptr;
+	if (target && (target->VarFlags & VARF_Native))
+		ntarget = static_cast<VMNativeFunction *>(target);
+
+	if (ntarget && ntarget->DirectNativeCall)
+	{
+		EmitNativeCall(ntarget);
+	}
+	else
+	{
+		auto ptr = newTempIntPtr();
+		cc.mov(ptr, asmjit::imm_ptr(target));
+		EmitVMCall(ptr, target);
+	}
+
+	pc += C; // Skip RESULTs
+}
+
+void JitCompiler::EmitVMCall(asmjit::X86Gp vmfunc, VMFunction *target)
+{
+	using namespace asmjit;
+
+	CheckVMFrame();
+
+	int numparams = StoreCallParams();
+	if (numparams != B)
+		I_Error("OP_CALL parameter count does not match the number of preceding OP_PARAM instructions");
+
+	if (pc > sfunc->Code && (pc - 1)->op == OP_VTBL)
+		EmitVtbl(pc - 1);
+
+	FillReturns(pc + 1, C);
+
+	X86Gp paramsptr = newTempIntPtr();
+	cc.lea(paramsptr, x86::ptr(vmframe, offsetParams));
+
+	auto scriptcall = newTempIntPtr();
+	cc.mov(scriptcall, x86::ptr(vmfunc, myoffsetof(VMScriptFunction, ScriptCall)));
+
+	auto result = newResultInt32();
+	auto call = cc.call(scriptcall, FuncSignature5<int, VMFunction *, VMValue*, int, VMReturn*, int>());
+	call->setRet(0, result);
+	call->setArg(0, vmfunc);
+	call->setArg(1, paramsptr);
+	call->setArg(2, Imm(B));
+	call->setArg(3, GetCallReturns());
+	call->setArg(4, Imm(C));
+	call->setInlineComment(target ? target->PrintableName.GetChars() : "VMCall");
+
+	LoadInOuts();
+	LoadReturns(pc + 1, C);
+
+	ParamOpcodes.Clear();
+}
+
+int JitCompiler::StoreCallParams()
+{
+	using namespace asmjit;
+
+	X86Gp stackPtr = newTempIntPtr();
+	X86Gp tmp = newTempIntPtr();
+	X86Xmm tmp2 = newTempXmmSd();
+
+	int numparams = 0;
+	for (unsigned int i = 0; i < ParamOpcodes.Size(); i++)
+	{
+		int slot = numparams++;
+
+		if (ParamOpcodes[i]->op == OP_PARAMI)
+		{
+			int abcs = ParamOpcodes[i]->i24;
+			cc.mov(asmjit::x86::dword_ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, i)), abcs);
+			continue;
+		}
+
+		int bc = ParamOpcodes[i]->i16u;
+
+		switch (ParamOpcodes[i]->a)
+		{
+		case REGT_NIL:
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), (int64_t)0);
+			break;
+		case REGT_INT:
+			cc.mov(x86::dword_ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, i)), regD[bc]);
+			break;
+		case REGT_INT | REGT_ADDROF:
+			cc.lea(stackPtr, x86::ptr(vmframe, offsetD + (int)(bc * sizeof(int32_t))));
+			cc.mov(x86::dword_ptr(stackPtr), regD[bc]);
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), stackPtr);
+			break;
+		case REGT_INT | REGT_KONST:
+			cc.mov(x86::dword_ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, i)), konstd[bc]);
+			break;
+		case REGT_STRING:
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, sp)), regS[bc]);
+			break;
+		case REGT_STRING | REGT_ADDROF:
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), regS[bc]);
+			break;
+		case REGT_STRING | REGT_KONST:
+			cc.mov(tmp, asmjit::imm_ptr(&konsts[bc]));
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, sp)), tmp);
+			break;
+		case REGT_POINTER:
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), regA[bc]);
+			break;
+		case REGT_POINTER | REGT_ADDROF:
+			cc.lea(stackPtr, x86::ptr(vmframe, offsetA + (int)(bc * sizeof(void*))));
+			cc.mov(x86::ptr(stackPtr), regA[bc]);
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), stackPtr);
+			break;
+		case REGT_POINTER | REGT_KONST:
+			cc.mov(tmp, asmjit::imm_ptr(konsta[bc].v));
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), tmp);
+			break;
+		case REGT_FLOAT:
+			cc.movsd(x86::qword_ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, f)), regF[bc]);
+			break;
+		case REGT_FLOAT | REGT_MULTIREG2:
+			for (int j = 0; j < 2; j++)
+			{
+				cc.movsd(x86::qword_ptr(vmframe, offsetParams + (slot + j) * sizeof(VMValue) + myoffsetof(VMValue, f)), regF[bc + j]);
+			}
+			numparams++;
+			break;
+		case REGT_FLOAT | REGT_MULTIREG3:
+			for (int j = 0; j < 3; j++)
+			{
+				cc.movsd(x86::qword_ptr(vmframe, offsetParams + (slot + j) * sizeof(VMValue) + myoffsetof(VMValue, f)), regF[bc + j]);
+			}
+			numparams += 2;
+			break;
+		case REGT_FLOAT | REGT_ADDROF:
+			cc.lea(stackPtr, x86::ptr(vmframe, offsetF + (int)(bc * sizeof(double))));
+			// When passing the address to a float we don't know if the receiving function will treat it as float, vec2 or vec3.
+			for (int j = 0; j < 3; j++)
+			{
+				if ((unsigned int)(bc + j) < regF.Size())
+					cc.movsd(x86::qword_ptr(stackPtr, j * sizeof(double)), regF[bc + j]);
+			}
+			cc.mov(x86::ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, a)), stackPtr);
+			break;
+		case REGT_FLOAT | REGT_KONST:
+			cc.mov(tmp, asmjit::imm_ptr(konstf + bc));
+			cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp));
+			cc.movsd(x86::qword_ptr(vmframe, offsetParams + slot * sizeof(VMValue) + myoffsetof(VMValue, f)), tmp2);
+			break;
+
+		default:
+			I_Error("Unknown REGT value passed to EmitPARAM\n");
+			break;
+		}
+	}
+
+	return numparams;
+}
+
+void JitCompiler::LoadInOuts()
+{
+	for (unsigned int i = 0; i < ParamOpcodes.Size(); i++)
+	{
+		const VMOP &param = *ParamOpcodes[i];
+		if (param.op == OP_PARAM && (param.a & REGT_ADDROF))
+		{
+			LoadCallResult(param.a, param.i16u, true);
+		}
+	}
+}
+
+void JitCompiler::LoadReturns(const VMOP *retval, int numret)
+{
+	for (int i = 0; i < numret; ++i)
+	{
+		if (retval[i].op != OP_RESULT)
+			I_Error("Expected OP_RESULT to follow OP_CALL\n");
+
+		LoadCallResult(retval[i].b, retval[i].c, false);
+	}
+}
+
+void JitCompiler::LoadCallResult(int type, int regnum, bool addrof)
+{
+	switch (type & REGT_TYPE)
+	{
+	case REGT_INT:
+		cc.mov(regD[regnum], asmjit::x86::dword_ptr(vmframe, offsetD + regnum * sizeof(int32_t)));
+		break;
+	case REGT_FLOAT:
+		cc.movsd(regF[regnum], asmjit::x86::qword_ptr(vmframe, offsetF + regnum * sizeof(double)));
+		if (addrof)
+		{
+			// When passing the address to a float we don't know if the receiving function will treat it as float, vec2 or vec3.
+			if ((unsigned int)regnum + 1 < regF.Size())
+				cc.movsd(regF[regnum + 1], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 1) * sizeof(double)));
+			if ((unsigned int)regnum + 2 < regF.Size())
+				cc.movsd(regF[regnum + 2], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 2) * sizeof(double)));
+		}
+		else if (type & REGT_MULTIREG2)
+		{
+			cc.movsd(regF[regnum + 1], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 1) * sizeof(double)));
+		}
+		else if (type & REGT_MULTIREG3)
+		{
+			cc.movsd(regF[regnum + 1], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 1) * sizeof(double)));
+			cc.movsd(regF[regnum + 2], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 2) * sizeof(double)));
+		}
+		break;
+	case REGT_STRING:
+		// We don't have to do anything in this case. String values are never moved to virtual registers.
+		break;
+	case REGT_POINTER:
+		cc.mov(regA[regnum], asmjit::x86::ptr(vmframe, offsetA + regnum * sizeof(void*)));
+		break;
+	default:
+		I_Error("Unknown OP_RESULT/OP_PARAM type encountered in LoadCallResult\n");
+		break;
+	}
+}
+
+void JitCompiler::FillReturns(const VMOP *retval, int numret)
+{
+	using namespace asmjit;
+
+	for (int i = 0; i < numret; ++i)
+	{
+		if (retval[i].op != OP_RESULT)
+		{
+			I_Error("Expected OP_RESULT to follow OP_CALL\n");
+		}
+
+		int type = retval[i].b;
+		int regnum = retval[i].c;
+
+		if (type & REGT_KONST)
+		{
+			I_Error("OP_RESULT with REGT_KONST is not allowed\n");
+		}
+
+		auto regPtr = newTempIntPtr();
+
+		switch (type & REGT_TYPE)
+		{
+		case REGT_INT:
+			cc.lea(regPtr, x86::ptr(vmframe, offsetD + (int)(regnum * sizeof(int32_t))));
+			break;
+		case REGT_FLOAT:
+			cc.lea(regPtr, x86::ptr(vmframe, offsetF + (int)(regnum * sizeof(double))));
+			break;
+		case REGT_STRING:
+			cc.lea(regPtr, x86::ptr(vmframe, offsetS + (int)(regnum * sizeof(FString))));
+			break;
+		case REGT_POINTER:
+			cc.lea(regPtr, x86::ptr(vmframe, offsetA + (int)(regnum * sizeof(void*))));
+			break;
+		default:
+			I_Error("Unknown OP_RESULT type encountered in FillReturns\n");
+			break;
+		}
+
+		cc.mov(x86::ptr(GetCallReturns(), i * sizeof(VMReturn) + myoffsetof(VMReturn, Location)), regPtr);
+		cc.mov(x86::byte_ptr(GetCallReturns(), i * sizeof(VMReturn) + myoffsetof(VMReturn, RegType)), type);
+	}
+}
+
+void JitCompiler::EmitNativeCall(VMNativeFunction *target)
+{
+	using namespace asmjit;
+
+	if (pc > sfunc->Code && (pc - 1)->op == OP_VTBL)
+	{
+		I_Error("Native direct member function calls not implemented\n");
+	}
+
+	if (target->ImplicitArgs > 0)
+	{
+		auto label = EmitThrowExceptionLabel(X_READ_NIL);
+
+		assert(ParamOpcodes.Size() > 0);
+		const VMOP *param = ParamOpcodes[0];
+		const int bc = param->i16u;
+		asmjit::X86Gp *reg = nullptr;
+
+		switch (param->a & REGT_TYPE)
+		{
+		case REGT_STRING:  reg = &regS[bc]; break;
+		case REGT_POINTER: reg = &regA[bc]; break;
+		default:
+			I_Error("Unexpected register type for self pointer\n");
+			break;
+		}
+		
+		cc.test(*reg, *reg);
+		cc.jz(label);
+	}
+
+	asmjit::CBNode *cursorBefore = cc.getCursor();
+	auto call = cc.call(imm_ptr(target->DirectNativeCall), CreateFuncSignature());
+	call->setInlineComment(target->PrintableName.GetChars());
+	asmjit::CBNode *cursorAfter = cc.getCursor();
+	cc.setCursor(cursorBefore);
+
+	X86Gp tmp;
+	X86Xmm tmp2;
+
+	int numparams = 0;
+	for (unsigned int i = 0; i < ParamOpcodes.Size(); i++)
+	{
+		int slot = numparams++;
+
+		if (ParamOpcodes[i]->op == OP_PARAMI)
+		{
+			int abcs = ParamOpcodes[i]->i24;
+			call->setArg(slot, imm(abcs));
+		}
+		else // OP_PARAM
+		{
+			int bc = ParamOpcodes[i]->i16u;
+			switch (ParamOpcodes[i]->a)
+			{
+			case REGT_NIL:
+				call->setArg(slot, imm(0));
+				break;
+			case REGT_INT:
+				call->setArg(slot, regD[bc]);
+				break;
+			case REGT_INT | REGT_KONST:
+				call->setArg(slot, imm(konstd[bc]));
+				break;
+			case REGT_STRING | REGT_ADDROF:	// AddrOf string is essentially the same - a reference to the register, just not constant on the receiving side.
+			case REGT_STRING:
+				call->setArg(slot, regS[bc]);
+				break;
+			case REGT_STRING | REGT_KONST:
+				tmp = newTempIntPtr();
+				cc.mov(tmp, imm_ptr(&konsts[bc]));
+				call->setArg(slot, tmp);
+				break;
+			case REGT_POINTER:
+				call->setArg(slot, regA[bc]);
+				break;
+			case REGT_POINTER | REGT_KONST:
+				tmp = newTempIntPtr();
+				cc.mov(tmp, imm_ptr(konsta[bc].v));
+				call->setArg(slot, tmp);
+				break;
+			case REGT_FLOAT:
+				call->setArg(slot, regF[bc]);
+				break;
+			case REGT_FLOAT | REGT_MULTIREG2:
+				for (int j = 0; j < 2; j++)
+					call->setArg(slot + j, regF[bc + j]);
+				numparams++;
+				break;
+			case REGT_FLOAT | REGT_MULTIREG3:
+				for (int j = 0; j < 3; j++)
+					call->setArg(slot + j, regF[bc + j]);
+				numparams += 2;
+				break;
+			case REGT_FLOAT | REGT_KONST:
+				tmp = newTempIntPtr();
+				tmp2 = newTempXmmSd();
+				cc.mov(tmp, asmjit::imm_ptr(konstf + bc));
+				cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp));
+				call->setArg(slot, tmp2);
+				break;
+
+			case REGT_INT | REGT_ADDROF:
+			case REGT_POINTER | REGT_ADDROF:
+			case REGT_FLOAT | REGT_ADDROF:
+				I_Error("REGT_ADDROF not implemented for native direct calls\n");
+				break;
+
+			default:
+				I_Error("Unknown REGT value passed to EmitPARAM\n");
+				break;
+			}
+		}
+	}
+
+	if (numparams != B)
+		I_Error("OP_CALL parameter count does not match the number of preceding OP_PARAM instructions\n");
+
+	// Note: the usage of newResultXX is intentional. Asmjit has a register allocation bug
+	// if the return virtual register is already allocated in an argument slot.
+
+	const VMOP *retval = pc + 1;
+	int numret = C;
+
+	// Check if first return value was placed in the function's real return value slot
+	int startret = 1;
+	if (numret > 0)
+	{
+		int type = retval[0].b;
+		switch (type)
+		{
+		case REGT_INT:
+		case REGT_FLOAT:
+		case REGT_POINTER:
+			break;
+		default:
+			startret = 0;
+			break;
+		}
+	}
+
+	// Pass return pointers as arguments
+	for (int i = startret; i < numret; ++i)
+	{
+		int type = retval[i].b;
+		int regnum = retval[i].c;
+
+		if (type & REGT_KONST)
+		{
+			I_Error("OP_RESULT with REGT_KONST is not allowed\n");
+		}
+
+		CheckVMFrame();
+
+		if ((type & REGT_TYPE) == REGT_STRING)
+		{
+			// For strings we already have them on the stack and got named registers for them.
+			call->setArg(numparams + i - startret, regS[regnum]);
+		}
+		else
+		{
+			auto regPtr = newTempIntPtr();
+
+			switch (type & REGT_TYPE)
+			{
+			case REGT_INT:
+				cc.lea(regPtr, x86::ptr(vmframe, offsetD + (int)(regnum * sizeof(int32_t))));
+				break;
+			case REGT_FLOAT:
+				cc.lea(regPtr, x86::ptr(vmframe, offsetF + (int)(regnum * sizeof(double))));
+				break;
+			case REGT_STRING:
+				cc.lea(regPtr, x86::ptr(vmframe, offsetS + (int)(regnum * sizeof(FString))));
+				break;
+			case REGT_POINTER:
+				cc.lea(regPtr, x86::ptr(vmframe, offsetA + (int)(regnum * sizeof(void*))));
+				break;
+			default:
+				I_Error("Unknown OP_RESULT type encountered\n");
+				break;
+			}
+
+			call->setArg(numparams + i - startret, regPtr);
+		}
+	}
+
+	cc.setCursor(cursorAfter);
+
+	if (startret == 1 && numret > 0)
+	{
+		int type = retval[0].b;
+		int regnum = retval[0].c;
+
+		switch (type)
+		{
+		case REGT_INT:
+			tmp = newResultInt32();
+			call->setRet(0, tmp);
+			cc.mov(regD[regnum], tmp);
+			break;
+		case REGT_FLOAT:
+			tmp2 = newResultXmmSd();
+			call->setRet(0, tmp2);
+			cc.movsd(regF[regnum], tmp2);
+			break;
+		case REGT_POINTER:
+			tmp = newResultIntPtr();
+			call->setRet(0, tmp);
+			cc.mov(regA[regnum], tmp);
+			break;
+		}
+	}
+
+	// Move the result into virtual registers
+	for (int i = startret; i < numret; ++i)
+	{
+		int type = retval[i].b;
+		int regnum = retval[i].c;
+
+		switch (type)
+		{
+		case REGT_INT:
+			cc.mov(regD[regnum], asmjit::x86::dword_ptr(vmframe, offsetD + regnum * sizeof(int32_t)));
+			break;
+		case REGT_FLOAT:
+			cc.movsd(regF[regnum], asmjit::x86::qword_ptr(vmframe, offsetF + regnum * sizeof(double)));
+			break;
+		case REGT_FLOAT | REGT_MULTIREG2:
+			cc.movsd(regF[regnum], asmjit::x86::qword_ptr(vmframe, offsetF + regnum * sizeof(double)));
+			cc.movsd(regF[regnum + 1], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 1) * sizeof(double)));
+			break;
+		case REGT_FLOAT | REGT_MULTIREG3:
+			cc.movsd(regF[regnum], asmjit::x86::qword_ptr(vmframe, offsetF + regnum * sizeof(double)));
+			cc.movsd(regF[regnum + 1], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 1) * sizeof(double)));
+			cc.movsd(regF[regnum + 2], asmjit::x86::qword_ptr(vmframe, offsetF + (regnum + 2) * sizeof(double)));
+			break;
+		case REGT_STRING:
+			// We don't have to do anything in this case. String values are never moved to virtual registers.
+			break;
+		case REGT_POINTER:
+			cc.mov(regA[regnum], asmjit::x86::ptr(vmframe, offsetA + regnum * sizeof(void*)));
+			break;
+		default:
+			I_Error("Unknown OP_RESULT type encountered\n");
+			break;
+		}
+	}
+
+	ParamOpcodes.Clear();
+}
+
+static std::map<FString, std::unique_ptr<TArray<uint8_t>>> argsCache;
+
+asmjit::FuncSignature JitCompiler::CreateFuncSignature()
+{
+	using namespace asmjit;
+
+	TArray<uint8_t> args;
+	FString key;
+
+	// First add parameters as args to the signature
+
+	for (unsigned int i = 0; i < ParamOpcodes.Size(); i++)
+	{
+		if (ParamOpcodes[i]->op == OP_PARAMI)
+		{
+			args.Push(TypeIdOf<int>::kTypeId);
+			key += "i";
+		}
+		else // OP_PARAM
+		{
+			int bc = ParamOpcodes[i]->i16u;
+			switch (ParamOpcodes[i]->a)
+			{
+			case REGT_NIL:
+			case REGT_POINTER:
+			case REGT_POINTER | REGT_KONST:
+			case REGT_STRING | REGT_ADDROF:
+			case REGT_INT | REGT_ADDROF:
+			case REGT_POINTER | REGT_ADDROF:
+			case REGT_FLOAT | REGT_ADDROF:
+				args.Push(TypeIdOf<void*>::kTypeId);
+				key += "v";
+				break;
+			case REGT_INT:
+			case REGT_INT | REGT_KONST:
+				args.Push(TypeIdOf<int>::kTypeId);
+				key += "i";
+				break;
+			case REGT_STRING:
+			case REGT_STRING | REGT_KONST:
+				args.Push(TypeIdOf<void*>::kTypeId);
+				key += "s";
+				break;
+			case REGT_FLOAT:
+			case REGT_FLOAT | REGT_KONST:
+				args.Push(TypeIdOf<double>::kTypeId);
+				key += "f";
+				break;
+			case REGT_FLOAT | REGT_MULTIREG2:
+				args.Push(TypeIdOf<double>::kTypeId);
+				args.Push(TypeIdOf<double>::kTypeId);
+				key += "ff";
+				break;
+			case REGT_FLOAT | REGT_MULTIREG3:
+				args.Push(TypeIdOf<double>::kTypeId);
+				args.Push(TypeIdOf<double>::kTypeId);
+				args.Push(TypeIdOf<double>::kTypeId);
+				key += "fff";
+				break;
+
+			default:
+				I_Error("Unknown REGT value passed to EmitPARAM\n");
+				break;
+			}
+		}
+	}
+
+	const VMOP *retval = pc + 1;
+	int numret = C;
+
+	uint32_t rettype = TypeIdOf<void>::kTypeId;
+
+	// Check if first return value can be placed in the function's real return value slot
+	int startret = 1;
+	if (numret > 0)
+	{
+		if (retval[0].op != OP_RESULT)
+		{
+			I_Error("Expected OP_RESULT to follow OP_CALL\n");
+		}
+
+		int type = retval[0].b;
+		switch (type)
+		{
+		case REGT_INT:
+			rettype = TypeIdOf<int>::kTypeId;
+			key += "ri";
+			break;
+		case REGT_FLOAT:
+			rettype = TypeIdOf<double>::kTypeId;
+			key += "rf";
+			break;
+		case REGT_POINTER:
+			rettype = TypeIdOf<void*>::kTypeId;
+			key += "rv";
+			break;
+		case REGT_STRING:
+		default:
+			startret = 0;
+			break;
+		}
+	}
+
+	// Add any additional return values as function arguments
+	for (int i = startret; i < numret; ++i)
+	{
+		if (retval[i].op != OP_RESULT)
+		{
+			I_Error("Expected OP_RESULT to follow OP_CALL\n");
+		}
+
+		args.Push(TypeIdOf<void*>::kTypeId);
+		key += "v";
+	}
+
+	// FuncSignature only keeps a pointer to its args array. Store a copy of each args array variant.
+	std::unique_ptr<TArray<uint8_t>> &cachedArgs = argsCache[key];
+	if (!cachedArgs) cachedArgs.reset(new TArray<uint8_t>(args));
+
+	FuncSignature signature;
+	signature.init(CallConv::kIdHost, rettype, cachedArgs->Data(), cachedArgs->Size());
+	return signature;
+}
diff --git a/source/common/scripting/jit/jit_flow.cpp b/source/common/scripting/jit/jit_flow.cpp
new file mode 100644
index 000000000..5db91818a
--- /dev/null
+++ b/source/common/scripting/jit/jit_flow.cpp
@@ -0,0 +1,319 @@
+
+#include "jitintern.h"
+
+void JitCompiler::EmitTEST()
+{
+	int i = (int)(ptrdiff_t)(pc - sfunc->Code);
+	cc.cmp(regD[A], BC);
+	cc.jne(GetLabel(i + 2));
+}
+	
+void JitCompiler::EmitTESTN()
+{
+	int bc = BC;
+	int i = (int)(ptrdiff_t)(pc - sfunc->Code);
+	cc.cmp(regD[A], -bc);
+	cc.jne(GetLabel(i + 2));
+}
+
+void JitCompiler::EmitJMP()
+{
+	auto dest = pc + JMPOFS(pc) + 1;
+	int i = (int)(ptrdiff_t)(dest - sfunc->Code);
+	cc.jmp(GetLabel(i));
+}
+
+void JitCompiler::EmitIJMP()
+{
+	int base = (int)(ptrdiff_t)(pc - sfunc->Code) + 1;
+	auto val = newTempInt32();
+	cc.mov(val, regD[A]);
+
+	for (int i = 0; i < (int)BCs; i++)
+	{
+		if (sfunc->Code[base +i].op == OP_JMP)
+		{
+			int target = base + i + JMPOFS(&sfunc->Code[base + i]) + 1;
+
+			cc.cmp(val, i);
+			cc.je(GetLabel(target));
+		}
+	}
+	pc += BCs;
+
+	// This should never happen. It means we are jumping to something that is not a JMP instruction!
+	EmitThrowException(X_OTHER);
+}
+
+static void ValidateCall(DObject *o, VMFunction *f, int b)
+{
+	FScopeBarrier::ValidateCall(o->GetClass(), f, b - 1);
+}
+
+void JitCompiler::EmitSCOPE()
+{
+	auto label = EmitThrowExceptionLabel(X_READ_NIL);
+	cc.test(regA[A], regA[A]);
+	cc.jz(label);
+
+	auto f = newTempIntPtr();
+	cc.mov(f, asmjit::imm_ptr(konsta[C].v));
+
+	typedef int(*FuncPtr)(DObject*, VMFunction*, int);
+	auto call = CreateCall<void, DObject*, VMFunction*, int>(ValidateCall);
+	call->setArg(0, regA[A]);
+	call->setArg(1, f);
+	call->setArg(2, asmjit::Imm(B));
+}
+
+static void SetString(VMReturn* ret, FString* str)
+{
+	ret->SetString(*str);
+}
+
+void JitCompiler::EmitRET()
+{
+	using namespace asmjit;
+	if (B == REGT_NIL)
+	{
+		EmitPopFrame();
+		X86Gp vReg = newTempInt32();
+		cc.mov(vReg, 0);
+		cc.ret(vReg);
+	}
+	else
+	{
+		int a = A;
+		int retnum = a & ~RET_FINAL;
+
+		X86Gp reg_retnum = newTempInt32();
+		X86Gp location = newTempIntPtr();
+		Label L_endif = cc.newLabel();
+
+		cc.mov(reg_retnum, retnum);
+		cc.cmp(reg_retnum, numret);
+		cc.jge(L_endif);
+
+		cc.mov(location, x86::ptr(ret, retnum * sizeof(VMReturn)));
+
+		int regtype = B;
+		int regnum = C;
+		switch (regtype & REGT_TYPE)
+		{
+		case REGT_INT:
+			if (regtype & REGT_KONST)
+				cc.mov(x86::dword_ptr(location), konstd[regnum]);
+			else
+				cc.mov(x86::dword_ptr(location), regD[regnum]);
+			break;
+		case REGT_FLOAT:
+			if (regtype & REGT_KONST)
+			{
+				auto tmp = newTempInt64();
+				if (regtype & REGT_MULTIREG3)
+				{
+					cc.mov(tmp, (((int64_t *)konstf)[regnum]));
+					cc.mov(x86::qword_ptr(location), tmp);
+
+					cc.mov(tmp, (((int64_t *)konstf)[regnum + 1]));
+					cc.mov(x86::qword_ptr(location, 8), tmp);
+
+					cc.mov(tmp, (((int64_t *)konstf)[regnum + 2]));
+					cc.mov(x86::qword_ptr(location, 16), tmp);
+				}
+				else if (regtype & REGT_MULTIREG2)
+				{
+					cc.mov(tmp, (((int64_t *)konstf)[regnum]));
+					cc.mov(x86::qword_ptr(location), tmp);
+
+					cc.mov(tmp, (((int64_t *)konstf)[regnum + 1]));
+					cc.mov(x86::qword_ptr(location, 8), tmp);
+				}
+				else
+				{
+					cc.mov(tmp, (((int64_t *)konstf)[regnum]));
+					cc.mov(x86::qword_ptr(location), tmp);
+				}
+			}
+			else
+			{
+				if (regtype & REGT_MULTIREG3)
+				{
+					cc.movsd(x86::qword_ptr(location), regF[regnum]);
+					cc.movsd(x86::qword_ptr(location, 8), regF[regnum + 1]);
+					cc.movsd(x86::qword_ptr(location, 16), regF[regnum + 2]);
+				}
+				else if (regtype & REGT_MULTIREG2)
+				{
+					cc.movsd(x86::qword_ptr(location), regF[regnum]);
+					cc.movsd(x86::qword_ptr(location, 8), regF[regnum + 1]);
+				}
+				else
+				{
+					cc.movsd(x86::qword_ptr(location), regF[regnum]);
+				}
+			}
+			break;
+		case REGT_STRING:
+		{
+			auto ptr = newTempIntPtr();
+			cc.mov(ptr, ret);
+			cc.add(ptr, (int)(retnum * sizeof(VMReturn)));
+			auto call = CreateCall<void, VMReturn*, FString*>(SetString);
+			call->setArg(0, ptr);
+			if (regtype & REGT_KONST) call->setArg(1, asmjit::imm_ptr(&konsts[regnum]));
+			else                      call->setArg(1, regS[regnum]);
+			break;
+		}
+		case REGT_POINTER:
+			if (cc.is64Bit())
+			{
+				if (regtype & REGT_KONST)
+				{
+					auto ptr = newTempIntPtr();
+					cc.mov(ptr, asmjit::imm_ptr(konsta[regnum].v));
+					cc.mov(x86::qword_ptr(location), ptr);
+				}
+				else
+				{
+					cc.mov(x86::qword_ptr(location), regA[regnum]);
+				}
+			}
+			else
+			{
+				if (regtype & REGT_KONST)
+				{
+					auto ptr = newTempIntPtr();
+					cc.mov(ptr, asmjit::imm_ptr(konsta[regnum].v));
+					cc.mov(x86::dword_ptr(location), ptr);
+				}
+				else
+				{
+					cc.mov(x86::dword_ptr(location), regA[regnum]);
+				}
+			}
+			break;
+		}
+
+		if (a & RET_FINAL)
+		{
+			cc.add(reg_retnum, 1);
+			EmitPopFrame();
+			cc.ret(reg_retnum);
+		}
+
+		cc.bind(L_endif);
+		if (a & RET_FINAL)
+		{
+			EmitPopFrame();
+			cc.ret(numret);
+		}
+	}
+}
+
+void JitCompiler::EmitRETI()
+{
+	using namespace asmjit;
+
+	int a = A;
+	int retnum = a & ~RET_FINAL;
+
+	X86Gp reg_retnum = newTempInt32();
+	X86Gp location = newTempIntPtr();
+	Label L_endif = cc.newLabel();
+
+	cc.mov(reg_retnum, retnum);
+	cc.cmp(reg_retnum, numret);
+	cc.jge(L_endif);
+
+	cc.mov(location, x86::ptr(ret, retnum * sizeof(VMReturn)));
+	cc.mov(x86::dword_ptr(location), BCs);
+
+	if (a & RET_FINAL)
+	{
+		cc.add(reg_retnum, 1);
+		EmitPopFrame();
+		cc.ret(reg_retnum);
+	}
+
+	cc.bind(L_endif);
+	if (a & RET_FINAL)
+	{
+		EmitPopFrame();
+		cc.ret(numret);
+	}
+}
+
+void JitCompiler::EmitTHROW()
+{
+	EmitThrowException(EVMAbortException(BC));
+}
+
+void JitCompiler::EmitBOUND()
+{
+	auto cursor = cc.getCursor();
+	auto label = cc.newLabel();
+	cc.bind(label);
+	auto call = CreateCall<void, int, int>(&JitCompiler::ThrowArrayOutOfBounds);
+	call->setArg(0, regD[A]);
+	call->setArg(1, asmjit::imm(BC));
+	cc.setCursor(cursor);
+
+	cc.cmp(regD[A], (int)BC);
+	cc.jae(label);
+
+	JitLineInfo info;
+	info.Label = label;
+	info.LineNumber = sfunc->PCToLine(pc);
+	LineInfo.Push(info);
+}
+
+void JitCompiler::EmitBOUND_K()
+{
+	auto cursor = cc.getCursor();
+	auto label = cc.newLabel();
+	cc.bind(label);
+	auto call = CreateCall<void, int, int>(&JitCompiler::ThrowArrayOutOfBounds);
+	call->setArg(0, regD[A]);
+	call->setArg(1, asmjit::imm(konstd[BC]));
+	cc.setCursor(cursor);
+
+	cc.cmp(regD[A], (int)konstd[BC]);
+	cc.jae(label);
+
+	JitLineInfo info;
+	info.Label = label;
+	info.LineNumber = sfunc->PCToLine(pc);
+	LineInfo.Push(info);
+}
+
+void JitCompiler::EmitBOUND_R()
+{
+	auto cursor = cc.getCursor();
+	auto label = cc.newLabel();
+	cc.bind(label);
+	auto call = CreateCall<void, int, int>(&JitCompiler::ThrowArrayOutOfBounds);
+	call->setArg(0, regD[A]);
+	call->setArg(1, regD[B]);
+	cc.setCursor(cursor);
+
+	cc.cmp(regD[A], regD[B]);
+	cc.jae(label);
+
+	JitLineInfo info;
+	info.Label = label;
+	info.LineNumber = sfunc->PCToLine(pc);
+	LineInfo.Push(info);
+}
+
+void JitCompiler::ThrowArrayOutOfBounds(int index, int size)
+{
+	if (index >= size)
+	{
+		ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", size, index);
+	}
+	else
+	{
+		ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", index);
+	}
+}
diff --git a/source/common/scripting/jit/jit_load.cpp b/source/common/scripting/jit/jit_load.cpp
new file mode 100644
index 000000000..0881cfd21
--- /dev/null
+++ b/source/common/scripting/jit/jit_load.cpp
@@ -0,0 +1,360 @@
+
+#include "jitintern.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// Load constants.
+
+void JitCompiler::EmitLI()
+{
+	cc.mov(regD[A], BCs);
+}
+
+void JitCompiler::EmitLK()
+{
+	cc.mov(regD[A], konstd[BC]);
+}
+
+void JitCompiler::EmitLKF()
+{
+	auto base = newTempIntPtr();
+	cc.mov(base, asmjit::imm_ptr(konstf + BC));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(base));
+}
+
+void JitCompiler::EmitLKS()
+{
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, asmjit::imm_ptr(konsts + BC));
+}
+
+void JitCompiler::EmitLKP()
+{
+	cc.mov(regA[A], (int64_t)konsta[BC].v);
+}
+
+void JitCompiler::EmitLK_R()
+{
+	auto base = newTempIntPtr();
+	cc.mov(base, asmjit::imm_ptr(konstd + C));
+	cc.mov(regD[A], asmjit::x86::ptr(base, regD[B], 2));
+}
+
+void JitCompiler::EmitLKF_R()
+{
+	auto base = newTempIntPtr();
+	cc.mov(base, asmjit::imm_ptr(konstf + C));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(base, regD[B], 3));
+}
+
+void JitCompiler::EmitLKS_R()
+{
+	auto base = newTempIntPtr();
+	cc.mov(base, asmjit::imm_ptr(konsts + C));
+	auto ptr = newTempIntPtr();
+	if (cc.is64Bit())
+		cc.lea(ptr, asmjit::x86::ptr(base, regD[B], 3));
+	else
+		cc.lea(ptr, asmjit::x86::ptr(base, regD[B], 2));
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, ptr);
+}
+
+void JitCompiler::EmitLKP_R()
+{
+	auto base = newTempIntPtr();
+	cc.mov(base, asmjit::imm_ptr(konsta + C));
+	if (cc.is64Bit())
+		cc.mov(regA[A], asmjit::x86::ptr(base, regD[B], 3));
+	else
+		cc.mov(regA[A], asmjit::x86::ptr(base, regD[B], 2));
+}
+
+void JitCompiler::EmitLFP()
+{
+	CheckVMFrame();
+	cc.lea(regA[A], asmjit::x86::ptr(vmframe, offsetExtra));
+}
+
+void JitCompiler::EmitMETA()
+{
+	auto label = EmitThrowExceptionLabel(X_READ_NIL);
+	cc.test(regA[B], regA[B]);
+	cc.je(label);
+
+	cc.mov(regA[A], asmjit::x86::qword_ptr(regA[B], myoffsetof(DObject, Class)));
+	cc.mov(regA[A], asmjit::x86::qword_ptr(regA[A], myoffsetof(PClass, Meta)));
+}
+
+void JitCompiler::EmitCLSS()
+{
+	auto label = EmitThrowExceptionLabel(X_READ_NIL);
+	cc.test(regA[B], regA[B]);
+	cc.je(label);
+	cc.mov(regA[A], asmjit::x86::qword_ptr(regA[B], myoffsetof(DObject, Class)));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Load from memory. rA = *(rB + rkC)
+
+void JitCompiler::EmitLB()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLB_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLH()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsx(regD[A], asmjit::x86::word_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLH_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsx(regD[A], asmjit::x86::word_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLW()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.mov(regD[A], asmjit::x86::dword_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLW_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.mov(regD[A], asmjit::x86::dword_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLBU()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movzx(regD[A], asmjit::x86::byte_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLBU_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movzx(regD[A].r8Lo(), asmjit::x86::byte_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLHU()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movzx(regD[A].r16(), asmjit::x86::word_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLHU_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movzx(regD[A].r16(), asmjit::x86::word_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLSP()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.xorpd(regF[A], regF[A]);
+	cc.cvtss2sd(regF[A], asmjit::x86::dword_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLSP_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.xorpd(regF[A], regF[A]);
+	cc.cvtss2sd(regF[A], asmjit::x86::dword_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLDP()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLDP_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLS()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto ptr = newTempIntPtr();
+	cc.lea(ptr, asmjit::x86::ptr(regA[B], konstd[C]));
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, ptr);
+}
+
+void JitCompiler::EmitLS_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto ptr = newTempIntPtr();
+	cc.lea(ptr, asmjit::x86::ptr(regA[B], regD[C]));
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, ptr);
+}
+
+#if 1 // Inline read barrier impl
+
+void JitCompiler::EmitReadBarrier()
+{
+	auto isnull = cc.newLabel();
+	cc.test(regA[A], regA[A]);
+	cc.je(isnull);
+
+	auto mask = newTempIntPtr();
+	cc.mov(mask.r32(), asmjit::x86::dword_ptr(regA[A], myoffsetof(DObject, ObjectFlags)));
+	cc.shl(mask, 63 - 5); // put OF_EuthanizeMe (1 << 5) in the highest bit
+	cc.sar(mask, 63); // sign extend so all bits are set if OF_EuthanizeMe was set
+	cc.not_(mask);
+	cc.and_(regA[A], mask);
+
+	cc.bind(isnull);
+}
+
+void JitCompiler::EmitLO()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+
+	cc.mov(regA[A], asmjit::x86::ptr(regA[B], konstd[C]));
+	EmitReadBarrier();
+}
+
+void JitCompiler::EmitLO_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+
+	cc.mov(regA[A], asmjit::x86::ptr(regA[B], regD[C]));
+	EmitReadBarrier();
+}
+
+#else
+
+static DObject *ReadBarrier(DObject *p)
+{
+	return GC::ReadBarrier(p);
+}
+
+void JitCompiler::EmitLO()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+
+	auto ptr = newTempIntPtr();
+	cc.mov(ptr, asmjit::x86::ptr(regA[B], konstd[C]));
+
+	auto result = newResultIntPtr();
+	auto call = CreateCall<DObject*, DObject*>(ReadBarrier);
+	call->setRet(0, result);
+	call->setArg(0, ptr);
+	cc.mov(regA[A], result);
+}
+
+void JitCompiler::EmitLO_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+
+	auto ptr = newTempIntPtr();
+	cc.mov(ptr, asmjit::x86::ptr(regA[B], regD[C]));
+
+	auto result = newResultIntPtr();
+	auto call = CreateCall<DObject*, DObject*>(ReadBarrier);
+	call->setRet(0, result);
+	call->setArg(0, ptr);
+	cc.mov(regA[A], result);
+}
+
+#endif
+
+void JitCompiler::EmitLP()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.mov(regA[A], asmjit::x86::ptr(regA[B], konstd[C]));
+}
+
+void JitCompiler::EmitLP_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.mov(regA[A], asmjit::x86::ptr(regA[B], regD[C]));
+}
+
+void JitCompiler::EmitLV2()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto tmp = newTempIntPtr();
+	cc.lea(tmp, asmjit::x86::qword_ptr(regA[B], konstd[C]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
+}
+
+void JitCompiler::EmitLV2_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto tmp = newTempIntPtr();
+	cc.lea(tmp, asmjit::x86::qword_ptr(regA[B], regD[C]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
+}
+
+void JitCompiler::EmitLV3()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto tmp = newTempIntPtr();
+	cc.lea(tmp, asmjit::x86::qword_ptr(regA[B], konstd[C]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
+	cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
+}
+
+void JitCompiler::EmitLV3_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto tmp = newTempIntPtr();
+	cc.lea(tmp, asmjit::x86::qword_ptr(regA[B], regD[C]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.movsd(regF[A + 1], asmjit::x86::qword_ptr(tmp, 8));
+	cc.movsd(regF[A + 2], asmjit::x86::qword_ptr(tmp, 16));
+}
+
+static void SetString(FString *to, char **from)
+{
+	*to = *from;
+}
+
+void JitCompiler::EmitLCS()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto ptr = newTempIntPtr();
+	cc.lea(ptr, asmjit::x86::ptr(regA[B], konstd[C]));
+	auto call = CreateCall<void, FString*, char**>(SetString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, ptr);
+}
+
+void JitCompiler::EmitLCS_R()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	auto ptr = newTempIntPtr();
+	cc.lea(ptr, asmjit::x86::ptr(regA[B], regD[C]));
+	auto call = CreateCall<void, FString*, char**>(SetString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, ptr);
+}
+
+void JitCompiler::EmitLBIT()
+{
+	EmitNullPointerThrow(B, X_READ_NIL);
+	cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B]));
+	cc.and_(regD[A], C);
+	cc.cmp(regD[A], 0);
+	cc.setne(regD[A]);
+}
diff --git a/source/common/scripting/jit/jit_math.cpp b/source/common/scripting/jit/jit_math.cpp
new file mode 100644
index 000000000..0b142563d
--- /dev/null
+++ b/source/common/scripting/jit/jit_math.cpp
@@ -0,0 +1,1524 @@
+
+#include "jitintern.h"
+#include "basics.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// String instructions.
+
+static void ConcatString(FString* to, FString* first, FString* second)
+{
+	*to = *first + *second;
+}
+
+void JitCompiler::EmitCONCAT()
+{
+	auto rc = CheckRegS(C, A);
+	auto call = CreateCall<void, FString*, FString*, FString*>(ConcatString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, regS[B]);
+	call->setArg(2, rc);
+}
+
+static int StringLength(FString* str)
+{
+	return static_cast<int>(str->Len());
+}
+
+void JitCompiler::EmitLENS()
+{
+	auto result = newResultInt32();
+	auto call = CreateCall<int, FString*>(StringLength);
+	call->setRet(0, result);
+	call->setArg(0, regS[B]);
+	cc.mov(regD[A], result);
+}
+
+static int StringCompareNoCase(FString* first, FString* second)
+{
+	return first->CompareNoCase(*second);
+}
+
+static int StringCompare(FString* first, FString* second)
+{
+	return first->Compare(*second);
+}
+
+void JitCompiler::EmitCMPS()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+
+		auto call = CreateCall<int, FString*, FString*>(static_cast<bool>(A & CMP_APPROX) ? StringCompareNoCase : StringCompare);
+
+		auto result = newResultInt32();
+		call->setRet(0, result);
+
+		if (static_cast<bool>(A & CMP_BK)) call->setArg(0, asmjit::imm_ptr(&konsts[B]));
+		else                               call->setArg(0, regS[B]);
+
+		if (static_cast<bool>(A & CMP_CK)) call->setArg(1, asmjit::imm_ptr(&konsts[C]));
+		else                               call->setArg(1, regS[C]);
+
+		int method = A & CMP_METHOD_MASK;
+		if (method == CMP_EQ) {
+			cc.test(result, result);
+			if (check) cc.jz(fail);
+			else       cc.jnz(fail);
+		}
+		else if (method == CMP_LT) {
+			cc.cmp(result, 0);
+			if (check) cc.jl(fail);
+			else       cc.jnl(fail);
+		}
+		else {
+			cc.cmp(result, 0);
+			if (check) cc.jle(fail);
+			else       cc.jnle(fail);
+		}
+	});
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Integer math.
+
+void JitCompiler::EmitSLL_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.shl(regD[A], rc);
+}
+
+void JitCompiler::EmitSLL_RI()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.shl(regD[A], C);
+}
+
+void JitCompiler::EmitSLL_KR()
+{
+	auto rc = CheckRegD(C, A);
+	cc.mov(regD[A], konstd[B]);
+	cc.shl(regD[A], rc);
+}
+
+void JitCompiler::EmitSRL_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.shr(regD[A], rc);
+}
+
+void JitCompiler::EmitSRL_RI()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.shr(regD[A], C);
+}
+
+void JitCompiler::EmitSRL_KR()
+{
+	auto rc = CheckRegD(C, A);
+	cc.mov(regD[A], konstd[B]);
+	cc.shr(regD[A], rc);
+}
+
+void JitCompiler::EmitSRA_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.sar(regD[A], rc);
+}
+
+void JitCompiler::EmitSRA_RI()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.sar(regD[A], C);
+}
+
+void JitCompiler::EmitSRA_KR()
+{
+	auto rc = CheckRegD(C, A);
+	cc.mov(regD[A], konstd[B]);
+	cc.sar(regD[A], rc);
+}
+
+void JitCompiler::EmitADD_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.add(regD[A], rc);
+}
+
+void JitCompiler::EmitADD_RK()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.add(regD[A], konstd[C]);
+}
+
+void JitCompiler::EmitADDI()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.add(regD[A], Cs);
+}
+
+void JitCompiler::EmitSUB_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.sub(regD[A], rc);
+}
+
+void JitCompiler::EmitSUB_RK()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.sub(regD[A], konstd[C]);
+}
+
+void JitCompiler::EmitSUB_KR()
+{
+	auto rc = CheckRegD(C, A);
+	cc.mov(regD[A], konstd[B]);
+	cc.sub(regD[A], rc);
+}
+
+void JitCompiler::EmitMUL_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.imul(regD[A], rc);
+}
+
+void JitCompiler::EmitMUL_RK()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.imul(regD[A], konstd[C]);
+}
+
+void JitCompiler::EmitDIV_RR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, regD[B]);
+	cc.cdq(tmp1, tmp0);
+	cc.idiv(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp0);
+}
+
+void JitCompiler::EmitDIV_RK()
+{
+	if (konstd[C] != 0)
+	{
+		auto tmp0 = newTempInt32();
+		auto tmp1 = newTempInt32();
+		auto konstTmp = newTempIntPtr();
+		cc.mov(tmp0, regD[B]);
+		cc.cdq(tmp1, tmp0);
+		cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C]));
+		cc.idiv(tmp1, tmp0, asmjit::x86::ptr(konstTmp));
+		cc.mov(regD[A], tmp0);
+	}
+	else
+	{
+		EmitThrowException(X_DIVISION_BY_ZERO);
+	}
+}
+
+void JitCompiler::EmitDIV_KR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, konstd[B]);
+	cc.cdq(tmp1, tmp0);
+	cc.idiv(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp0);
+}
+
+void JitCompiler::EmitDIVU_RR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, regD[B]);
+	cc.mov(tmp1, 0);
+	cc.div(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp0);
+}
+
+void JitCompiler::EmitDIVU_RK()
+{
+	if (konstd[C] != 0)
+	{
+		auto tmp0 = newTempInt32();
+		auto tmp1 = newTempInt32();
+		auto konstTmp = newTempIntPtr();
+		cc.mov(tmp0, regD[B]);
+		cc.mov(tmp1, 0);
+		cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C]));
+		cc.div(tmp1, tmp0, asmjit::x86::ptr(konstTmp));
+		cc.mov(regD[A], tmp0);
+	}
+	else
+	{
+		EmitThrowException(X_DIVISION_BY_ZERO);
+	}
+}
+
+void JitCompiler::EmitDIVU_KR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, konstd[B]);
+	cc.mov(tmp1, 0);
+	cc.div(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp0);
+}
+
+void JitCompiler::EmitMOD_RR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, regD[B]);
+	cc.cdq(tmp1, tmp0);
+	cc.idiv(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp1);
+}
+
+void JitCompiler::EmitMOD_RK()
+{
+	if (konstd[C] != 0)
+	{
+		auto tmp0 = newTempInt32();
+		auto tmp1 = newTempInt32();
+		auto konstTmp = newTempIntPtr();
+		cc.mov(tmp0, regD[B]);
+		cc.cdq(tmp1, tmp0);
+		cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C]));
+		cc.idiv(tmp1, tmp0, asmjit::x86::ptr(konstTmp));
+		cc.mov(regD[A], tmp1);
+	}
+	else
+	{
+		EmitThrowException(X_DIVISION_BY_ZERO);
+	}
+}
+
+void JitCompiler::EmitMOD_KR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, konstd[B]);
+	cc.cdq(tmp1, tmp0);
+	cc.idiv(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp1);
+}
+
+void JitCompiler::EmitMODU_RR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, regD[B]);
+	cc.mov(tmp1, 0);
+	cc.div(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp1);
+}
+
+void JitCompiler::EmitMODU_RK()
+{
+	if (konstd[C] != 0)
+	{
+		auto tmp0 = newTempInt32();
+		auto tmp1 = newTempInt32();
+		auto konstTmp = newTempIntPtr();
+		cc.mov(tmp0, regD[B]);
+		cc.mov(tmp1, 0);
+		cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C]));
+		cc.div(tmp1, tmp0, asmjit::x86::ptr(konstTmp));
+		cc.mov(regD[A], tmp1);
+	}
+	else
+	{
+		EmitThrowException(X_DIVISION_BY_ZERO);
+	}
+}
+
+void JitCompiler::EmitMODU_KR()
+{
+	auto tmp0 = newTempInt32();
+	auto tmp1 = newTempInt32();
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	cc.test(regD[C], regD[C]);
+	cc.je(label);
+
+	cc.mov(tmp0, konstd[B]);
+	cc.mov(tmp1, 0);
+	cc.div(tmp1, tmp0, regD[C]);
+	cc.mov(regD[A], tmp1);
+}
+
+void JitCompiler::EmitAND_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.and_(regD[A], rc);
+}
+
+void JitCompiler::EmitAND_RK()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.and_(regD[A], konstd[C]);
+}
+
+void JitCompiler::EmitOR_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.or_(regD[A], rc);
+}
+
+void JitCompiler::EmitOR_RK()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.or_(regD[A], konstd[C]);
+}
+
+void JitCompiler::EmitXOR_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.xor_(regD[A], rc);
+}
+
+void JitCompiler::EmitXOR_RK()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.xor_(regD[A], konstd[C]);
+}
+
+void JitCompiler::EmitMIN_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.cmp(rc, regD[A]);
+	cc.cmovl(regD[A], rc);
+}
+
+void JitCompiler::EmitMIN_RK()
+{
+	auto rc = newTempInt32();
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.mov(rc, asmjit::imm(konstd[C]));
+	cc.cmp(rc, regD[A]);
+	cc.cmovl(regD[A], rc);
+}
+
+void JitCompiler::EmitMAX_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.cmp(rc, regD[A]);
+	cc.cmovg(regD[A], rc);
+}
+
+void JitCompiler::EmitMAX_RK()
+{
+	auto rc = newTempInt32();
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.mov(rc, asmjit::imm(konstd[C]));
+	cc.cmp(rc, regD[A]);
+	cc.cmovg(regD[A], rc);
+}
+
+void JitCompiler::EmitMINU_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.cmp(rc, regD[A]);
+	cc.cmovb(regD[A], rc);
+}
+
+void JitCompiler::EmitMINU_RK()
+{
+	auto rc = newTempInt32();
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.mov(rc, asmjit::imm(konstd[C]));
+	cc.cmp(rc, regD[A]);
+	cc.cmovb(regD[A], rc);
+}
+
+void JitCompiler::EmitMAXU_RR()
+{
+	auto rc = CheckRegD(C, A);
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.cmp(rc, regD[A]);
+	cc.cmova(regD[A], rc);
+}
+
+void JitCompiler::EmitMAXU_RK()
+{
+	auto rc = newTempInt32();
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.mov(rc, asmjit::imm(konstd[C]));
+	cc.cmp(rc, regD[A]);
+	cc.cmova(regD[A], rc);
+}
+
+void JitCompiler::EmitABS()
+{
+	auto srcB = CheckRegD(B, A);
+	auto tmp = newTempInt32();
+	cc.mov(tmp, regD[B]);
+	cc.sar(tmp, 31);
+	cc.mov(regD[A], tmp);
+	cc.xor_(regD[A], srcB);
+	cc.sub(regD[A], tmp);
+}
+
+void JitCompiler::EmitNEG()
+{
+	auto srcB = CheckRegD(B, A);
+	cc.xor_(regD[A], regD[A]);
+	cc.sub(regD[A], srcB);
+}
+
+void JitCompiler::EmitNOT()
+{
+	if (A != B)
+		cc.mov(regD[A], regD[B]);
+	cc.not_(regD[A]);
+}
+
+void JitCompiler::EmitEQ_R()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], regD[C]);
+		if (check) cc.je(fail);
+		else       cc.jne(fail);
+	});
+}
+
+void JitCompiler::EmitEQ_K()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], konstd[C]);
+		if (check) cc.je(fail);
+		else       cc.jne(fail);
+	});
+}
+
+void JitCompiler::EmitLT_RR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], regD[C]);
+		if (check) cc.jl(fail);
+		else       cc.jnl(fail);
+	});
+}
+
+void JitCompiler::EmitLT_RK()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], konstd[C]);
+		if (check) cc.jl(fail);
+		else       cc.jnl(fail);
+	});
+}
+
+void JitCompiler::EmitLT_KR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(&konstd[B]));
+		cc.cmp(asmjit::x86::ptr(tmp), regD[C]);
+		if (check) cc.jl(fail);
+		else       cc.jnl(fail);
+	});
+}
+
+void JitCompiler::EmitLE_RR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], regD[C]);
+		if (check) cc.jle(fail);
+		else       cc.jnle(fail);
+	});
+}
+
+void JitCompiler::EmitLE_RK()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], konstd[C]);
+		if (check) cc.jle(fail);
+		else       cc.jnle(fail);
+	});
+}
+
+void JitCompiler::EmitLE_KR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(&konstd[B]));
+		cc.cmp(asmjit::x86::ptr(tmp), regD[C]);
+		if (check) cc.jle(fail);
+		else       cc.jnle(fail);
+	});
+}
+
+void JitCompiler::EmitLTU_RR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], regD[C]);
+		if (check) cc.jb(fail);
+		else       cc.jnb(fail);
+	});
+}
+
+void JitCompiler::EmitLTU_RK()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], konstd[C]);
+		if (check) cc.jb(fail);
+		else       cc.jnb(fail);
+	});
+}
+
+void JitCompiler::EmitLTU_KR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(&konstd[B]));
+		cc.cmp(asmjit::x86::ptr(tmp), regD[C]);
+		if (check) cc.jb(fail);
+		else       cc.jnb(fail);
+	});
+}
+
+void JitCompiler::EmitLEU_RR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], regD[C]);
+		if (check) cc.jbe(fail);
+		else       cc.jnbe(fail);
+	});
+}
+
+void JitCompiler::EmitLEU_RK()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regD[B], konstd[C]);
+		if (check) cc.jbe(fail);
+		else       cc.jnbe(fail);
+	});
+}
+
+void JitCompiler::EmitLEU_KR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(&konstd[B]));
+		cc.cmp(asmjit::x86::ptr(tmp), regD[C]);
+		if (check) cc.jbe(fail);
+		else       cc.jnbe(fail);
+	});
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Double-precision floating point math.
+
+void JitCompiler::EmitADDF_RR()
+{
+	auto rc = CheckRegF(C, A);
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.addsd(regF[A], rc);
+}
+
+void JitCompiler::EmitADDF_RK()
+{
+	auto tmp = newTempIntPtr();
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.addsd(regF[A], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitSUBF_RR()
+{
+	auto rc = CheckRegF(C, A);
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.subsd(regF[A], rc);
+}
+
+void JitCompiler::EmitSUBF_RK()
+{
+	auto tmp = newTempIntPtr();
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.subsd(regF[A], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitSUBF_KR()
+{
+	auto rc = CheckRegF(C, A);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[B]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.subsd(regF[A], rc);
+}
+
+void JitCompiler::EmitMULF_RR()
+{
+	auto rc = CheckRegF(C, A);
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.mulsd(regF[A], rc);
+}
+
+void JitCompiler::EmitMULF_RK()
+{
+	auto tmp = newTempIntPtr();
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitDIVF_RR()
+{
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	auto zero = newTempXmmSd();
+	cc.xorpd(zero, zero);
+	cc.ucomisd(regF[C], zero);
+	cc.je(label);
+
+	auto rc = CheckRegF(C, A);
+	cc.movsd(regF[A], regF[B]);
+	cc.divsd(regF[A], rc);
+}
+
+void JitCompiler::EmitDIVF_RK()
+{
+	if (konstf[C] == 0.)
+	{
+		EmitThrowException(X_DIVISION_BY_ZERO);
+	}
+	else
+	{
+		auto tmp = newTempIntPtr();
+		cc.movsd(regF[A], regF[B]);
+		cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+		cc.divsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	}
+}
+
+void JitCompiler::EmitDIVF_KR()
+{
+	auto rc = CheckRegF(C, A);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[B]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.divsd(regF[A], rc);
+}
+
+static double DoubleModF(double a, double b)
+{
+	return a - floor(a / b) * b;
+}
+
+void JitCompiler::EmitMODF_RR()
+{
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	auto zero = newTempXmmSd();
+	cc.xorpd(zero, zero);
+	cc.ucomisd(regF[C], zero);
+	cc.je(label);
+
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double, double>(DoubleModF);
+	call->setRet(0, result);
+	call->setArg(0, regF[B]);
+	call->setArg(1, regF[C]);
+	cc.movsd(regF[A], result);
+}
+
+void JitCompiler::EmitMODF_RK()
+{
+	if (konstf[C] == 0.)
+	{
+		EmitThrowException(X_DIVISION_BY_ZERO);
+	}
+	else
+	{
+		auto tmpPtr = newTempIntPtr();
+		cc.mov(tmpPtr, asmjit::imm_ptr(&konstf[C]));
+
+		auto tmp = newTempXmmSd();
+		cc.movsd(tmp, asmjit::x86::qword_ptr(tmpPtr));
+
+		auto result = newResultXmmSd();
+		auto call = CreateCall<double, double, double>(DoubleModF);
+		call->setRet(0, result);
+		call->setArg(0, regF[B]);
+		call->setArg(1, tmp);
+		cc.movsd(regF[A], result);
+	}
+}
+
+void JitCompiler::EmitMODF_KR()
+{
+	using namespace asmjit;
+
+	auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO);
+	auto zero = newTempXmmSd();
+	cc.xorpd(zero, zero);
+	cc.ucomisd(regF[C], zero);
+	cc.je(label);
+
+	auto tmp = newTempXmmSd();
+	cc.movsd(tmp, x86::ptr(ToMemAddress(&konstf[B])));
+
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double, double>(DoubleModF);
+	call->setRet(0, result);
+	call->setArg(0, tmp);
+	call->setArg(1, regF[C]);
+	cc.movsd(regF[A], result);
+}
+
+void JitCompiler::EmitPOWF_RR()
+{
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double, double>(g_pow);
+	call->setRet(0, result);
+	call->setArg(0, regF[B]);
+	call->setArg(1, regF[C]);
+	cc.movsd(regF[A], result);
+}
+
+void JitCompiler::EmitPOWF_RK()
+{
+	auto tmp = newTempIntPtr();
+	auto tmp2 = newTempXmmSd();
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp));
+
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double, double>(g_pow);
+	call->setRet(0, result);
+	call->setArg(0, regF[B]);
+	call->setArg(1, tmp2);
+	cc.movsd(regF[A], result);
+}
+
+void JitCompiler::EmitPOWF_KR()
+{
+	auto tmp = newTempIntPtr();
+	auto tmp2 = newTempXmmSd();
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[B]));
+	cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp));
+
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double, double>(g_pow);
+	call->setRet(0, result);
+	call->setArg(0, tmp2);
+	call->setArg(1, regF[C]);
+	cc.movsd(regF[A], result);
+}
+
+void JitCompiler::EmitMINF_RR()
+{
+	auto rc = CheckRegF(C, A);
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.minpd(regF[A], rc);  // minsd requires SSE 4.1
+}
+
+void JitCompiler::EmitMINF_RK()
+{
+	auto rb = CheckRegF(B, A);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.minpd(regF[A], rb); // minsd requires SSE 4.1
+}
+	
+void JitCompiler::EmitMAXF_RR()
+{
+	auto rc = CheckRegF(C, A);
+	if (A != B)
+		cc.movsd(regF[A], regF[B]);
+	cc.maxpd(regF[A], rc); // maxsd requires SSE 4.1
+}
+
+void JitCompiler::EmitMAXF_RK()
+{
+	auto rb = CheckRegF(B, A);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.maxpd(regF[A], rb); // maxsd requires SSE 4.1
+}
+	
+void JitCompiler::EmitATAN2()
+{
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double, double>(g_atan2);
+	call->setRet(0, result);
+	call->setArg(0, regF[B]);
+	call->setArg(1, regF[C]);
+	cc.movsd(regF[A], result);
+
+	static const double constant = 180 / M_PI;
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, asmjit::imm_ptr(&constant));
+	cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitFLOP()
+{
+	if (C == FLOP_NEG)
+	{
+		auto mask = cc.newDoubleConst(asmjit::kConstScopeLocal, -0.0);
+		auto maskXmm = newTempXmmSd();
+		cc.movsd(maskXmm, mask);
+		if (A != B)
+			cc.movsd(regF[A], regF[B]);
+		cc.xorpd(regF[A], maskXmm);
+	}
+	else
+	{
+		auto v = newTempXmmSd();
+		cc.movsd(v, regF[B]);
+
+		if (C == FLOP_TAN_DEG)
+		{
+			static const double constant = M_PI / 180;
+			auto tmp = newTempIntPtr();
+			cc.mov(tmp, asmjit::imm_ptr(&constant));
+			cc.mulsd(v, asmjit::x86::qword_ptr(tmp));
+		}
+
+		typedef double(*FuncPtr)(double);
+		FuncPtr func = nullptr;
+		switch (C)
+		{
+		default: I_Error("Unknown OP_FLOP subfunction");
+		case FLOP_ABS:		func = fabs; break;
+		case FLOP_EXP:		func = g_exp; break;
+		case FLOP_LOG:		func = g_log; break;
+		case FLOP_LOG10:	func = g_log10; break;
+		case FLOP_SQRT:		func = g_sqrt; break;
+		case FLOP_CEIL:		func = ceil; break;
+		case FLOP_FLOOR:	func = floor; break;
+		case FLOP_ACOS:		func = g_acos; break;
+		case FLOP_ASIN:		func = g_asin; break;
+		case FLOP_ATAN:		func = g_atan; break;
+		case FLOP_COS:		func = g_cos; break;
+		case FLOP_SIN:		func = g_sin; break;
+		case FLOP_TAN:		func = g_tan; break;
+		case FLOP_ACOS_DEG:	func = g_acos; break;
+		case FLOP_ASIN_DEG:	func = g_asin; break;
+		case FLOP_ATAN_DEG:	func = g_atan; break;
+		case FLOP_COS_DEG:	func = g_cosdeg; break;
+		case FLOP_SIN_DEG:	func = g_sindeg; break;
+		case FLOP_TAN_DEG:	func = g_tan; break;
+		case FLOP_COSH:		func = g_cosh; break;
+		case FLOP_SINH:		func = g_sinh; break;
+		case FLOP_TANH:		func = g_tanh; break;
+		case FLOP_ROUND:	func = round; break;
+		}
+
+		auto result = newResultXmmSd();
+		auto call = CreateCall<double, double>(func);
+		call->setRet(0, result);
+		call->setArg(0, v);
+		cc.movsd(regF[A], result);
+
+		if (C == FLOP_ACOS_DEG || C == FLOP_ASIN_DEG || C == FLOP_ATAN_DEG)
+		{
+			static const double constant = 180 / M_PI;
+			auto tmp = newTempIntPtr();
+			cc.mov(tmp, asmjit::imm_ptr(&constant));
+			cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp));
+		}
+	}
+}
+
+void JitCompiler::EmitEQF_R()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		bool approx = static_cast<bool>(A & CMP_APPROX);
+		if (!approx)
+		{
+			cc.ucomisd(regF[B], regF[C]);
+			if (check) {
+				cc.jp(success);
+				cc.je(fail);
+			}
+			else {
+				cc.jp(fail);
+				cc.jne(fail);
+			}
+		}
+		else
+		{
+			auto tmp = newTempXmmSd();
+
+			const int64_t absMaskInt = 0x7FFFFFFFFFFFFFFF;
+			auto absMask = cc.newDoubleConst(asmjit::kConstScopeLocal, reinterpret_cast<const double&>(absMaskInt));
+			auto absMaskXmm = newTempXmmPd();
+
+			auto epsilon = cc.newDoubleConst(asmjit::kConstScopeLocal, VM_EPSILON);
+			auto epsilonXmm = newTempXmmSd();
+
+			cc.movsd(tmp, regF[B]);
+			cc.subsd(tmp, regF[C]);
+			cc.movsd(absMaskXmm, absMask);
+			cc.andpd(tmp, absMaskXmm);
+			cc.movsd(epsilonXmm, epsilon);
+			cc.ucomisd(epsilonXmm, tmp);
+
+			if (check) cc.ja(fail);
+			else       cc.jna(fail);
+		}
+	});
+}
+
+void JitCompiler::EmitEQF_K()
+{
+	using namespace asmjit;
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		bool approx = static_cast<bool>(A & CMP_APPROX);
+		if (!approx) {
+			auto konstTmp = newTempIntPtr();
+			cc.mov(konstTmp, asmjit::imm_ptr(&konstf[C]));
+			cc.ucomisd(regF[B], x86::qword_ptr(konstTmp));
+			if (check) {
+				cc.jp(success);
+				cc.je(fail);
+			}
+			else {
+				cc.jp(fail);
+				cc.jne(fail);
+			}
+		}
+		else {
+			auto konstTmp = newTempIntPtr();
+			auto subTmp = newTempXmmSd();
+
+			const int64_t absMaskInt = 0x7FFFFFFFFFFFFFFF;
+			auto absMask = cc.newDoubleConst(kConstScopeLocal, reinterpret_cast<const double&>(absMaskInt));
+			auto absMaskXmm = newTempXmmPd();
+
+			auto epsilon = cc.newDoubleConst(kConstScopeLocal, VM_EPSILON);
+			auto epsilonXmm = newTempXmmSd();
+
+			cc.mov(konstTmp, asmjit::imm_ptr(&konstf[C]));
+
+			cc.movsd(subTmp, regF[B]);
+			cc.subsd(subTmp, x86::qword_ptr(konstTmp));
+			cc.movsd(absMaskXmm, absMask);
+			cc.andpd(subTmp, absMaskXmm);
+			cc.movsd(epsilonXmm, epsilon);
+			cc.ucomisd(epsilonXmm, subTmp);
+
+			if (check) cc.ja(fail);
+			else       cc.jna(fail);
+		}
+	});
+}
+
+void JitCompiler::EmitLTF_RR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		if (static_cast<bool>(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LTF_RR.\n");
+
+		cc.ucomisd(regF[C], regF[B]);
+		if (check) cc.ja(fail);
+		else       cc.jna(fail);
+	});
+}
+
+void JitCompiler::EmitLTF_RK()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		if (static_cast<bool>(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LTF_RK.\n");
+
+		auto constTmp = newTempIntPtr();
+		auto xmmTmp = newTempXmmSd();
+		cc.mov(constTmp, asmjit::imm_ptr(&konstf[C]));
+		cc.movsd(xmmTmp, asmjit::x86::qword_ptr(constTmp));
+
+		cc.ucomisd(xmmTmp, regF[B]);
+		if (check) cc.ja(fail);
+		else       cc.jna(fail);
+	});
+}
+
+void JitCompiler::EmitLTF_KR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		if (static_cast<bool>(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LTF_KR.\n");
+
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(&konstf[B]));
+
+		cc.ucomisd(regF[C], asmjit::x86::qword_ptr(tmp));
+		if (check) cc.ja(fail);
+		else       cc.jna(fail);
+	});
+}
+
+void JitCompiler::EmitLEF_RR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		if (static_cast<bool>(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LEF_RR.\n");
+
+		cc.ucomisd(regF[C], regF[B]);
+		if (check) cc.jae(fail);
+		else       cc.jnae(fail);
+	});
+}
+
+void JitCompiler::EmitLEF_RK()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		if (static_cast<bool>(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LEF_RK.\n");
+
+		auto constTmp = newTempIntPtr();
+		auto xmmTmp = newTempXmmSd();
+		cc.mov(constTmp, asmjit::imm_ptr(&konstf[C]));
+		cc.movsd(xmmTmp, asmjit::x86::qword_ptr(constTmp));
+
+		cc.ucomisd(xmmTmp, regF[B]);
+		if (check) cc.jae(fail);
+		else       cc.jnae(fail);
+	});
+}
+
+void JitCompiler::EmitLEF_KR()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		if (static_cast<bool>(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LEF_KR.\n");
+
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(&konstf[B]));
+
+		cc.ucomisd(regF[C], asmjit::x86::qword_ptr(tmp));
+		if (check) cc.jae(fail);
+		else       cc.jnae(fail);
+	});
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Vector math. (2D)
+
+void JitCompiler::EmitNEGV2()
+{
+	auto mask = cc.newDoubleConst(asmjit::kConstScopeLocal, -0.0);
+	auto maskXmm = newTempXmmSd();
+	cc.movsd(maskXmm, mask);
+	cc.movsd(regF[A], regF[B]);
+	cc.xorpd(regF[A], maskXmm);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.xorpd(regF[A + 1], maskXmm);
+}
+
+void JitCompiler::EmitADDV2_RR()
+{
+	auto rc0 = CheckRegF(C, A);
+	auto rc1 = CheckRegF(C + 1, A + 1);
+	cc.movsd(regF[A], regF[B]);
+	cc.addsd(regF[A], rc0);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.addsd(regF[A + 1], rc1);
+}
+
+void JitCompiler::EmitSUBV2_RR()
+{
+	auto rc0 = CheckRegF(C, A);
+	auto rc1 = CheckRegF(C + 1, A + 1);
+	cc.movsd(regF[A], regF[B]);
+	cc.subsd(regF[A], rc0);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.subsd(regF[A + 1], rc1);
+}
+
+void JitCompiler::EmitDOTV2_RR()
+{
+	auto rc0 = CheckRegF(C, A);
+	auto rc1 = CheckRegF(C + 1, A);
+	auto tmp = newTempXmmSd();
+	cc.movsd(regF[A], regF[B]);
+	cc.mulsd(regF[A], rc0);
+	cc.movsd(tmp, regF[B + 1]);
+	cc.mulsd(tmp, rc1);
+	cc.addsd(regF[A], tmp);
+}
+
+void JitCompiler::EmitMULVF2_RR()
+{
+	auto rc = CheckRegF(C, A, A + 1);
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.mulsd(regF[A], rc);
+	cc.mulsd(regF[A + 1], rc);
+}
+
+void JitCompiler::EmitMULVF2_RK()
+{
+	auto tmp = newTempIntPtr();
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.mulsd(regF[A + 1], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitDIVVF2_RR()
+{
+	auto rc = CheckRegF(C, A, A + 1);
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.divsd(regF[A], rc);
+	cc.divsd(regF[A + 1], rc);
+}
+
+void JitCompiler::EmitDIVVF2_RK()
+{
+	auto tmp = newTempIntPtr();
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.divsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.divsd(regF[A + 1], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitLENV2()
+{
+	auto rb0 = CheckRegF(B, A);
+	auto rb1 = CheckRegF(B + 1, A);
+	auto tmp = newTempXmmSd();
+	cc.movsd(regF[A], regF[B]);
+	cc.mulsd(regF[A], rb0);
+	cc.movsd(tmp, rb1);
+	cc.mulsd(tmp, rb1);
+	cc.addsd(regF[A], tmp);
+	CallSqrt(regF[A], regF[A]);
+}
+
+void JitCompiler::EmitEQV2_R()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		EmitVectorComparison<2> (check, fail, success);
+	});
+}
+
+void JitCompiler::EmitEQV2_K()
+{
+	I_Error("EQV2_K is not used.");
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Vector math. (3D)
+
+void JitCompiler::EmitNEGV3()
+{
+	auto mask = cc.newDoubleConst(asmjit::kConstScopeLocal, -0.0);
+	auto maskXmm = newTempXmmSd();
+	cc.movsd(maskXmm, mask);
+	cc.movsd(regF[A], regF[B]);
+	cc.xorpd(regF[A], maskXmm);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.xorpd(regF[A + 1], maskXmm);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.xorpd(regF[A + 2], maskXmm);
+}
+
+void JitCompiler::EmitADDV3_RR()
+{
+	auto rc0 = CheckRegF(C, A);
+	auto rc1 = CheckRegF(C + 1, A + 1);
+	auto rc2 = CheckRegF(C + 2, A + 2);
+	cc.movsd(regF[A], regF[B]);
+	cc.addsd(regF[A], rc0);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.addsd(regF[A + 1], rc1);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.addsd(regF[A + 2], rc2);
+}
+
+void JitCompiler::EmitSUBV3_RR()
+{
+	auto rc0 = CheckRegF(C, A);
+	auto rc1 = CheckRegF(C + 1, A + 1);
+	auto rc2 = CheckRegF(C + 2, A + 2);
+	cc.movsd(regF[A], regF[B]);
+	cc.subsd(regF[A], rc0);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.subsd(regF[A + 1], rc1);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.subsd(regF[A + 2], rc2);
+}
+
+void JitCompiler::EmitDOTV3_RR()
+{
+	auto rb1 = CheckRegF(B + 1, A);
+	auto rb2 = CheckRegF(B + 2, A);
+	auto rc0 = CheckRegF(C, A);
+	auto rc1 = CheckRegF(C + 1, A);
+	auto rc2 = CheckRegF(C + 2, A);
+	auto tmp = newTempXmmSd();
+	cc.movsd(regF[A], regF[B]);
+	cc.mulsd(regF[A], rc0);
+	cc.movsd(tmp, rb1);
+	cc.mulsd(tmp, rc1);
+	cc.addsd(regF[A], tmp);
+	cc.movsd(tmp, rb2);
+	cc.mulsd(tmp, rc2);
+	cc.addsd(regF[A], tmp);
+}
+
+void JitCompiler::EmitCROSSV_RR()
+{
+	auto tmp = newTempXmmSd();
+
+	auto a0 = CheckRegF(B, A);
+	auto a1 = CheckRegF(B + 1, A + 1);
+	auto a2 = CheckRegF(B + 2, A + 2);
+	auto b0 = CheckRegF(C, A);
+	auto b1 = CheckRegF(C + 1, A + 1);
+	auto b2 = CheckRegF(C + 2, A + 2);
+
+	// r0 = a1b2 - a2b1
+	cc.movsd(regF[A], a1);
+	cc.mulsd(regF[A], b2);
+	cc.movsd(tmp, a2);
+	cc.mulsd(tmp, b1);
+	cc.subsd(regF[A], tmp);
+
+	// r1 = a2b0 - a0b2
+	cc.movsd(regF[A + 1], a2);
+	cc.mulsd(regF[A + 1], b0);
+	cc.movsd(tmp, a0);
+	cc.mulsd(tmp, b2);
+	cc.subsd(regF[A + 1], tmp);
+
+	// r2 = a0b1 - a1b0
+	cc.movsd(regF[A + 2], a0);
+	cc.mulsd(regF[A + 2], b1);
+	cc.movsd(tmp, a1);
+	cc.mulsd(tmp, b0);
+	cc.subsd(regF[A + 2], tmp);
+}
+
+void JitCompiler::EmitMULVF3_RR()
+{
+	auto rc = CheckRegF(C, A, A + 1, A + 2);
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.mulsd(regF[A], rc);
+	cc.mulsd(regF[A + 1], rc);
+	cc.mulsd(regF[A + 2], rc);
+}
+
+void JitCompiler::EmitMULVF3_RK()
+{
+	auto tmp = newTempIntPtr();
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.mulsd(regF[A + 1], asmjit::x86::qword_ptr(tmp));
+	cc.mulsd(regF[A + 2], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitDIVVF3_RR()
+{
+	auto rc = CheckRegF(C, A, A + 1, A + 2);
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.divsd(regF[A], rc);
+	cc.divsd(regF[A + 1], rc);
+	cc.divsd(regF[A + 2], rc);
+}
+
+void JitCompiler::EmitDIVVF3_RK()
+{
+	auto tmp = newTempIntPtr();
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+	cc.mov(tmp, asmjit::imm_ptr(&konstf[C]));
+	cc.divsd(regF[A], asmjit::x86::qword_ptr(tmp));
+	cc.divsd(regF[A + 1], asmjit::x86::qword_ptr(tmp));
+	cc.divsd(regF[A + 2], asmjit::x86::qword_ptr(tmp));
+}
+
+void JitCompiler::EmitLENV3()
+{
+	auto rb1 = CheckRegF(B + 1, A);
+	auto rb2 = CheckRegF(B + 2, A);
+	auto tmp = newTempXmmSd();
+	cc.movsd(regF[A], regF[B]);
+	cc.mulsd(regF[A], regF[B]);
+	cc.movsd(tmp, rb1);
+	cc.mulsd(tmp, rb1);
+	cc.addsd(regF[A], tmp);
+	cc.movsd(tmp, rb2);
+	cc.mulsd(tmp, rb2);
+	cc.addsd(regF[A], tmp);
+	CallSqrt(regF[A], regF[A]);
+}
+
+void JitCompiler::EmitEQV3_R()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		EmitVectorComparison<3> (check, fail, success);
+	});
+}
+	
+void JitCompiler::EmitEQV3_K()
+{
+	I_Error("EQV3_K is not used.");
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Pointer math.
+
+void JitCompiler::EmitADDA_RR()
+{
+	auto tmp = newTempIntPtr();
+	auto label = cc.newLabel();
+
+	cc.mov(tmp, regA[B]);
+
+	// Check if zero, the first operand is zero, if it is, don't add.
+	cc.cmp(tmp, 0);
+	cc.je(label);
+
+	auto tmpptr = newTempIntPtr();
+	cc.mov(tmpptr, regD[C]);
+	cc.add(tmp, tmpptr);
+
+	cc.bind(label);
+	cc.mov(regA[A], tmp);
+}
+
+void JitCompiler::EmitADDA_RK()
+{
+	auto tmp = newTempIntPtr();
+	auto label = cc.newLabel();
+
+	cc.mov(tmp, regA[B]);
+
+	// Check if zero, the first operand is zero, if it is, don't add.
+	cc.cmp(tmp, 0);
+	cc.je(label);
+
+	cc.add(tmp, konstd[C]);
+
+	cc.bind(label);
+	cc.mov(regA[A], tmp);
+}
+
+void JitCompiler::EmitSUBA()
+{
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, regA[B]);
+	cc.sub(tmp, regD[C]);
+	cc.mov(regA[A], tmp);
+}
+
+void JitCompiler::EmitEQA_R()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		cc.cmp(regA[B], regA[C]);
+		if (check) cc.je(fail);
+		else       cc.jne(fail);
+	});
+}
+
+void JitCompiler::EmitEQA_K()
+{
+	EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) {
+		auto tmp = newTempIntPtr();
+		cc.mov(tmp, asmjit::imm_ptr(konsta[C].v));
+		cc.cmp(regA[B], tmp);
+		if (check) cc.je(fail);
+		else       cc.jne(fail);
+	});
+}
+
+void JitCompiler::CallSqrt(const asmjit::X86Xmm &a, const asmjit::X86Xmm &b)
+{
+	auto result = newResultXmmSd();
+	auto call = CreateCall<double, double>(g_sqrt);
+	call->setRet(0, result);
+	call->setArg(0, b);
+	cc.movsd(a, result);
+}
diff --git a/source/common/scripting/jit/jit_move.cpp b/source/common/scripting/jit/jit_move.cpp
new file mode 100644
index 000000000..d6b870646
--- /dev/null
+++ b/source/common/scripting/jit/jit_move.cpp
@@ -0,0 +1,272 @@
+
+#include "jitintern.h"
+#include "v_video.h"
+#include "s_soundinternal.h"
+#include "texturemanager.h"
+
+void JitCompiler::EmitMOVE()
+{
+	cc.mov(regD[A], regD[B]);
+}
+void JitCompiler::EmitMOVEF()
+{
+	cc.movsd(regF[A], regF[B]);
+}
+
+void JitCompiler::EmitMOVES()
+{
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, regS[A]);
+	call->setArg(1, regS[B]);
+}
+
+void JitCompiler::EmitMOVEA()
+{
+	cc.mov(regA[A], regA[B]);
+}
+
+void JitCompiler::EmitMOVEV2()
+{
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+}
+
+void JitCompiler::EmitMOVEV3()
+{
+	cc.movsd(regF[A], regF[B]);
+	cc.movsd(regF[A + 1], regF[B + 1]);
+	cc.movsd(regF[A + 2], regF[B + 2]);
+}
+
+static void CastI2S(FString *a, int b) { a->Format("%d", b); }
+static void CastU2S(FString *a, int b) { a->Format("%u", b); }
+static void CastF2S(FString *a, double b) { a->Format("%.5f", b); }
+static void CastV22S(FString *a, double b, double b1) { a->Format("(%.5f, %.5f)", b, b1); }
+static void CastV32S(FString *a, double b, double b1, double b2) { a->Format("(%.5f, %.5f, %.5f)", b, b1, b2); }
+static void CastP2S(FString *a, void *b) { if (b == nullptr) *a = "null"; else a->Format("%p", b); }
+static int CastS2I(FString *b) { return (int)b->ToLong(); }
+static double CastS2F(FString *b) { return b->ToDouble(); }
+static int CastS2N(FString *b) { return b->Len() == 0 ? NAME_None : FName(*b).GetIndex(); }
+static void CastN2S(FString *a, int b) { FName name = FName(ENamedName(b)); *a = name.IsValidName() ? name.GetChars() : ""; }
+static int CastS2Co(FString *b) { return V_GetColor(nullptr, *b); }
+static void CastCo2S(FString *a, int b) { PalEntry c(b); a->Format("%02x %02x %02x", c.r, c.g, c.b); }
+static int CastS2So(FString *b) { return FSoundID(*b); }
+static void CastSo2S(FString* a, int b) { *a = soundEngine->GetSoundName(b); }
+static void CastSID2S(FString* a, unsigned int b) { *a = "";/* (b >= sprites.Size()) ? "TNT1" : sprites[b].name;*/ }
+static void CastTID2S(FString *a, int b) { auto tex = TexMan.GetTexture(*(FTextureID*)&b); *a = (tex == nullptr) ? "(null)" : tex->GetName().GetChars(); }
+
+void JitCompiler::EmitCAST()
+{
+	asmjit::X86Gp tmp, resultD;
+	asmjit::X86Xmm resultF;
+	asmjit::CCFuncCall *call = nullptr;
+
+	switch (C)
+	{
+	case CAST_I2F:
+		cc.cvtsi2sd(regF[A], regD[B]);
+		break;
+	case CAST_U2F:
+		tmp = newTempInt64();
+		cc.xor_(tmp, tmp);
+		cc.mov(tmp.r32(), regD[B]);
+		cc.cvtsi2sd(regF[A], tmp);
+		break;
+	case CAST_F2I:
+		cc.cvttsd2si(regD[A], regF[B]);
+		break;
+	case CAST_F2U:
+		tmp = newTempInt64();
+		cc.cvttsd2si(tmp, regF[B]);
+		cc.mov(regD[A], tmp.r32());
+		break;
+	case CAST_I2S:
+		call = CreateCall<void, FString*, int>(CastI2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	case CAST_U2S:
+		call = CreateCall<void, FString*, int>(CastU2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	case CAST_F2S:
+		call = CreateCall<void, FString*, double>(CastF2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regF[B]);
+		break;
+	case CAST_V22S:
+		call = CreateCall<void, FString*, double, double>(CastV22S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regF[B]);
+		call->setArg(2, regF[B + 1]);
+		break;
+	case CAST_V32S:
+		call = CreateCall<void, FString*, double, double, double>(CastV32S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regF[B]);
+		call->setArg(2, regF[B + 1]);
+		call->setArg(3, regF[B + 2]);
+		break;
+	case CAST_P2S:
+		call = CreateCall<void, FString*, void*>(CastP2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regA[B]);
+		break;
+	case CAST_S2I:
+		resultD = newResultInt32();
+		call = CreateCall<int, FString*>(CastS2I);
+		call->setRet(0, resultD);
+		call->setArg(0, regS[B]);
+		cc.mov(regD[A], resultD);
+		break;
+	case CAST_S2F:
+		resultF = newResultXmmSd();
+		call = CreateCall<double, FString*>(CastS2F);
+		call->setRet(0, resultF);
+		call->setArg(0, regS[B]);
+		cc.movsd(regF[A], resultF);
+		break;
+	case CAST_S2N:
+		resultD = newResultInt32();
+		call = CreateCall<int, FString*>(CastS2N);
+		call->setRet(0, resultD);
+		call->setArg(0, regS[B]);
+		cc.mov(regD[A], resultD);
+		break;
+	case CAST_N2S:
+		call = CreateCall<void, FString*, int>(CastN2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	case CAST_S2Co:
+		resultD = newResultInt32();
+		call = CreateCall<int, FString*>(CastS2Co);
+		call->setRet(0, resultD);
+		call->setArg(0, regS[B]);
+		cc.mov(regD[A], resultD);
+		break;
+	case CAST_Co2S:
+		call = CreateCall<void, FString*, int>(CastCo2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	case CAST_S2So:
+		resultD = newResultInt32();
+		call = CreateCall<int, FString*>(CastS2So);
+		call->setRet(0, resultD);
+		call->setArg(0, regS[B]);
+		cc.mov(regD[A], resultD);
+		break;
+	case CAST_So2S:
+		call = CreateCall<void, FString*, int>(CastSo2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	case CAST_SID2S:
+		call = CreateCall<void, FString*, unsigned int>(CastSID2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	case CAST_TID2S:
+		call = CreateCall<void, FString*, int>(CastTID2S);
+		call->setArg(0, regS[A]);
+		call->setArg(1, regD[B]);
+		break;
+	default:
+		I_Error("Unknown OP_CAST type\n");
+	}
+}
+
+static int CastB_S(FString *s) { return s->Len() > 0; }
+
+void JitCompiler::EmitCASTB()
+{
+	if (C == CASTB_I)
+	{
+		cc.cmp(regD[B], (int)0);
+		cc.setne(regD[A]);
+		cc.movzx(regD[A], regD[A].r8Lo()); // not sure if this is needed
+	}
+	else if (C == CASTB_F)
+	{
+		auto zero = newTempXmmSd();
+		auto one = newTempInt32();
+		cc.xorpd(zero, zero);
+		cc.mov(one, 1);
+		cc.xor_(regD[A], regD[A]);
+		cc.ucomisd(regF[B], zero);
+		cc.setp(regD[A]);
+		cc.cmovne(regD[A], one);
+	}
+	else if (C == CASTB_A)
+	{
+		cc.test(regA[B], regA[B]);
+		cc.setne(regD[A]);
+		cc.movzx(regD[A], regD[A].r8Lo()); // not sure if this is needed
+	}
+	else
+	{
+		auto result = newResultInt32();
+		auto call = CreateCall<int, FString*>(CastB_S);
+		call->setRet(0, result);
+		call->setArg(0, regS[B]);
+		cc.mov(regD[A], result);
+	}
+}
+
+static DObject *DynCast(DObject *obj, PClass *cls)
+{
+	return (obj && obj->IsKindOf(cls)) ? obj : nullptr;
+}
+
+void JitCompiler::EmitDYNCAST_R()
+{
+	auto result = newResultIntPtr();
+	auto call = CreateCall<DObject*, DObject*, PClass*>(DynCast);
+	call->setRet(0, result);
+	call->setArg(0, regA[B]);
+	call->setArg(1, regA[C]);
+	cc.mov(regA[A], result);
+}
+
+void JitCompiler::EmitDYNCAST_K()
+{
+	auto result = newResultIntPtr();
+	auto c = newTempIntPtr();
+	cc.mov(c, asmjit::imm_ptr(konsta[C].o));
+	auto call = CreateCall<DObject*, DObject*, PClass*>(DynCast);
+	call->setRet(0, result);
+	call->setArg(0, regA[B]);
+	call->setArg(1, c);
+	cc.mov(regA[A], result);
+}
+
+static PClass *DynCastC(PClass *cls1, PClass *cls2)
+{
+	return (cls1 && cls1->IsDescendantOf(cls2)) ? cls1 : nullptr;
+}
+
+void JitCompiler::EmitDYNCASTC_R()
+{
+	auto result = newResultIntPtr();
+	auto call = CreateCall<PClass*, PClass*, PClass*>(DynCastC);
+	call->setRet(0, result);
+	call->setArg(0, regA[B]);
+	call->setArg(1, regA[C]);
+	cc.mov(regA[A], result);
+}
+
+void JitCompiler::EmitDYNCASTC_K()
+{
+	using namespace asmjit;
+	auto result = newResultIntPtr();
+	auto c = newTempIntPtr();
+	cc.mov(c, asmjit::imm_ptr(konsta[C].o));
+	typedef PClass*(*FuncPtr)(PClass*, PClass*);
+	auto call = CreateCall<PClass*, PClass*, PClass*>(DynCastC);
+	call->setRet(0, result);
+	call->setArg(0, regA[B]);
+	call->setArg(1, c);
+	cc.mov(regA[A], result);
+}
diff --git a/source/common/scripting/jit/jit_runtime.cpp b/source/common/scripting/jit/jit_runtime.cpp
new file mode 100644
index 000000000..b0a9dc488
--- /dev/null
+++ b/source/common/scripting/jit/jit_runtime.cpp
@@ -0,0 +1,970 @@
+
+#include <memory>
+#include "jit.h"
+#include "jitintern.h"
+
+#ifdef WIN32
+#include <DbgHelp.h>
+#else
+#include <execinfo.h>
+#include <cxxabi.h>
+#include <cstring>
+#include <cstdlib>
+#include <memory>
+#endif
+
+struct JitFuncInfo
+{
+	FString name;
+	FString filename;
+	TArray<JitLineInfo> LineInfo;
+	void *start;
+	void *end;
+};
+
+static TArray<JitFuncInfo> JitDebugInfo;
+static TArray<uint8_t*> JitBlocks;
+static TArray<uint8_t*> JitFrames;
+static size_t JitBlockPos = 0;
+static size_t JitBlockSize = 0;
+
+asmjit::CodeInfo GetHostCodeInfo()
+{
+	static bool firstCall = true;
+	static asmjit::CodeInfo codeInfo;
+
+	if (firstCall)
+	{
+		asmjit::JitRuntime rt;
+		codeInfo = rt.getCodeInfo();
+		firstCall = false;
+	}
+
+	return codeInfo;
+}
+
+static void *AllocJitMemory(size_t size)
+{
+	using namespace asmjit;
+
+	if (JitBlockPos + size <= JitBlockSize)
+	{
+		uint8_t *p = JitBlocks[JitBlocks.Size() - 1];
+		p += JitBlockPos;
+		JitBlockPos += size;
+		return p;
+	}
+	else
+	{
+		const size_t bytesToAllocate = std::max(size_t(1024 * 1024), size);
+		size_t allocatedSize = 0;
+		void *p = OSUtils::allocVirtualMemory(bytesToAllocate, &allocatedSize, OSUtils::kVMWritable | OSUtils::kVMExecutable);
+		if (!p)
+			return nullptr;
+		JitBlocks.Push((uint8_t*)p);
+		JitBlockSize = allocatedSize;
+		JitBlockPos = size;
+		return p;
+	}
+}
+
+#ifdef WIN32
+
+#define UWOP_PUSH_NONVOL 0
+#define UWOP_ALLOC_LARGE 1
+#define UWOP_ALLOC_SMALL 2
+#define UWOP_SET_FPREG 3
+#define UWOP_SAVE_NONVOL 4
+#define UWOP_SAVE_NONVOL_FAR 5
+#define UWOP_SAVE_XMM128 8
+#define UWOP_SAVE_XMM128_FAR 9
+#define UWOP_PUSH_MACHFRAME 10
+
+static TArray<uint16_t> CreateUnwindInfoWindows(asmjit::CCFunc *func)
+{
+	using namespace asmjit;
+	FuncFrameLayout layout;
+	Error error = layout.init(func->getDetail(), func->getFrameInfo());
+	if (error != kErrorOk)
+		I_Error("FuncFrameLayout.init failed");
+
+	// We need a dummy emitter for instruction size calculations
+	CodeHolder code;
+	code.init(GetHostCodeInfo());
+	X86Assembler assembler(&code);
+	X86Emitter *emitter = assembler.asEmitter();
+
+	// Build UNWIND_CODE codes:
+
+	TArray<uint16_t> codes;
+	uint32_t opoffset, opcode, opinfo;
+
+	// Note: this must match exactly what X86Internal::emitProlog does
+
+	X86Gp zsp = emitter->zsp();   // ESP|RSP register.
+	X86Gp zbp = emitter->zsp();   // EBP|RBP register.
+	zbp.setId(X86Gp::kIdBp);
+	X86Gp gpReg = emitter->zsp(); // General purpose register (temporary).
+	X86Gp saReg = emitter->zsp(); // Stack-arguments base register.
+	uint32_t gpSaved = layout.getSavedRegs(X86Reg::kKindGp);
+
+	if (layout.hasPreservedFP())
+	{
+		// Emit: 'push zbp'
+		//       'mov  zbp, zsp'.
+		gpSaved &= ~Utils::mask(X86Gp::kIdBp);
+		emitter->push(zbp);
+
+		opoffset = (uint32_t)assembler.getOffset();
+		opcode = UWOP_PUSH_NONVOL;
+		opinfo = X86Gp::kIdBp;
+		codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+
+		emitter->mov(zbp, zsp);
+	}
+
+	if (gpSaved)
+	{
+		for (uint32_t i = gpSaved, regId = 0; i; i >>= 1, regId++)
+		{
+			if (!(i & 0x1)) continue;
+			// Emit: 'push gp' sequence.
+			gpReg.setId(regId);
+			emitter->push(gpReg);
+
+			opoffset = (uint32_t)assembler.getOffset();
+			opcode = UWOP_PUSH_NONVOL;
+			opinfo = regId;
+			codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+		}
+	}
+
+	uint32_t stackArgsRegId = layout.getStackArgsRegId();
+	if (stackArgsRegId != Globals::kInvalidRegId && stackArgsRegId != X86Gp::kIdSp)
+	{
+		saReg.setId(stackArgsRegId);
+		if (!(layout.hasPreservedFP() && stackArgsRegId == X86Gp::kIdBp))
+		{
+			// Emit: 'mov saReg, zsp'.
+			emitter->mov(saReg, zsp);
+		}
+	}
+
+	if (layout.hasDynamicAlignment())
+	{
+		// Emit: 'and zsp, StackAlignment'.
+		emitter->and_(zsp, -static_cast<int32_t>(layout.getStackAlignment()));
+	}
+
+	if (layout.hasStackAdjustment())
+	{
+		// Emit: 'sub zsp, StackAdjustment'.
+		emitter->sub(zsp, layout.getStackAdjustment());
+
+		uint32_t stackadjust = layout.getStackAdjustment();
+		if (stackadjust <= 128)
+		{
+			opoffset = (uint32_t)assembler.getOffset();
+			opcode = UWOP_ALLOC_SMALL;
+			opinfo = stackadjust / 8 - 1;
+			codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+		}
+		else if (stackadjust <= 512 * 1024 - 8)
+		{
+			opoffset = (uint32_t)assembler.getOffset();
+			opcode = UWOP_ALLOC_LARGE;
+			opinfo = 0;
+			codes.Push(stackadjust / 8);
+			codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+		}
+		else
+		{
+			opoffset = (uint32_t)assembler.getOffset();
+			opcode = UWOP_ALLOC_LARGE;
+			opinfo = 1;
+			codes.Push((uint16_t)(stackadjust >> 16));
+			codes.Push((uint16_t)stackadjust);
+			codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+		}
+	}
+
+	if (layout.hasDynamicAlignment() && layout.hasDsaSlotUsed())
+	{
+		// Emit: 'mov [zsp + dsaSlot], saReg'.
+		X86Mem saMem = x86::ptr(zsp, layout._dsaSlot);
+		emitter->mov(saMem, saReg);
+	}
+
+	uint32_t xmmSaved = layout.getSavedRegs(X86Reg::kKindVec);
+	if (xmmSaved)
+	{
+		X86Mem vecBase = x86::ptr(zsp, layout.getVecStackOffset());
+		X86Reg vecReg = x86::xmm(0);
+		bool avx = layout.isAvxEnabled();
+		bool aligned = layout.hasAlignedVecSR();
+		uint32_t vecInst = aligned ? (avx ? X86Inst::kIdVmovaps : X86Inst::kIdMovaps) : (avx ? X86Inst::kIdVmovups : X86Inst::kIdMovups);
+		uint32_t vecSize = 16;
+		for (uint32_t i = xmmSaved, regId = 0; i; i >>= 1, regId++)
+		{
+			if (!(i & 0x1)) continue;
+
+			// Emit 'movaps|movups [zsp + X], xmm0..15'.
+			vecReg.setId(regId);
+			emitter->emit(vecInst, vecBase, vecReg);
+			vecBase.addOffsetLo32(static_cast<int32_t>(vecSize));
+
+			if (vecBase.getOffsetLo32() / vecSize < (1 << 16))
+			{
+				opoffset = (uint32_t)assembler.getOffset();
+				opcode = UWOP_SAVE_XMM128;
+				opinfo = regId;
+				codes.Push(vecBase.getOffsetLo32() / vecSize);
+				codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+			}
+			else
+			{
+				opoffset = (uint32_t)assembler.getOffset();
+				opcode = UWOP_SAVE_XMM128_FAR;
+				opinfo = regId;
+				codes.Push((uint16_t)(vecBase.getOffsetLo32() >> 16));
+				codes.Push((uint16_t)vecBase.getOffsetLo32());
+				codes.Push(opoffset | (opcode << 8) | (opinfo << 12));
+			}
+		}
+	}
+
+	// Build the UNWIND_INFO structure:
+
+	uint16_t version = 1, flags = 0, frameRegister = 0, frameOffset = 0;
+	uint16_t sizeOfProlog = (uint16_t)assembler.getOffset();
+	uint16_t countOfCodes = (uint16_t)codes.Size();
+
+	TArray<uint16_t> info;
+	info.Push(version | (flags << 3) | (sizeOfProlog << 8));
+	info.Push(countOfCodes | (frameRegister << 8) | (frameOffset << 12));
+
+	for (unsigned int i = codes.Size(); i > 0; i--)
+		info.Push(codes[i - 1]);
+
+	if (codes.Size() % 2 == 1)
+		info.Push(0);
+
+	return info;
+}
+
+void *AddJitFunction(asmjit::CodeHolder* code, JitCompiler *compiler)
+{
+	using namespace asmjit;
+
+	CCFunc *func = compiler->Codegen();
+
+	size_t codeSize = code->getCodeSize();
+	if (codeSize == 0)
+		return nullptr;
+
+#ifdef _WIN64
+	TArray<uint16_t> unwindInfo = CreateUnwindInfoWindows(func);
+	size_t unwindInfoSize = unwindInfo.Size() * sizeof(uint16_t);
+	size_t functionTableSize = sizeof(RUNTIME_FUNCTION);
+#else
+	size_t unwindInfoSize = 0;
+	size_t functionTableSize = 0;
+#endif
+
+	codeSize = (codeSize + 15) / 16 * 16;
+
+	uint8_t *p = (uint8_t *)AllocJitMemory(codeSize + unwindInfoSize + functionTableSize);
+	if (!p)
+		return nullptr;
+
+	size_t relocSize = code->relocate(p);
+	if (relocSize == 0)
+		return nullptr;
+
+	size_t unwindStart = relocSize;
+	unwindStart = (unwindStart + 15) / 16 * 16;
+	JitBlockPos -= codeSize - unwindStart;
+
+#ifdef _WIN64
+	uint8_t *baseaddr = JitBlocks.Last();
+	uint8_t *startaddr = p;
+	uint8_t *endaddr = p + relocSize;
+	uint8_t *unwindptr = p + unwindStart;
+	memcpy(unwindptr, &unwindInfo[0], unwindInfoSize);
+
+	RUNTIME_FUNCTION *table = (RUNTIME_FUNCTION*)(unwindptr + unwindInfoSize);
+	table[0].BeginAddress = (DWORD)(ptrdiff_t)(startaddr - baseaddr);
+	table[0].EndAddress = (DWORD)(ptrdiff_t)(endaddr - baseaddr);
+#ifndef __MINGW64__
+	table[0].UnwindInfoAddress = (DWORD)(ptrdiff_t)(unwindptr - baseaddr);
+#else
+	table[0].UnwindData = (DWORD)(ptrdiff_t)(unwindptr - baseaddr);
+#endif
+	BOOLEAN result = RtlAddFunctionTable(table, 1, (DWORD64)baseaddr);
+	JitFrames.Push((uint8_t*)table);
+	if (result == 0)
+		I_Error("RtlAddFunctionTable failed");
+
+	JitDebugInfo.Push({ compiler->GetScriptFunction()->PrintableName, compiler->GetScriptFunction()->SourceFileName, compiler->LineInfo, startaddr, endaddr });
+#endif
+
+	return p;
+}
+
+#else
+
+extern "C"
+{
+	void __register_frame(const void*);
+	void __deregister_frame(const void*);
+}
+
+static void WriteLength(TArray<uint8_t> &stream, unsigned int pos, unsigned int v)
+{
+	*(uint32_t*)(&stream[pos]) = v;
+}
+
+static void WriteUInt64(TArray<uint8_t> &stream, uint64_t v)
+{
+	for (int i = 0; i < 8; i++)
+		stream.Push((v >> (i * 8)) & 0xff);
+}
+
+static void WriteUInt32(TArray<uint8_t> &stream, uint32_t v)
+{
+	for (int i = 0; i < 4; i++)
+		stream.Push((v >> (i * 8)) & 0xff);
+}
+
+static void WriteUInt16(TArray<uint8_t> &stream, uint16_t v)
+{
+	for (int i = 0; i < 2; i++)
+		stream.Push((v >> (i * 8)) & 0xff);
+}
+
+static void WriteUInt8(TArray<uint8_t> &stream, uint8_t v)
+{
+	stream.Push(v);
+}
+
+static void WriteULEB128(TArray<uint8_t> &stream, uint32_t v)
+{
+	while (true)
+	{
+		if (v < 128)
+		{
+			WriteUInt8(stream, v);
+			break;
+		}
+		else
+		{
+			WriteUInt8(stream, (v & 0x7f) | 0x80);
+			v >>= 7;
+		}
+	}
+}
+
+static void WriteSLEB128(TArray<uint8_t> &stream, int32_t v)
+{
+	if (v >= 0)
+	{
+		WriteULEB128(stream, v);
+	}
+	else
+	{
+		while (true)
+		{
+			if (v > -128)
+			{
+				WriteUInt8(stream, v & 0x7f);
+				break;
+			}
+			else
+			{
+				WriteUInt8(stream, v);
+				v >>= 7;
+			}
+		}
+	}
+}
+
+static void WritePadding(TArray<uint8_t> &stream)
+{
+	int padding = stream.Size() % 8;
+	if (padding != 0)
+	{
+		padding = 8 - padding;
+		for (int i = 0; i < padding; i++) WriteUInt8(stream, 0);
+	}
+}
+
+static void WriteCIE(TArray<uint8_t> &stream, const TArray<uint8_t> &cieInstructions, uint8_t returnAddressReg)
+{
+	unsigned int lengthPos = stream.Size();
+	WriteUInt32(stream, 0); // Length
+	WriteUInt32(stream, 0); // CIE ID
+	
+	WriteUInt8(stream, 1); // CIE Version
+	WriteUInt8(stream, 'z');
+	WriteUInt8(stream, 'R'); // fde encoding
+	WriteUInt8(stream, 0);
+	WriteULEB128(stream, 1);
+	WriteSLEB128(stream, -1);
+	WriteULEB128(stream, returnAddressReg);
+
+	WriteULEB128(stream, 1); // LEB128 augmentation size
+	WriteUInt8(stream, 0); // DW_EH_PE_absptr (FDE uses absolute pointers)
+
+	for (unsigned int i = 0; i < cieInstructions.Size(); i++)
+		stream.Push(cieInstructions[i]);
+
+	WritePadding(stream);
+	WriteLength(stream, lengthPos, stream.Size() - lengthPos - 4);
+}
+
+static void WriteFDE(TArray<uint8_t> &stream, const TArray<uint8_t> &fdeInstructions, uint32_t cieLocation, unsigned int &functionStart)
+{
+	unsigned int lengthPos = stream.Size();
+	WriteUInt32(stream, 0); // Length
+	uint32_t offsetToCIE = stream.Size() - cieLocation;
+	WriteUInt32(stream, offsetToCIE);
+	
+	functionStart = stream.Size();
+	WriteUInt64(stream, 0); // func start
+	WriteUInt64(stream, 0); // func size
+
+	WriteULEB128(stream, 0); // LEB128 augmentation size
+
+	for (unsigned int i = 0; i < fdeInstructions.Size(); i++)
+		stream.Push(fdeInstructions[i]);
+
+	WritePadding(stream);
+	WriteLength(stream, lengthPos, stream.Size() - lengthPos - 4);
+}
+
+static void WriteAdvanceLoc(TArray<uint8_t> &fdeInstructions, uint64_t offset, uint64_t &lastOffset)
+{
+	uint64_t delta = offset - lastOffset;
+	if (delta < (1 << 6))
+	{
+		WriteUInt8(fdeInstructions, (1 << 6) | delta); // DW_CFA_advance_loc
+	}
+	else if (delta < (1 << 8))
+	{
+		WriteUInt8(fdeInstructions, 2); // DW_CFA_advance_loc1
+		WriteUInt8(fdeInstructions, delta);
+	}
+	else if (delta < (1 << 16))
+	{
+		WriteUInt8(fdeInstructions, 3); // DW_CFA_advance_loc2
+		WriteUInt16(fdeInstructions, delta);
+	}
+	else
+	{
+		WriteUInt8(fdeInstructions, 4); // DW_CFA_advance_loc3
+		WriteUInt32(fdeInstructions, delta);
+	}
+	lastOffset = offset;
+}
+
+static void WriteDefineCFA(TArray<uint8_t> &cieInstructions, int dwarfRegId, int stackOffset)
+{
+	WriteUInt8(cieInstructions, 0x0c); // DW_CFA_def_cfa
+	WriteULEB128(cieInstructions, dwarfRegId);
+	WriteULEB128(cieInstructions, stackOffset);
+}
+
+static void WriteDefineStackOffset(TArray<uint8_t> &fdeInstructions, int stackOffset)
+{
+	WriteUInt8(fdeInstructions, 0x0e); // DW_CFA_def_cfa_offset
+	WriteULEB128(fdeInstructions, stackOffset);
+}
+
+static void WriteRegisterStackLocation(TArray<uint8_t> &instructions, int dwarfRegId, int stackLocation)
+{
+	WriteUInt8(instructions, (2 << 6) | dwarfRegId); // DW_CFA_offset
+	WriteULEB128(instructions, stackLocation);
+}
+
+static TArray<uint8_t> CreateUnwindInfoUnix(asmjit::CCFunc *func, unsigned int &functionStart)
+{
+	using namespace asmjit;
+
+	// Build .eh_frame:
+	//
+	// The documentation for this can be found in the DWARF standard
+	// The x64 specific details are described in "System V Application Binary Interface AMD64 Architecture Processor Supplement"
+	//
+	// See appendix D.6 "Call Frame Information Example" in the DWARF 5 spec.
+	//
+	// The CFI_Parser<A>::decodeFDE parser on the other side..
+	// https://github.com/llvm-mirror/libunwind/blob/master/src/DwarfParser.hpp
+	
+	// Asmjit -> DWARF register id
+	int dwarfRegId[16];
+	dwarfRegId[X86Gp::kIdAx] = 0;
+	dwarfRegId[X86Gp::kIdDx] = 1;
+	dwarfRegId[X86Gp::kIdCx] = 2;
+	dwarfRegId[X86Gp::kIdBx] = 3;
+	dwarfRegId[X86Gp::kIdSi] = 4;
+	dwarfRegId[X86Gp::kIdDi] = 5;
+	dwarfRegId[X86Gp::kIdBp] = 6;
+	dwarfRegId[X86Gp::kIdSp] = 7;
+	dwarfRegId[X86Gp::kIdR8] = 8;
+	dwarfRegId[X86Gp::kIdR9] = 9;
+	dwarfRegId[X86Gp::kIdR10] = 10;
+	dwarfRegId[X86Gp::kIdR11] = 11;
+	dwarfRegId[X86Gp::kIdR12] = 12;
+	dwarfRegId[X86Gp::kIdR13] = 13;
+	dwarfRegId[X86Gp::kIdR14] = 14;
+	dwarfRegId[X86Gp::kIdR15] = 15;
+	int dwarfRegRAId = 16;
+	int dwarfRegXmmId = 17;
+	
+	TArray<uint8_t> cieInstructions;
+	TArray<uint8_t> fdeInstructions;
+
+	uint8_t returnAddressReg = dwarfRegRAId;
+	int stackOffset = 8; // Offset from RSP to the Canonical Frame Address (CFA) - stack position where the CALL return address is stored
+
+	WriteDefineCFA(cieInstructions, dwarfRegId[X86Gp::kIdSp], stackOffset);
+	WriteRegisterStackLocation(cieInstructions, returnAddressReg, stackOffset);
+	
+	FuncFrameLayout layout;
+	Error error = layout.init(func->getDetail(), func->getFrameInfo());
+	if (error != kErrorOk)
+		I_Error("FuncFrameLayout.init failed");
+
+	// We need a dummy emitter for instruction size calculations
+	CodeHolder code;
+	code.init(GetHostCodeInfo());
+	X86Assembler assembler(&code);
+	X86Emitter *emitter = assembler.asEmitter();
+	uint64_t lastOffset = 0;
+
+	// Note: the following code must match exactly what X86Internal::emitProlog does
+
+	X86Gp zsp = emitter->zsp();   // ESP|RSP register.
+	X86Gp zbp = emitter->zsp();   // EBP|RBP register.
+	zbp.setId(X86Gp::kIdBp);
+	X86Gp gpReg = emitter->zsp(); // General purpose register (temporary).
+	X86Gp saReg = emitter->zsp(); // Stack-arguments base register.
+	uint32_t gpSaved = layout.getSavedRegs(X86Reg::kKindGp);
+
+	if (layout.hasPreservedFP())
+	{
+		// Emit: 'push zbp'
+		//       'mov  zbp, zsp'.
+		gpSaved &= ~Utils::mask(X86Gp::kIdBp);
+		emitter->push(zbp);
+
+		stackOffset += 8;
+		WriteAdvanceLoc(fdeInstructions, assembler.getOffset(), lastOffset);
+		WriteDefineStackOffset(fdeInstructions, stackOffset);
+		WriteRegisterStackLocation(fdeInstructions, dwarfRegId[X86Gp::kIdBp], stackOffset);
+
+		emitter->mov(zbp, zsp);
+	}
+
+	if (gpSaved)
+	{
+		for (uint32_t i = gpSaved, regId = 0; i; i >>= 1, regId++)
+		{
+			if (!(i & 0x1)) continue;
+			// Emit: 'push gp' sequence.
+			gpReg.setId(regId);
+			emitter->push(gpReg);
+
+			stackOffset += 8;
+			WriteAdvanceLoc(fdeInstructions, assembler.getOffset(), lastOffset);
+			WriteDefineStackOffset(fdeInstructions, stackOffset);
+			WriteRegisterStackLocation(fdeInstructions, dwarfRegId[regId], stackOffset);
+		}
+	}
+
+	uint32_t stackArgsRegId = layout.getStackArgsRegId();
+	if (stackArgsRegId != Globals::kInvalidRegId && stackArgsRegId != X86Gp::kIdSp)
+	{
+		saReg.setId(stackArgsRegId);
+		if (!(layout.hasPreservedFP() && stackArgsRegId == X86Gp::kIdBp))
+		{
+			// Emit: 'mov saReg, zsp'.
+			emitter->mov(saReg, zsp);
+		}
+	}
+
+	if (layout.hasDynamicAlignment())
+	{
+		// Emit: 'and zsp, StackAlignment'.
+		emitter->and_(zsp, -static_cast<int32_t>(layout.getStackAlignment()));
+	}
+
+	if (layout.hasStackAdjustment())
+	{
+		// Emit: 'sub zsp, StackAdjustment'.
+		emitter->sub(zsp, layout.getStackAdjustment());
+
+		stackOffset += layout.getStackAdjustment();
+		WriteAdvanceLoc(fdeInstructions, assembler.getOffset(), lastOffset);
+		WriteDefineStackOffset(fdeInstructions, stackOffset);
+	}
+
+	if (layout.hasDynamicAlignment() && layout.hasDsaSlotUsed())
+	{
+		// Emit: 'mov [zsp + dsaSlot], saReg'.
+		X86Mem saMem = x86::ptr(zsp, layout._dsaSlot);
+		emitter->mov(saMem, saReg);
+	}
+
+	uint32_t xmmSaved = layout.getSavedRegs(X86Reg::kKindVec);
+	if (xmmSaved)
+	{
+		int vecOffset = layout.getVecStackOffset();
+		X86Mem vecBase = x86::ptr(zsp, layout.getVecStackOffset());
+		X86Reg vecReg = x86::xmm(0);
+		bool avx = layout.isAvxEnabled();
+		bool aligned = layout.hasAlignedVecSR();
+		uint32_t vecInst = aligned ? (avx ? X86Inst::kIdVmovaps : X86Inst::kIdMovaps) : (avx ? X86Inst::kIdVmovups : X86Inst::kIdMovups);
+		uint32_t vecSize = 16;
+		for (uint32_t i = xmmSaved, regId = 0; i; i >>= 1, regId++)
+		{
+			if (!(i & 0x1)) continue;
+
+			// Emit 'movaps|movups [zsp + X], xmm0..15'.
+			vecReg.setId(regId);
+			emitter->emit(vecInst, vecBase, vecReg);
+			vecBase.addOffsetLo32(static_cast<int32_t>(vecSize));
+
+			WriteAdvanceLoc(fdeInstructions, assembler.getOffset(), lastOffset);
+			WriteRegisterStackLocation(fdeInstructions, dwarfRegXmmId + regId, stackOffset - vecOffset);
+			vecOffset += static_cast<int32_t>(vecSize);
+		}
+	}
+
+	TArray<uint8_t> stream;
+	WriteCIE(stream, cieInstructions, returnAddressReg);
+	WriteFDE(stream, fdeInstructions, 0, functionStart);
+	WriteUInt32(stream, 0);
+	return stream;
+}
+
+void *AddJitFunction(asmjit::CodeHolder* code, JitCompiler *compiler)
+{
+	using namespace asmjit;
+
+	CCFunc *func = compiler->Codegen();
+
+	size_t codeSize = code->getCodeSize();
+	if (codeSize == 0)
+		return nullptr;
+
+	unsigned int fdeFunctionStart = 0;
+	TArray<uint8_t> unwindInfo = CreateUnwindInfoUnix(func, fdeFunctionStart);
+	size_t unwindInfoSize = unwindInfo.Size();
+
+	codeSize = (codeSize + 15) / 16 * 16;
+
+	uint8_t *p = (uint8_t *)AllocJitMemory(codeSize + unwindInfoSize);
+	if (!p)
+		return nullptr;
+
+	size_t relocSize = code->relocate(p);
+	if (relocSize == 0)
+		return nullptr;
+
+	size_t unwindStart = relocSize;
+	unwindStart = (unwindStart + 15) / 16 * 16;
+	JitBlockPos -= codeSize - unwindStart;
+
+	uint8_t *baseaddr = JitBlocks.Last();
+	uint8_t *startaddr = p;
+	uint8_t *endaddr = p + relocSize;
+	uint8_t *unwindptr = p + unwindStart;
+	memcpy(unwindptr, &unwindInfo[0], unwindInfoSize);
+
+	if (unwindInfo.Size() > 0)
+	{
+		uint64_t *unwindfuncaddr = (uint64_t *)(unwindptr + fdeFunctionStart);
+		unwindfuncaddr[0] = (ptrdiff_t)startaddr;
+		unwindfuncaddr[1] = (ptrdiff_t)(endaddr - startaddr);
+
+#ifdef __APPLE__
+		// On macOS __register_frame takes a single FDE as an argument
+		uint8_t *entry = unwindptr;
+		while (true)
+		{
+			uint32_t length = *((uint32_t *)entry);
+			if (length == 0)
+				break;
+
+			if (length == 0xffffffff)
+			{
+				uint64_t length64 = *((uint64_t *)(entry + 4));
+				if (length64 == 0)
+					break;
+				
+				uint64_t offset = *((uint64_t *)(entry + 12));
+				if (offset != 0)
+				{
+					__register_frame(entry);
+					JitFrames.Push(entry);
+				}
+				entry += length64 + 12;
+			}
+			else
+			{
+				uint32_t offset = *((uint32_t *)(entry + 4));
+				if (offset != 0)
+				{
+					__register_frame(entry);
+					JitFrames.Push(entry);
+				}
+				entry += length + 4;
+			}
+		}
+#else
+		// On Linux it takes a pointer to the entire .eh_frame
+		__register_frame(unwindptr);
+		JitFrames.Push(unwindptr);
+#endif
+	}
+
+	JitDebugInfo.Push({ compiler->GetScriptFunction()->PrintableName, compiler->GetScriptFunction()->SourceFileName, compiler->LineInfo, startaddr, endaddr });
+
+	return p;
+}
+#endif
+
+void JitRelease()
+{
+#ifdef _WIN64
+	for (auto p : JitFrames)
+	{
+		RtlDeleteFunctionTable((PRUNTIME_FUNCTION)p);
+	}
+#elif !defined(WIN32)
+	for (auto p : JitFrames)
+	{
+		__deregister_frame(p);
+	}
+#endif
+	for (auto p : JitBlocks)
+	{
+		asmjit::OSUtils::releaseVirtualMemory(p, 1024 * 1024);
+	}
+	JitDebugInfo.Clear();
+	JitFrames.Clear();
+	JitBlocks.Clear();
+	JitBlockPos = 0;
+	JitBlockSize = 0;
+}
+
+static int CaptureStackTrace(int max_frames, void **out_frames)
+{
+	memset(out_frames, 0, sizeof(void *) * max_frames);
+
+#ifdef _WIN64
+	// RtlCaptureStackBackTrace doesn't support RtlAddFunctionTable..
+
+	CONTEXT context;
+	RtlCaptureContext(&context);
+
+	UNWIND_HISTORY_TABLE history;
+	memset(&history, 0, sizeof(UNWIND_HISTORY_TABLE));
+
+	ULONG64 establisherframe = 0;
+	PVOID handlerdata = nullptr;
+
+	int frame;
+	for (frame = 0; frame < max_frames; frame++)
+	{
+		ULONG64 imagebase;
+		PRUNTIME_FUNCTION rtfunc = RtlLookupFunctionEntry(context.Rip, &imagebase, &history);
+
+		KNONVOLATILE_CONTEXT_POINTERS nvcontext;
+		memset(&nvcontext, 0, sizeof(KNONVOLATILE_CONTEXT_POINTERS));
+		if (!rtfunc)
+		{
+			// Leaf function
+			context.Rip = (ULONG64)(*(PULONG64)context.Rsp);
+			context.Rsp += 8;
+		}
+		else
+		{
+			RtlVirtualUnwind(UNW_FLAG_NHANDLER, imagebase, context.Rip, rtfunc, &context, &handlerdata, &establisherframe, &nvcontext);
+		}
+
+		if (!context.Rip)
+			break;
+
+		out_frames[frame] = (void*)context.Rip;
+	}
+	return frame;
+
+#elif defined(WIN32)
+	// JIT isn't supported here, so just do nothing.
+	return 0;//return RtlCaptureStackBackTrace(0, MIN(max_frames, 32), out_frames, nullptr);
+#else
+	return backtrace(out_frames, max_frames);
+#endif
+}
+
+#ifdef WIN32
+class NativeSymbolResolver
+{
+public:
+	NativeSymbolResolver() { SymInitialize(GetCurrentProcess(), nullptr, TRUE); }
+	~NativeSymbolResolver() { SymCleanup(GetCurrentProcess()); }
+
+	FString GetName(void *frame)
+	{
+		FString s;
+
+		unsigned char buffer[sizeof(IMAGEHLP_SYMBOL64) + 128];
+		IMAGEHLP_SYMBOL64 *symbol64 = reinterpret_cast<IMAGEHLP_SYMBOL64*>(buffer);
+		memset(symbol64, 0, sizeof(IMAGEHLP_SYMBOL64) + 128);
+		symbol64->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+		symbol64->MaxNameLength = 128;
+
+		DWORD64 displacement = 0;
+		BOOL result = SymGetSymFromAddr64(GetCurrentProcess(), (DWORD64)frame, &displacement, symbol64);
+		if (result)
+		{
+			IMAGEHLP_LINE64 line64;
+			DWORD displacement = 0;
+			memset(&line64, 0, sizeof(IMAGEHLP_LINE64));
+			line64.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+			result = SymGetLineFromAddr64(GetCurrentProcess(), (DWORD64)frame, &displacement, &line64);
+			if (result)
+			{
+				s.Format("Called from %s at %s, line %d\n", symbol64->Name, line64.FileName, (int)line64.LineNumber);
+			}
+			else
+			{
+				s.Format("Called from %s\n", symbol64->Name);
+			}
+		}
+
+		return s;
+	}
+};
+#else
+class NativeSymbolResolver
+{
+public:
+	FString GetName(void *frame)
+	{
+		FString s;
+		char **strings;
+		void *frames[1] = { frame };
+		strings = backtrace_symbols(frames, 1);
+
+		// Decode the strings
+		char *ptr = strings[0];
+		char *filename = ptr;
+		const char *function = "";
+
+		// Find function name
+		while (*ptr)
+		{
+			if (*ptr == '(')	// Found function name
+			{
+				*(ptr++) = 0;
+				function = ptr;
+				break;
+			}
+			ptr++;
+		}
+
+		// Find offset
+		if (function[0])	// Only if function was found
+		{
+			while (*ptr)
+			{
+				if (*ptr == '+')	// Found function offset
+				{
+					*(ptr++) = 0;
+					break;
+				}
+				if (*ptr == ')')	// Not found function offset, but found, end of function
+				{
+					*(ptr++) = 0;
+					break;
+				}
+				ptr++;
+			}
+		}
+
+		int status;
+		char *new_function = abi::__cxa_demangle(function, nullptr, nullptr, &status);
+		if (new_function)	// Was correctly decoded
+		{
+			function = new_function;
+		}
+
+		s.Format("Called from %s at %s\n", function, filename);
+
+		if (new_function)
+		{
+			free(new_function);
+		}
+
+		free(strings);
+		return s;
+	}
+};
+#endif
+
+int JITPCToLine(uint8_t *pc, const JitFuncInfo *info)
+{
+	int PCIndex = int(pc - ((uint8_t *) (info->start)));
+	if (info->LineInfo.Size () == 1) return info->LineInfo[0].LineNumber;
+	for (unsigned i = 1; i < info->LineInfo.Size (); i++)
+	{
+		if (info->LineInfo[i].InstructionIndex >= PCIndex)
+		{
+			return info->LineInfo[i - 1].LineNumber;
+		}
+	}
+	return -1;
+}
+
+FString JitGetStackFrameName(NativeSymbolResolver *nativeSymbols, void *pc)
+{
+	for (unsigned int i = 0; i < JitDebugInfo.Size(); i++)
+	{
+		const auto &info = JitDebugInfo[i];
+		if (pc >= info.start && pc < info.end)
+		{
+			int line = JITPCToLine ((uint8_t *)pc, &info);
+
+			FString s;
+
+			if (line == -1)
+				s.Format("Called from %s at %s\n", info.name.GetChars(), info.filename.GetChars());
+			else
+				s.Format("Called from %s at %s, line %d\n", info.name.GetChars(), info.filename.GetChars(), line);
+
+			return s;
+		}
+	}
+
+	return nativeSymbols ? nativeSymbols->GetName(pc) : FString();
+}
+
+FString JitCaptureStackTrace(int framesToSkip, bool includeNativeFrames)
+{
+	void *frames[32];
+	int numframes = CaptureStackTrace(32, frames);
+
+	std::unique_ptr<NativeSymbolResolver> nativeSymbols;
+	if (includeNativeFrames)
+		nativeSymbols.reset(new NativeSymbolResolver());
+
+	FString s;
+	for (int i = framesToSkip + 1; i < numframes; i++)
+	{
+		s += JitGetStackFrameName(nativeSymbols.get(), frames[i]);
+	}
+	return s;
+}
diff --git a/source/common/scripting/jit/jit_store.cpp b/source/common/scripting/jit/jit_store.cpp
new file mode 100644
index 000000000..6dc1a45a9
--- /dev/null
+++ b/source/common/scripting/jit/jit_store.cpp
@@ -0,0 +1,176 @@
+
+#include "jitintern.h"
+
+void JitCompiler::EmitSB()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::byte_ptr(regA[A], konstd[C]), regD[B].r8Lo());
+}
+
+void JitCompiler::EmitSB_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::byte_ptr(regA[A], regD[C]), regD[B].r8Lo());
+}
+
+void JitCompiler::EmitSH()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::word_ptr(regA[A], konstd[C]), regD[B].r16());
+}
+
+void JitCompiler::EmitSH_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::word_ptr(regA[A], regD[C]), regD[B].r16());
+}
+
+void JitCompiler::EmitSW()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::dword_ptr(regA[A], konstd[C]), regD[B]);
+}
+
+void JitCompiler::EmitSW_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::dword_ptr(regA[A], regD[C]), regD[B]);
+}
+
+void JitCompiler::EmitSSP()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp = newTempXmmSd();
+	cc.xorpd(tmp, tmp);
+	cc.cvtsd2ss(tmp, regF[B]);
+	cc.movss(asmjit::x86::dword_ptr(regA[A], konstd[C]), tmp);
+}
+
+void JitCompiler::EmitSSP_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp = newTempXmmSd();
+	cc.xorpd(tmp, tmp);
+	cc.cvtsd2ss(tmp, regF[B]);
+	cc.movss(asmjit::x86::dword_ptr(regA[A], regD[C]), tmp);
+}
+
+void JitCompiler::EmitSDP()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.movsd(asmjit::x86::qword_ptr(regA[A], konstd[C]), regF[B]);
+}
+
+void JitCompiler::EmitSDP_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.movsd(asmjit::x86::qword_ptr(regA[A], regD[C]), regF[B]);
+}
+
+void JitCompiler::EmitSS()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto ptr = newTempIntPtr();
+	cc.lea(ptr, asmjit::x86::ptr(regA[A], konstd[C]));
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, ptr);
+	call->setArg(1, regS[B]);
+}
+
+void JitCompiler::EmitSS_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto ptr = newTempIntPtr();
+	cc.lea(ptr, asmjit::x86::ptr(regA[A], regD[C]));
+	auto call = CreateCall<void, FString*, FString*>(&JitCompiler::CallAssignString);
+	call->setArg(0, ptr);
+	call->setArg(1, regS[B]);
+}
+
+void JitCompiler::EmitSO()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::ptr(regA[A], konstd[C]), regA[B]);
+
+	typedef void(*FuncPtr)(DObject*);
+	auto call = CreateCall<void, DObject*>(static_cast<FuncPtr>(GC::WriteBarrier));
+	call->setArg(0, regA[B]);
+}
+
+void JitCompiler::EmitSO_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::ptr(regA[A], regD[C]), regA[B]);
+
+	typedef void(*FuncPtr)(DObject*);
+	auto call = CreateCall<void, DObject*>(static_cast<FuncPtr>(GC::WriteBarrier));
+	call->setArg(0, regA[B]);
+}
+
+void JitCompiler::EmitSP()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::ptr(regA[A], konstd[C]), regA[B]);
+}
+
+void JitCompiler::EmitSP_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	cc.mov(asmjit::x86::ptr(regA[A], regD[C]), regA[B]);
+}
+
+void JitCompiler::EmitSV2()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, regA[A]);
+	cc.add(tmp, konstd[C]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
+}
+
+void JitCompiler::EmitSV2_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, regA[A]);
+	cc.add(tmp, regD[C]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
+}
+
+void JitCompiler::EmitSV3()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, regA[A]);
+	cc.add(tmp, konstd[C]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp, 16), regF[B + 2]);
+}
+
+void JitCompiler::EmitSV3_R()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp = newTempIntPtr();
+	cc.mov(tmp, regA[A]);
+	cc.add(tmp, regD[C]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]);
+	cc.movsd(asmjit::x86::qword_ptr(tmp, 16), regF[B + 2]);
+}
+
+void JitCompiler::EmitSBIT()
+{
+	EmitNullPointerThrow(A, X_WRITE_NIL);
+	auto tmp1 = newTempInt32();
+	auto tmp2 = newTempInt32();
+	cc.mov(tmp1, asmjit::x86::byte_ptr(regA[A]));
+	cc.mov(tmp2, tmp1);
+	cc.or_(tmp1, (int)C);
+	cc.and_(tmp2, ~(int)C);
+	cc.test(regD[B], regD[B]);
+	cc.cmove(tmp1, tmp2);
+	cc.mov(asmjit::x86::byte_ptr(regA[A]), tmp1);
+}
diff --git a/source/common/scripting/jit/jitintern.h b/source/common/scripting/jit/jitintern.h
new file mode 100644
index 000000000..ac3d8acf5
--- /dev/null
+++ b/source/common/scripting/jit/jitintern.h
@@ -0,0 +1,337 @@
+
+#include "jit.h"
+
+#include "types.h"
+#include "stats.h"
+
+// To do: get cmake to define these..
+#define ASMJIT_BUILD_EMBED
+#define ASMJIT_STATIC
+
+#include <asmjit/asmjit.h>
+#include <asmjit/x86.h>
+#include <functional>
+#include <vector>
+
+extern cycle_t VMCycles[10];
+extern int VMCalls[10];
+
+#define A				(pc[0].a)
+#define B				(pc[0].b)
+#define C				(pc[0].c)
+#define Cs				(pc[0].cs)
+#define BC				(pc[0].i16u)
+#define BCs				(pc[0].i16)
+#define ABCs			(pc[0].i24)
+#define JMPOFS(x)		((x)->i24)
+
+struct JitLineInfo
+{
+	ptrdiff_t InstructionIndex = 0;
+	int32_t LineNumber = -1;
+	asmjit::Label Label;
+};
+
+class JitCompiler
+{
+public:
+	JitCompiler(asmjit::CodeHolder *code, VMScriptFunction *sfunc) : cc(code), sfunc(sfunc) { }
+
+	asmjit::CCFunc *Codegen();
+	VMScriptFunction *GetScriptFunction() { return sfunc; }
+
+	TArray<JitLineInfo> LineInfo;
+
+private:
+	// Declare EmitXX functions for the opcodes:
+	#define xx(op, name, mode, alt, kreg, ktype)	void Emit##op();
+	#include "vmops.h"
+	#undef xx
+
+	asmjit::FuncSignature CreateFuncSignature();
+
+	void Setup();
+	void CreateRegisters();
+	void IncrementVMCalls();
+	void SetupFrame();
+	void SetupSimpleFrame();
+	void SetupFullVMFrame();
+	void BindLabels();
+	void EmitOpcode();
+	void EmitPopFrame();
+
+	void EmitNativeCall(VMNativeFunction *target);
+	void EmitVMCall(asmjit::X86Gp ptr, VMFunction *target);
+	void EmitVtbl(const VMOP *op);
+
+	int StoreCallParams();
+	void LoadInOuts();
+	void LoadReturns(const VMOP *retval, int numret);
+	void FillReturns(const VMOP *retval, int numret);
+	void LoadCallResult(int type, int regnum, bool addrof);
+
+	template <typename Func>
+	void EmitComparisonOpcode(Func jmpFunc)
+	{
+		using namespace asmjit;
+
+		int i = (int)(ptrdiff_t)(pc - sfunc->Code);
+
+		auto successLabel = cc.newLabel();
+
+		auto failLabel = GetLabel(i + 2 + JMPOFS(pc + 1));
+
+		jmpFunc(static_cast<bool>(A & CMP_CHECK), failLabel, successLabel);
+
+		cc.bind(successLabel);
+		pc++; // This instruction uses two instruction slots - skip the next one
+	}
+
+	template<int N>
+	void EmitVectorComparison(bool check, asmjit::Label& fail, asmjit::Label& success)
+	{
+		bool approx = static_cast<bool>(A & CMP_APPROX);
+		if (!approx)
+		{
+			for (int i = 0; i < N; i++)
+			{
+				cc.ucomisd(regF[B + i], regF[C + i]);
+				if (check)
+				{
+					cc.jp(success);
+					if (i == (N - 1))
+					{
+						cc.je(fail);
+					}
+					else
+					{
+						cc.jne(success);
+					}
+				}
+				else
+				{
+					cc.jp(fail);
+					cc.jne(fail);
+				}
+			}
+		}
+		else
+		{
+			auto tmp = newTempXmmSd();
+
+			const int64_t absMaskInt = 0x7FFFFFFFFFFFFFFF;
+			auto absMask = cc.newDoubleConst(asmjit::kConstScopeLocal, reinterpret_cast<const double&>(absMaskInt));
+			auto absMaskXmm = newTempXmmPd();
+
+			auto epsilon = cc.newDoubleConst(asmjit::kConstScopeLocal, VM_EPSILON);
+			auto epsilonXmm = newTempXmmSd();
+
+			for (int i = 0; i < N; i++)
+			{
+				cc.movsd(tmp, regF[B + i]);
+				cc.subsd(tmp, regF[C + i]);
+				cc.movsd(absMaskXmm, absMask);
+				cc.andpd(tmp, absMaskXmm);
+				cc.movsd(epsilonXmm, epsilon);
+				cc.ucomisd(epsilonXmm, tmp);
+
+				if (check)
+				{
+					cc.jp(success);
+					if (i == (N - 1))
+					{
+						cc.ja(fail);
+					}
+					else
+					{
+						cc.jna(success);
+					}
+				}
+				else
+				{
+					cc.jp(fail);
+					cc.jna(fail);
+				}
+			}
+		}
+	}
+
+	static uint64_t ToMemAddress(const void *d)
+	{
+		return (uint64_t)(ptrdiff_t)d;
+	}
+
+	void CallSqrt(const asmjit::X86Xmm &a, const asmjit::X86Xmm &b);
+
+	static void CallAssignString(FString* to, FString* from) {
+		*to = *from;
+	}
+
+	template<typename RetType, typename P1>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1)>(func))), asmjit::FuncSignature1<RetType, P1>()); }
+
+	template<typename RetType, typename P1, typename P2>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2)>(func))), asmjit::FuncSignature2<RetType, P1, P2>()); }
+
+	template<typename RetType, typename P1, typename P2, typename P3>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3)>(func))), asmjit::FuncSignature3<RetType, P1, P2, P3>()); }
+
+	template<typename RetType, typename P1, typename P2, typename P3, typename P4>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4)>(func))), asmjit::FuncSignature4<RetType, P1, P2, P3, P4>()); }
+
+	template<typename RetType, typename P1, typename P2, typename P3, typename P4, typename P5>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5)>(func))), asmjit::FuncSignature5<RetType, P1, P2, P3, P4, P5>()); }
+
+	template<typename RetType, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6)>(func))), asmjit::FuncSignature6<RetType, P1, P2, P3, P4, P5, P6>()); }
+
+	template<typename RetType, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7>
+	asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7)) { return cc.call(asmjit::imm_ptr(reinterpret_cast<void*>(static_cast<RetType(*)(P1, P2, P3, P4, P5, P6, P7)>(func))), asmjit::FuncSignature7<RetType, P1, P2, P3, P4, P5, P6, P7>()); }
+
+	FString regname;
+	size_t tmpPosInt32, tmpPosInt64, tmpPosIntPtr, tmpPosXmmSd, tmpPosXmmSs, tmpPosXmmPd, resultPosInt32, resultPosIntPtr, resultPosXmmSd;
+	std::vector<asmjit::X86Gp> regTmpInt32, regTmpInt64, regTmpIntPtr, regResultInt32, regResultIntPtr;
+	std::vector<asmjit::X86Xmm> regTmpXmmSd, regTmpXmmSs, regTmpXmmPd, regResultXmmSd;
+
+	void ResetTemp()
+	{
+		tmpPosInt32 = 0;
+		tmpPosInt64 = 0;
+		tmpPosIntPtr = 0;
+		tmpPosXmmSd = 0;
+		tmpPosXmmSs = 0;
+		tmpPosXmmPd = 0;
+		resultPosInt32 = 0;
+		resultPosIntPtr = 0;
+		resultPosXmmSd = 0;
+	}
+
+	template<typename T, typename NewFunc>
+	T newTempRegister(std::vector<T> &tmpVector, size_t &tmpPos, const char *name, NewFunc newCallback)
+	{
+		if (tmpPos == tmpVector.size())
+		{
+			regname.Format("%s%d", name, (int)tmpVector.size());
+			tmpVector.push_back(newCallback(regname.GetChars()));
+		}
+		return tmpVector[tmpPos++];
+	}
+
+	asmjit::X86Gp newTempInt32() { return newTempRegister(regTmpInt32, tmpPosInt32, "tmpDword", [&](const char *name) { return cc.newInt32(name); }); }
+	asmjit::X86Gp newTempInt64() { return newTempRegister(regTmpInt64, tmpPosInt64, "tmpQword", [&](const char *name) { return cc.newInt64(name); }); }
+	asmjit::X86Gp newTempIntPtr() { return newTempRegister(regTmpIntPtr, tmpPosIntPtr, "tmpPtr", [&](const char *name) { return cc.newIntPtr(name); }); }
+	asmjit::X86Xmm newTempXmmSd() { return newTempRegister(regTmpXmmSd, tmpPosXmmSd, "tmpXmmSd", [&](const char *name) { return cc.newXmmSd(name); }); }
+	asmjit::X86Xmm newTempXmmSs() { return newTempRegister(regTmpXmmSs, tmpPosXmmSs, "tmpXmmSs", [&](const char *name) { return cc.newXmmSs(name); }); }
+	asmjit::X86Xmm newTempXmmPd() { return newTempRegister(regTmpXmmPd, tmpPosXmmPd, "tmpXmmPd", [&](const char *name) { return cc.newXmmPd(name); }); }
+
+	asmjit::X86Gp newResultInt32() { return newTempRegister(regResultInt32, resultPosInt32, "resultDword", [&](const char *name) { return cc.newInt32(name); }); }
+	asmjit::X86Gp newResultIntPtr() { return newTempRegister(regResultIntPtr, resultPosIntPtr, "resultPtr", [&](const char *name) { return cc.newIntPtr(name); }); }
+	asmjit::X86Xmm newResultXmmSd() { return newTempRegister(regResultXmmSd, resultPosXmmSd, "resultXmmSd", [&](const char *name) { return cc.newXmmSd(name); }); }
+
+	void EmitReadBarrier();
+
+	void EmitNullPointerThrow(int index, EVMAbortException reason);
+	void EmitThrowException(EVMAbortException reason);
+	asmjit::Label EmitThrowExceptionLabel(EVMAbortException reason);
+
+	static void ThrowArrayOutOfBounds(int index, int size);
+	static void ThrowException(int reason);
+
+	asmjit::X86Gp CheckRegD(int r0, int r1);
+	asmjit::X86Xmm CheckRegF(int r0, int r1);
+	asmjit::X86Xmm CheckRegF(int r0, int r1, int r2);
+	asmjit::X86Xmm CheckRegF(int r0, int r1, int r2, int r3);
+	asmjit::X86Gp CheckRegS(int r0, int r1);
+	asmjit::X86Gp CheckRegA(int r0, int r1);
+
+	asmjit::X86Compiler cc;
+	VMScriptFunction *sfunc;
+
+	asmjit::CCFunc *func = nullptr;
+	asmjit::X86Gp args;
+	asmjit::X86Gp numargs;
+	asmjit::X86Gp ret;
+	asmjit::X86Gp numret;
+	asmjit::X86Gp stack;
+
+	int offsetParams;
+	int offsetF;
+	int offsetS;
+	int offsetA;
+	int offsetD;
+	int offsetExtra;
+
+	TArray<const VMOP *> ParamOpcodes;
+
+	void CheckVMFrame();
+	asmjit::X86Gp GetCallReturns();
+
+	bool vmframeAllocated = false;
+	asmjit::CBNode *vmframeCursor = nullptr;
+	asmjit::X86Gp vmframe;
+
+	bool callReturnsAllocated = false;
+	asmjit::CBNode *callReturnsCursor = nullptr;
+	asmjit::X86Gp callReturns;
+
+	const int *konstd;
+	const double *konstf;
+	const FString *konsts;
+	const FVoidObj *konsta;
+
+	TArray<asmjit::X86Gp> regD;
+	TArray<asmjit::X86Xmm> regF;
+	TArray<asmjit::X86Gp> regA;
+	TArray<asmjit::X86Gp> regS;
+
+	struct OpcodeLabel
+	{
+		asmjit::CBNode *cursor = nullptr;
+		asmjit::Label label;
+		bool inUse = false;
+	};
+
+	asmjit::Label GetLabel(size_t pos)
+	{
+		auto &label = labels[pos];
+		if (!label.inUse)
+		{
+			label.label = cc.newLabel();
+			label.inUse = true;
+		}
+		return label.label;
+	}
+
+	TArray<OpcodeLabel> labels;
+
+	const VMOP *pc;
+	VM_UBYTE op;
+};
+
+class AsmJitException : public std::exception
+{
+public:
+	AsmJitException(asmjit::Error error, const char *message) noexcept : error(error), message(message)
+	{
+	}
+
+	const char* what() const noexcept override
+	{
+		return message.GetChars();
+	}
+
+	asmjit::Error error;
+	FString message;
+};
+
+class ThrowingErrorHandler : public asmjit::ErrorHandler
+{
+public:
+	bool handleError(asmjit::Error err, const char *message, asmjit::CodeEmitter *origin) override
+	{
+		throw AsmJitException(err, message);
+	}
+};
+
+void *AddJitFunction(asmjit::CodeHolder* code, JitCompiler *compiler);
+asmjit::CodeInfo GetHostCodeInfo();
diff --git a/source/common/scripting/vm/vm.h b/source/common/scripting/vm/vm.h
new file mode 100644
index 000000000..cb5c2dab5
--- /dev/null
+++ b/source/common/scripting/vm/vm.h
@@ -0,0 +1,790 @@
+/*
+** vm.h
+** VM <-> native interface
+**
+**---------------------------------------------------------------------------
+** Copyright -2016 Randy Heit
+** Copyright 2016 Christoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#ifndef VM_H
+#define VM_H
+
+#include "autosegs.h"
+#include "zstring.h"
+#include "vectors.h"
+#include "cmdlib.h"
+#include "engineerrors.h"
+#include "memarena.h"
+#include "name.h"
+#include "scopebarrier.h"
+
+class DObject;
+union VMOP;
+class VMScriptFunction;
+
+extern FMemArena ClassDataAllocator;
+
+#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
+
+void JitRelease();
+
+
+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/65536.0)
+
+// Register types for VMParam
+enum
+{
+	REGT_INT		= 0,
+	REGT_FLOAT		= 1,
+	REGT_STRING		= 2,
+	REGT_POINTER	= 3,
+	REGT_TYPE		= 3,
+
+	REGT_KONST		= 4,
+	REGT_MULTIREG2	= 8,
+	REGT_MULTIREG3	= 16,	// (e.g. a vector)
+	REGT_MULTIREG	= 24,
+	REGT_ADDROF		= 32,	// used with PARAM: pass address of this register
+
+	REGT_NIL		= 128	// parameter was omitted
+};
+
+#define RET_FINAL	(0x80)	// Used with RET and RETI in the destination slot: this is the final return value
+
+
+enum EVMAbortException
+{
+	X_OTHER,
+	X_READ_NIL,
+	X_WRITE_NIL,
+	X_TOO_MANY_TRIES,
+	X_ARRAY_OUT_OF_BOUNDS,
+	X_DIVISION_BY_ZERO,
+	X_BAD_SELF,
+	X_FORMAT_ERROR
+};
+
+class CVMAbortException : public CEngineError
+{
+public:
+	static FString stacktrace;
+	CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap);
+	void MaybePrintMessage();
+};
+
+// This must be a separate function because the VC compiler would otherwise allocate memory on the stack for every separate instance of the exception object that may get thrown.
+void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...);
+void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...);
+
+void ClearGlobalVMStack();
+
+struct VMReturn
+{
+	void *Location;
+	VM_UBYTE RegType;	// Same as VMParam RegType, except REGT_KONST is invalid; only used by asserts
+
+	void SetInt(int val)
+	{
+		assert(RegType == REGT_INT);
+		*(int *)Location = val;
+	}
+	void SetFloat(double val)
+	{
+		assert(RegType == REGT_FLOAT);
+		*(double *)Location = val;
+	}
+	void SetVector(const double val[3])
+	{	
+		assert(RegType == (REGT_FLOAT|REGT_MULTIREG3));
+		((double *)Location)[0] = val[0];
+		((double *)Location)[1] = val[1];
+		((double *)Location)[2] = val[2];
+	}
+	void SetVector(const DVector3 &val)
+	{
+		assert(RegType == (REGT_FLOAT | REGT_MULTIREG3));
+		((double *)Location)[0] = val[0];
+		((double *)Location)[1] = val[1];
+		((double *)Location)[2] = val[2];
+	}
+	void SetVector2(const double val[2])
+	{
+		assert(RegType == (REGT_FLOAT|REGT_MULTIREG2));
+		((double *)Location)[0] = val[0];
+		((double *)Location)[1] = val[1];
+	}
+	void SetVector2(const DVector2 &val)
+	{
+		assert(RegType == (REGT_FLOAT | REGT_MULTIREG2));
+		((double *)Location)[0] = val[0];
+		((double *)Location)[1] = val[1];
+	}
+	void SetString(const FString &val)
+	{
+		assert(RegType == REGT_STRING);
+		*(FString *)Location = val;
+	}
+
+	void SetPointer(void *val)
+	{
+		assert(RegType == REGT_POINTER);
+		*(void **)Location = val;
+	}
+
+	void SetObject(DObject *val)
+	{
+		assert(RegType == REGT_POINTER);
+		*(void **)Location = val;
+	}
+
+	void IntAt(int *loc)
+	{
+		Location = loc;
+		RegType = REGT_INT;
+	}
+	void FloatAt(double *loc)
+	{
+		Location = loc;
+		RegType = REGT_FLOAT;
+	}
+	void Vec2At(DVector2 *loc)
+	{
+		Location = loc;
+		RegType = REGT_FLOAT | REGT_MULTIREG2;
+	}
+	void StringAt(FString *loc)
+	{
+		Location = loc;
+		RegType = REGT_STRING;
+	}
+	void PointerAt(void **loc)
+	{
+		Location = loc;
+		RegType = REGT_POINTER;
+	}
+	VMReturn() { }
+	VMReturn(int *loc) { IntAt(loc); }
+	VMReturn(double *loc) { FloatAt(loc); }
+	VMReturn(DVector2 *loc) { Vec2At(loc); }
+	VMReturn(FString *loc) { StringAt(loc); }
+	VMReturn(void **loc) { PointerAt(loc); }
+};
+
+struct VMRegisters;
+
+struct TypedVMValue
+{
+	union
+	{
+		int i;
+		void *a;
+		double f;
+		struct { int pad[3]; VM_UBYTE Type; };
+		struct { int foo[4]; } biggest;
+		const FString *sp;
+	};
+
+	const FString &s() const { return *sp; }
+
+	TypedVMValue()
+	{
+		a = NULL;
+		Type = REGT_NIL;
+	}
+	TypedVMValue(const TypedVMValue &o)
+	{
+		biggest = o.biggest;
+	}
+	TypedVMValue(int v)
+	{
+		i = v;
+		Type = REGT_INT;
+	}
+	TypedVMValue(double v)
+	{
+		f = v;
+		Type = REGT_FLOAT;
+	}
+
+	TypedVMValue(const FString *s)
+	{
+		sp = s;
+		Type = REGT_STRING;
+	}
+	TypedVMValue(DObject *v)
+	{
+		a = v;
+		Type = REGT_POINTER;
+	}
+	TypedVMValue(void *v)
+	{
+		a = v;
+		Type = REGT_POINTER;
+	}
+	TypedVMValue &operator=(const TypedVMValue &o)
+	{
+		biggest = o.biggest;
+		return *this;
+	}
+	TypedVMValue &operator=(int v)
+	{
+		i = v;
+		Type = REGT_INT;
+		return *this;
+	}
+	TypedVMValue &operator=(double v)
+	{
+		f = v;
+		Type = REGT_FLOAT;
+		return *this;
+	}
+	TypedVMValue &operator=(const FString *v)
+	{
+		sp = v;
+		Type = REGT_STRING;
+		return *this;
+	}
+	TypedVMValue &operator=(DObject *v)
+	{
+		a = v;
+		Type = REGT_POINTER;
+		return *this;
+	}
+};
+
+
+struct VMValue
+{
+	union
+	{
+		int i;
+		void *a;
+		double f;
+		struct { int foo[2]; } biggest;
+		const FString *sp;
+	};
+
+	const FString &s() const { return *sp; }
+
+	VMValue()
+	{
+		a = NULL;
+	}
+	VMValue(const VMValue &o)
+	{
+		biggest = o.biggest;
+	}
+	VMValue(int v)
+	{
+		i = v;
+	}
+	VMValue(double v)
+	{
+		f = v;
+	}
+	VMValue(const char *s) = delete;
+	VMValue(const FString &s) = delete;
+
+	VMValue(const FString *s)
+	{
+		sp = s;
+	}
+	VMValue(void *v)
+	{
+		a = v;
+	}
+	VMValue &operator=(const VMValue &o)
+	{
+		biggest = o.biggest;
+		return *this;
+	}
+	VMValue &operator=(const TypedVMValue &o)
+	{
+		memcpy(&biggest, &o.biggest, sizeof(biggest));
+		return *this;
+	}
+	VMValue &operator=(int v)
+	{
+		i = v;
+		return *this;
+	}
+	VMValue &operator=(double v)
+	{
+		f = v;
+		return *this;
+	}
+	VMValue &operator=(const FString *v)
+	{
+		sp = v;
+		return *this;
+	}
+	VMValue &operator=(const FString &v) = delete;
+	VMValue &operator=(const char *v) = delete;
+	VMValue &operator=(DObject *v)
+	{
+		a = v;
+		return *this;
+	}
+	int ToInt(int Type)
+	{
+		if (Type == REGT_INT)
+		{
+			return i;
+		}
+		if (Type == REGT_FLOAT)
+		{
+			return int(f);
+		}
+		if (Type == REGT_STRING)
+		{
+			return (int)s().ToLong();
+		}
+		// FIXME
+		return 0;
+	}
+	double ToDouble(int Type)
+	{
+		if (Type == REGT_FLOAT)
+		{
+			return f;
+		}
+		if (Type == REGT_INT)
+		{
+			return i;
+		}
+		if (Type == REGT_STRING)
+		{
+			return s().ToDouble();
+		}
+		// FIXME
+		return 0;
+	}
+};
+
+class VMFunction
+{
+public:
+	bool Unsafe = false;
+	uint8_t ImplicitArgs = 0;	// either 0 for static, 1 for method or 3 for action
+	int VarFlags = 0; // [ZZ] this replaces 5+ bool fields
+	unsigned VirtualIndex = ~0u;
+	FName Name;
+	const uint8_t *RegTypes = nullptr;
+	TArray<TypedVMValue> DefaultArgs;
+	FString PrintableName;	// so that the VM can print meaningful info if something in this function goes wrong.
+
+	class PPrototype *Proto;
+	TArray<uint32_t> ArgFlags;		// Should be the same length as Proto->ArgumentTypes
+
+	int(*ScriptCall)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret) = nullptr;
+
+	VMFunction(FName name = NAME_None) : ImplicitArgs(0), Name(name), Proto(NULL)
+	{
+		AllFunctions.Push(this);
+	}
+	virtual ~VMFunction() {}
+
+	void *operator new(size_t size)
+	{
+		return ClassDataAllocator.Alloc(size);
+	}
+
+	void operator delete(void *block) {}
+	void operator delete[](void *block) {}
+	static void DeleteAll()
+	{
+		for (auto f : AllFunctions)
+		{
+			f->~VMFunction();
+		}
+		AllFunctions.Clear();
+		// also release any JIT data
+		JitRelease();
+	}
+	static void CreateRegUseInfo()
+	{
+		for (auto f : AllFunctions)
+		{
+			f->CreateRegUse();
+		}
+	}
+	static TArray<VMFunction *> AllFunctions;
+protected:
+	void CreateRegUse();
+};
+
+// Use this in the prototype for a native function.
+
+#ifdef NDEBUG
+#define VM_ARGS			VMValue *param, int numparam, VMReturn *ret, int numret
+#define VM_ARGS_NAMES	param, numparam, ret, numret
+#define VM_INVOKE(param, numparam, ret, numret, reginfo) (param), (numparam), (ret), (numret)
+#else
+#define VM_ARGS			VMValue *param, int numparam, VMReturn *ret, int numret, const uint8_t *reginfo
+#define VM_ARGS_NAMES	param, numparam, ret, numret, reginfo
+#define VM_INVOKE(param, numparam, ret, numret, reginfo) (param), (numparam), (ret), (numret), (reginfo)
+#endif
+
+class VMNativeFunction : public VMFunction
+{
+public:
+	typedef int (*NativeCallType)(VM_ARGS);
+
+	// 8 is VARF_Native. I can't write VARF_Native because of circular references between this and dobject/dobjtype.
+	VMNativeFunction() : NativeCall(NULL) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
+	VMNativeFunction(NativeCallType call) : NativeCall(call) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
+	VMNativeFunction(NativeCallType call, FName name) : VMFunction(name), NativeCall(call) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
+
+	// Return value is the number of results.
+	NativeCallType NativeCall;
+
+	// Function pointer to a native function to be called directly by the JIT using the platform calling convention
+	void *DirectNativeCall = nullptr;
+
+private:
+	static int NativeScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
+};
+
+int VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap = NULL*/);
+int VMCallWithDefaults(VMFunction *func, TArray<VMValue> &params, VMReturn *results, int numresults/*, VMException **trap = NULL*/);
+
+inline int VMCallAction(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap = NULL*/)
+{
+	return VMCall(func, params, numparams, results, numresults);
+}
+
+// Use these to collect the parameters in a native function.
+// variable name <x> at position <p>
+void NullParam(const char *varname);
+
+#ifndef NDEBUG
+bool AssertObject(void * ob);
+#endif
+
+#define PARAM_NULLCHECK(ptr, var) (ptr == nullptr? NullParam(#var), ptr : ptr)
+
+// This cannot assert because there is no info for varargs
+#define PARAM_VA_POINTER(x)			const uint8_t *x = (const uint8_t *)param[numparam-1].a;
+
+// For required parameters.
+#define PARAM_INT_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); int x = param[p].i;
+#define PARAM_UINT_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); unsigned x = param[p].i;
+#define PARAM_BOOL_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); bool x = !!param[p].i;
+#define PARAM_NAME_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); FName x = ENamedName(param[p].i);
+#define PARAM_SOUND_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); FSoundID x = param[p].i;
+#define PARAM_COLOR_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); PalEntry x; x.d = param[p].i;
+#define PARAM_FLOAT_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); double x = param[p].f;
+#define PARAM_ANGLE_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); DAngle x = param[p].f;
+#define PARAM_STRING_VAL_AT(p,x)	assert((p) < numparam); assert(reginfo[p] == REGT_STRING); FString x = param[p].s();
+#define PARAM_STRING_AT(p,x)		assert((p) < numparam); assert(reginfo[p] == REGT_STRING); const FString &x = param[p].s();
+#define PARAM_STATELABEL_AT(p,x)	assert((p) < numparam); assert(reginfo[p] == REGT_INT); int x = param[p].i;
+#define PARAM_STATE_AT(p,x)			assert((p) < numparam); assert(reginfo[p] == REGT_INT); FState *x = (FState *)StateLabels.GetState(param[p].i, self->GetClass());
+#define PARAM_STATE_ACTION_AT(p,x)	assert((p) < numparam); assert(reginfo[p] == REGT_INT); FState *x = (FState *)StateLabels.GetState(param[p].i, stateowner->GetClass());
+#define PARAM_POINTER_AT(p,x,type)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type *x = (type *)param[p].a;
+#define PARAM_OUTPOINTER_AT(p,x,type)	assert((p) < numparam); type *x = (type *)param[p].a;
+#define PARAM_POINTERTYPE_AT(p,x,type)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type x = (type )param[p].a;
+#define PARAM_OBJECT_AT(p,x,type)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER && AssertObject(param[p].a)); type *x = (type *)param[p].a; assert(x == NULL || x->IsKindOf(RUNTIME_CLASS(type)));
+#define PARAM_CLASS_AT(p,x,base)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); base::MetaClass *x = (base::MetaClass *)param[p].a; assert(x == NULL || x->IsDescendantOf(RUNTIME_CLASS(base)));
+#define PARAM_POINTER_NOT_NULL_AT(p,x,type)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type *x = (type *)PARAM_NULLCHECK(param[p].a, #x);
+#define PARAM_OBJECT_NOT_NULL_AT(p,x,type)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER && (AssertObject(param[p].a))); type *x = (type *)PARAM_NULLCHECK(param[p].a, #x); assert(x == NULL || x->IsKindOf(RUNTIME_CLASS(type)));
+#define PARAM_CLASS_NOT_NULL_AT(p,x,base)	assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); base::MetaClass *x = (base::MetaClass *)PARAM_NULLCHECK(param[p].a, #x); assert(x == NULL || x->IsDescendantOf(RUNTIME_CLASS(base)));
+
+
+// The above, but with an automatically increasing position index.
+#define PARAM_PROLOGUE				int paramnum = -1;
+
+#define PARAM_INT(x)				++paramnum; PARAM_INT_AT(paramnum,x)
+#define PARAM_UINT(x)				++paramnum; PARAM_UINT_AT(paramnum,x)
+#define PARAM_BOOL(x)				++paramnum; PARAM_BOOL_AT(paramnum,x)
+#define PARAM_NAME(x)				++paramnum; PARAM_NAME_AT(paramnum,x)
+#define PARAM_SOUND(x)				++paramnum; PARAM_SOUND_AT(paramnum,x)
+#define PARAM_COLOR(x)				++paramnum; PARAM_COLOR_AT(paramnum,x)
+#define PARAM_FLOAT(x)				++paramnum; PARAM_FLOAT_AT(paramnum,x)
+#define PARAM_ANGLE(x)				++paramnum; PARAM_ANGLE_AT(paramnum,x)
+#define PARAM_STRING(x)				++paramnum; PARAM_STRING_AT(paramnum,x)
+#define PARAM_STRING_VAL(x)				++paramnum; PARAM_STRING_VAL_AT(paramnum,x)
+#define PARAM_STATELABEL(x)				++paramnum; PARAM_STATELABEL_AT(paramnum,x)
+#define PARAM_STATE(x)				++paramnum; PARAM_STATE_AT(paramnum,x)
+#define PARAM_STATE_ACTION(x)		++paramnum; PARAM_STATE_ACTION_AT(paramnum,x)
+#define PARAM_POINTER(x,type)		++paramnum; PARAM_POINTER_AT(paramnum,x,type)
+#define PARAM_OUTPOINTER(x,type)		++paramnum; PARAM_OUTPOINTER_AT(paramnum,x,type)
+#define PARAM_POINTERTYPE(x,type)	++paramnum; PARAM_POINTERTYPE_AT(paramnum,x,type)
+#define PARAM_OBJECT(x,type)		++paramnum; PARAM_OBJECT_AT(paramnum,x,type)
+#define PARAM_CLASS(x,base)			++paramnum; PARAM_CLASS_AT(paramnum,x,base)
+#define PARAM_CLASS(x,base)			++paramnum; PARAM_CLASS_AT(paramnum,x,base)
+#define PARAM_POINTER_NOT_NULL(x,type)		++paramnum; PARAM_POINTER_NOT_NULL_AT(paramnum,x,type)
+#define PARAM_OBJECT_NOT_NULL(x,type)		++paramnum; PARAM_OBJECT_NOT_NULL_AT(paramnum,x,type)
+#define PARAM_CLASS_NOT_NULL(x,base)		++paramnum; PARAM_CLASS_NOT_NULL_AT(paramnum,x,base)
+
+typedef int(*actionf_p)(VM_ARGS);
+
+struct FieldDesc
+{
+	const char *ClassName;
+	const char *FieldName;
+	size_t FieldOffset;
+	unsigned FieldSize;
+	int BitValue;
+};
+
+namespace
+{
+	// Traits for the types we are interested in
+	template<typename T> struct native_is_valid { static const bool value = false; static const bool retval = false; };
+	template<typename T> struct native_is_valid<T*> { static const bool value = true; static const bool retval = true; };
+	template<typename T> struct native_is_valid<T&> { static const bool value = true; static const bool retval = true; };
+	template<> struct native_is_valid<void> { static const bool value = true; static const bool retval = true; };
+	template<> struct native_is_valid<int> { static const bool value = true;  static const bool retval = true; };
+	template<> struct native_is_valid<unsigned int> { static const bool value = true; static const bool retval = true; };
+	template<> struct native_is_valid<double> { static const bool value = true; static const bool retval = true; };
+	template<> struct native_is_valid<bool> { static const bool value = true; static const bool retval = false;};	// Bool as return does not work!
+}
+
+// Compile time validation of direct native functions
+struct DirectNativeDesc
+{
+	DirectNativeDesc() = default;
+
+	#define TP(n) typename P##n
+	#define VP(n) ValidateType<P##n>()
+	template<typename Ret> DirectNativeDesc(Ret(*func)()) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); }
+	template<typename Ret, TP(1)> DirectNativeDesc(Ret(*func)(P1)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); }
+	template<typename Ret, TP(1), TP(2)> DirectNativeDesc(Ret(*func)(P1,P2)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); }
+	template<typename Ret, TP(1), TP(2), TP(3)> DirectNativeDesc(Ret(*func)(P1,P2,P3)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13); }
+	template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13), TP(14)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13), VP(14); }
+	#undef TP
+	#undef VP
+
+	template<typename T> void ValidateType() { static_assert(native_is_valid<T>::value, "Argument type is not valid as a direct native parameter or return type"); }
+	template<typename T> void ValidateRet() { static_assert(native_is_valid<T>::retval, "Return type is not valid as a direct native parameter or return type"); }
+
+	operator void *() const { return Ptr; }
+
+	void *Ptr;
+};
+
+struct AFuncDesc
+{
+	const char *ClassName;
+	const char *FuncName;
+	actionf_p Function;
+	VMNativeFunction **VMPointer;
+	DirectNativeDesc DirectNative;
+};
+
+#if defined(_MSC_VER)
+#pragma section(".areg$u",read)
+#pragma section(".freg$u",read)
+
+#define MSVC_ASEG __declspec(allocate(".areg$u"))
+#define MSVC_FSEG __declspec(allocate(".freg$u"))
+#define GCC_ASEG
+#define GCC_FSEG
+#else
+#define MSVC_ASEG
+#define MSVC_FSEG
+#define GCC_ASEG __attribute__((section(SECTION_AREG))) __attribute__((used))
+#define GCC_FSEG __attribute__((section(SECTION_FREG))) __attribute__((used))
+#endif
+
+// Macros to handle action functions. These are here so that I don't have to
+// change every single use in case the parameters change.
+
+#define DEFINE_ACTION_FUNCTION_NATIVE(cls, name, native) \
+	static int AF_##cls##_##name(VM_ARGS); \
+	VMNativeFunction *cls##_##name##_VMPtr; \
+	static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr, native }; \
+	extern AFuncDesc const *const cls##_##name##_HookPtr; \
+	MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
+	static int AF_##cls##_##name(VM_ARGS)
+
+#define DEFINE_ACTION_FUNCTION_NATIVE0(cls, name, native) \
+	static int AF_##cls##_##name(VM_ARGS); \
+	VMNativeFunction *cls##_##name##_VMPtr; \
+	static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
+	extern AFuncDesc const *const cls##_##name##_HookPtr; \
+	MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
+	static int AF_##cls##_##name(VM_ARGS)
+
+#define DEFINE_ACTION_FUNCTION(cls, name) \
+	static int AF_##cls##_##name(VM_ARGS); \
+	VMNativeFunction *cls##_##name##_VMPtr; \
+	static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
+	extern AFuncDesc const *const cls##_##name##_HookPtr; \
+	MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
+	static int AF_##cls##_##name(VM_ARGS)
+
+// cls is the scripted class name, icls the internal one (e.g. player_t vs. Player)
+#define DEFINE_FIELD_X(cls, icls, name) \
+	static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \
+	extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name;
+
+// This is for cases where the internal size does not match the part that gets exported.
+#define DEFINE_FIELD_UNSIZED(cls, icls, name) \
+	static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), ~0u, 0 }; \
+	extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name;
+
+#define DEFINE_FIELD_NAMED_X(cls, icls, name, scriptname) \
+	static const FieldDesc VMField_##cls##_##scriptname = { "A" #cls, #scriptname, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \
+	extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
+
+#define DEFINE_FIELD_X_BIT(cls, icls, name, bitval) \
+	static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), bitval }; \
+	extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name;
+
+#define DEFINE_FIELD(cls, name) \
+	static const FieldDesc VMField_##cls##_##name = { #cls, #name, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \
+	extern FieldDesc const *const VMField_##cls##_##name##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##cls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name;
+
+#define DEFINE_FIELD_NAMED(cls, name, scriptname) \
+		static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \
+	extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
+
+#define DEFINE_FIELD_BIT(cls, name, scriptname, bitval) \
+		static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), bitval }; \
+	extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
+
+#define DEFINE_GLOBAL(name) \
+	static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&name, (unsigned)sizeof(name), 0 }; \
+	extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
+
+#define DEFINE_GLOBAL_NAMED(iname, name) \
+	static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&iname, (unsigned)sizeof(iname), 0 }; \
+	extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
+	MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
+
+
+class AActor;
+
+#define ACTION_RETURN_STATE(v) do { FState *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0)
+#define ACTION_RETURN_POINTER(v) do { void *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0)
+#define ACTION_RETURN_OBJECT(v) do { auto state = v; if (numret > 0) { assert(ret != NULL); ret->SetObject(state); return 1; } return 0; } while(0)
+#define ACTION_RETURN_FLOAT(v) do { double u = v; if (numret > 0) { assert(ret != nullptr); ret->SetFloat(u); return 1; } return 0; } while(0)
+#define ACTION_RETURN_VEC2(v) do { DVector2 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector2(u); return 1; } return 0; } while(0)
+#define ACTION_RETURN_VEC3(v) do { DVector3 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector(u); return 1; } return 0; } while(0)
+#define ACTION_RETURN_INT(v) do { int u = v; if (numret > 0) { assert(ret != NULL); ret->SetInt(u); return 1; } return 0; } while(0)
+#define ACTION_RETURN_BOOL(v) ACTION_RETURN_INT(v)
+#define ACTION_RETURN_STRING(v) do { FString u = v; if (numret > 0) { assert(ret != NULL); ret->SetString(u); return 1; } return 0; } while(0)
+
+// Checks to see what called the current action function
+#define ACTION_CALL_FROM_ACTOR() (stateinfo == nullptr || stateinfo->mStateType == STATE_Actor)
+#define ACTION_CALL_FROM_PSPRITE() (self->player && stateinfo != nullptr && stateinfo->mStateType == STATE_Psprite)
+#define ACTION_CALL_FROM_INVENTORY() (stateinfo != nullptr && stateinfo->mStateType == STATE_StateChain)
+
+// Standard parameters for all action functions
+//   self         - Actor this action is to operate on (player if a weapon)
+//   stateowner   - Actor this action really belongs to (may be an item)
+//   callingstate - State this action was called from
+#define PARAM_ACTION_PROLOGUE(type) \
+	PARAM_PROLOGUE; \
+	PARAM_OBJECT_NOT_NULL (self, AActor); \
+	PARAM_OBJECT (stateowner, type) \
+	PARAM_POINTER  (stateinfo, FStateParamInfo) \
+
+// Number of action paramaters
+#define NAP 3
+
+#define PARAM_SELF_PROLOGUE(type) \
+	PARAM_PROLOGUE; \
+	PARAM_OBJECT_NOT_NULL(self, type);
+
+// for structs we cannot do a class validation
+#define PARAM_SELF_STRUCT_PROLOGUE(type) \
+	PARAM_PROLOGUE; \
+	PARAM_POINTER_NOT_NULL(self, type);
+
+class PFunction;
+
+VMFunction *FindVMFunction(PClass *cls, const char *name);
+#define DECLARE_VMFUNC(cls, name) static VMFunction *name; if (name == nullptr) name = FindVMFunction(RUNTIME_CLASS(cls), #name);
+
+FString FStringFormat(VM_ARGS, int offset = 0);
+
+#define IFVM(cls, funcname) \
+	static VMFunction * func = nullptr; \
+	if (func == nullptr) { \
+		PClass::FindFunction(&func, #cls, #funcname); \
+		assert(func); \
+	} \
+	if (func != nullptr)
+
+
+
+unsigned GetVirtualIndex(PClass *cls, const char *funcname);
+
+#define IFVIRTUALPTR(self, cls, funcname) \
+	static unsigned VIndex = ~0u; \
+	if (VIndex == ~0u) { \
+		VIndex = GetVirtualIndex(RUNTIME_CLASS(cls), #funcname); \
+		assert(VIndex != ~0u); \
+	} \
+	auto clss = self->GetClass(); \
+	VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr;  \
+	if (func != nullptr)
+
+#define IFVIRTUAL(cls, funcname) IFVIRTUALPTR(this, cls, funcname)
+
+#define IFVIRTUALPTRNAME(self, cls, funcname) \
+	static unsigned VIndex = ~0u; \
+	if (VIndex == ~0u) { \
+		VIndex = GetVirtualIndex(PClass::FindClass(cls), #funcname); \
+		assert(VIndex != ~0u); \
+	} \
+	auto clss = self->GetClass(); \
+	VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr;  \
+	if (func != nullptr)
+
+#endif
diff --git a/source/common/scripting/vm/vmexec.cpp b/source/common/scripting/vm/vmexec.cpp
new file mode 100644
index 000000000..088988bd7
--- /dev/null
+++ b/source/common/scripting/vm/vmexec.cpp
@@ -0,0 +1,253 @@
+/*
+** vmexec.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright -2016 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include <math.h>
+#include <assert.h>
+#include "v_video.h"
+#include "s_soundinternal.h"
+#include "basics.h"
+//#include "r_state.h"
+#include "stats.h"
+#include "vmintern.h"
+#include "types.h"
+#include "basics.h"
+#include "texturemanager.h"
+
+extern cycle_t VMCycles[10];
+extern int VMCalls[10];
+
+// intentionally implemented in a different source file to prevent inlining.
+#if 0
+void ThrowVMException(VMException *x);
+#endif
+
+#define IMPLEMENT_VMEXEC
+
+#if !defined(COMPGOTO) && defined(__GNUC__)
+#define COMPGOTO 1
+#endif
+
+#if COMPGOTO
+#define OP(x)	x
+#define NEXTOP	do { pc++; unsigned op = pc->op; a = pc->a; goto *ops[op]; } while(0)
+#else
+#define OP(x)	case OP_##x
+#define NEXTOP	pc++; break
+#endif
+
+#define luai_nummod(a,b)        ((a) - floor((a)/(b))*(b))
+
+#define A				(pc[0].a)
+#define B				(pc[0].b)
+#define C				(pc[0].c)
+#define Cs				(pc[0].cs)
+#define BC				(pc[0].i16u)
+#define BCs				(pc[0].i16)
+#define ABCs			(pc[0].i24)
+#define JMPOFS(x)		((x)->i24)
+
+#define KC				(konstd[C])
+#define RC				(reg.d[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 CMPJMP(test) \
+	if ((test) == (a & CMP_CHECK)) { \
+		assert(pc[1].op == OP_JMP); \
+		pc += 1 + JMPOFS(pc+1); \
+	} else { \
+		pc += 1; \
+	}
+
+#define GETADDR(a,o,x) \
+	if (a == NULL) { ThrowAbortException(x, nullptr); return 0; } \
+	ptr = (VM_SBYTE *)a + o
+
+#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)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret) =
+#ifdef NDEBUG
+VMExec_Unchecked::Exec
+#else
+VMExec_Checked::Exec
+#endif
+;
+
+// Note: If the VM is being used in multiple threads, this should be declared as thread_local.
+// ZDoom doesn't need this at the moment so this is disabled.
+
+thread_local VMFrameStack GlobalVMStack;
+
+
+//===========================================================================
+//
+// VMSelectEngine
+//
+// Selects the VM engine, either checked or unchecked. Default will decide
+// based on the NDEBUG preprocessor definition.
+//
+//===========================================================================
+
+void VMSelectEngine(EVMEngine engine)
+{
+	switch (engine)
+	{
+	case VMEngine_Default:
+#ifdef NDEBUG
+		VMExec = VMExec_Unchecked::Exec;
+#else
+#endif
+		VMExec = VMExec_Checked::Exec;
+		break;
+	case VMEngine_Unchecked:
+		VMExec = VMExec_Unchecked::Exec;
+		break;
+	case VMEngine_Checked:
+		VMExec = VMExec_Checked::Exec;
+		break;
+	}
+}
+
+//===========================================================================
+//
+// VMFillParams
+//
+// Takes parameters from the parameter 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->VarFlags & VARF_Native));
+	assert(numparam == calleefunc->NumArgs);
+	assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3);
+
+	regd = regf = regs = rega = 0;
+	const uint8_t *reginfo = calleefunc->RegTypes;
+	assert(reginfo != nullptr);
+	for (int i = 0; i < calleefunc->NumArgs; ++i, reginfo++)
+	{
+		// copy all parameters to the local registers.
+		VMValue &p = params[i];
+		if (*reginfo < REGT_STRING)
+		{
+			if (*reginfo == REGT_INT)
+			{
+				calleereg.d[regd++] = p.i;
+			}
+			else // p.Type == REGT_FLOAT
+			{
+				calleereg.f[regf++] = p.f;
+			}
+		}
+		else if (*reginfo == REGT_STRING)
+		{
+			calleereg.s[regs++] = p.s();
+		}
+		else
+		{
+			assert(*reginfo == REGT_POINTER);
+			calleereg.a[rega++] = p.a;
+		}
+	}
+}
+
+
+#ifndef NDEBUG
+bool AssertObject(void * ob)
+{
+	auto obj = (DObject*)ob;
+	if (obj == nullptr) return true;
+#ifdef _MSC_VER
+	__try
+	{
+		return obj->MagicID == DObject::MAGIC_ID;
+	}
+	__except (1)
+	{
+		return false;
+	}
+#else
+	// No SEH on non-Microsoft compilers. :(
+	return obj->MagicID == DObject::MAGIC_ID;
+#endif
+}
+#endif
diff --git a/source/common/scripting/vm/vmexec.h b/source/common/scripting/vm/vmexec.h
new file mode 100644
index 000000000..0e28bee2a
--- /dev/null
+++ b/source/common/scripting/vm/vmexec.h
@@ -0,0 +1,2017 @@
+/*
+** vmexec.h
+** VM bytecode interpreter
+**
+**---------------------------------------------------------------------------
+** Copyright -2016 Randy Heit
+** Copyright 2016-2017 Christoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#ifndef IMPLEMENT_VMEXEC
+#error vmexec.h must not be #included outside vmexec.cpp. Use vm.h instead.
+#endif
+
+static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret)
+{
+#if COMPGOTO
+	static const void * const ops[256] =
+	{
+#define xx(op,sym,mode,alt,kreg,ktype) &&op,
+#include "vmops.h"
+	};
+#endif
+	//const VMOP *exception_frames[MAX_TRY_DEPTH];
+	//int try_depth = 0;
+	VMFrame *f = stack->TopFrame();
+	VMScriptFunction *sfunc = static_cast<VMScriptFunction *>(f->Func);
+	const int *konstd = sfunc->KonstD;
+	const double *konstf = sfunc->KonstF;
+	const FString *konsts = sfunc->KonstS;
+	const FVoidObj *konsta = sfunc->KonstA;
+	const VMOP *pc = sfunc->Code;
+
+	assert(!(f->Func->VarFlags & VARF_Native) && "Only script functions should ever reach VMExec");
+
+	const VMRegisters reg(f);
+
+	void *ptr;
+	double fb, fc;
+	const double *fbp, *fcp;
+	int a, b, c;
+
+//begin:
+	try
+	{
+#if !COMPGOTO
+	VM_UBYTE op;
+	for(;;) switch(op = pc->op, a = pc->a, op)
+#else
+	pc--;
+	NEXTOP;
+#endif
+	{
+#if !COMPGOTO
+	default:
+		assert(0 && "Undefined opcode hit");
+		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].v;
+		NEXTOP;
+
+	OP(LK_R) :
+		ASSERTD(a); ASSERTD(B);
+		reg.d[a] = konstd[reg.d[B] + C];
+		NEXTOP;
+	OP(LKF_R) :
+		ASSERTF(a); ASSERTD(B);
+		reg.f[a] = konstf[reg.d[B] + C];
+		NEXTOP;
+	OP(LKS_R) :
+		ASSERTS(a); ASSERTD(B);
+		reg.s[a] = konsts[reg.d[B] + C];
+		NEXTOP;
+	OP(LKP_R) :
+		ASSERTA(a); ASSERTD(B);
+		b = reg.d[B] + C;
+		reg.a[a] = konsta[b].v;
+		NEXTOP;
+
+	OP(LFP):
+		ASSERTA(a); assert(sfunc != NULL); assert(sfunc->ExtraSpace > 0);
+		reg.a[a] = f->GetExtra();
+		NEXTOP;
+
+	OP(CLSS):
+	{
+		ASSERTA(a); ASSERTA(B);
+		DObject *o = (DObject*)reg.a[B];
+		if (o == nullptr)
+		{
+			ThrowAbortException(X_READ_NIL, nullptr);
+			return 0;
+		}
+		reg.a[a] = o->GetClass();
+		NEXTOP;
+	}
+
+	OP(META):
+	{
+		ASSERTA(a); ASSERTA(B);
+		DObject *o = (DObject*)reg.a[B];
+		if (o == nullptr)
+		{
+			ThrowAbortException(X_READ_NIL, nullptr);
+			return 0;
+		}
+		reg.a[a] = o->GetClass()->Meta;
+		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(LCS):
+		ASSERTS(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.s[a] = *(const char **)ptr;
+		NEXTOP;
+	OP(LCS_R):
+		ASSERTS(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.s[a] = *(const char **)ptr;
+		NEXTOP;
+	OP(LO):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.a[a] = GC::ReadBarrier(*(DObject **)ptr);
+		NEXTOP;
+	OP(LO_R):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.a[a] = GC::ReadBarrier(*(DObject **)ptr);
+		NEXTOP;
+	OP(LP):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		reg.a[a] = *(void **)ptr;
+		NEXTOP;
+	OP(LP_R):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		reg.a[a] = *(void **)ptr;
+		NEXTOP;
+	OP(LV2):
+		ASSERTF(a+1); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		{
+			auto v = (double *)ptr;
+			reg.f[a] = v[0];
+			reg.f[a+1] = v[1];
+		}
+		NEXTOP;
+	OP(LV2_R):
+		ASSERTF(a+1); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		{
+			auto v = (double *)ptr;
+			reg.f[a] = v[0];
+			reg.f[a+1] = v[1];
+		}
+		NEXTOP;
+	OP(LV3):
+		ASSERTF(a+2); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PB,KC,X_READ_NIL);
+		{
+			auto v = (double *)ptr;
+			reg.f[a] = v[0];
+			reg.f[a+1] = v[1];
+			reg.f[a+2] = v[2];
+		}
+		NEXTOP;
+	OP(LV3_R):
+		ASSERTF(a+2); ASSERTA(B); ASSERTD(C);
+		GETADDR(PB,RC,X_READ_NIL);
+		{
+			auto v = (double *)ptr;
+			reg.f[a] = v[0];
+			reg.f[a+1] = v[1];
+			reg.f[a+2] = v[2];
+		}
+		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(SO):
+		ASSERTA(a); ASSERTA(B); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		*(void **)ptr = reg.a[B];
+		GC::WriteBarrier((DObject*)*(void **)ptr);
+		NEXTOP;
+	OP(SO_R):
+		ASSERTA(a); ASSERTA(B); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		GC::WriteBarrier((DObject*)*(void **)ptr);
+		NEXTOP;
+	OP(SV2):
+		ASSERTA(a); ASSERTF(B+1); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		{
+			auto v = (double *)ptr;
+			v[0] = reg.f[B];
+			v[1] = reg.f[B+1];
+		}
+		NEXTOP;
+	OP(SV2_R):
+		ASSERTA(a); ASSERTF(B+1); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		{
+			auto v = (double *)ptr;
+			v[0] = reg.f[B];
+			v[1] = reg.f[B+1];
+		}
+		NEXTOP;
+	OP(SV3):
+		ASSERTA(a); ASSERTF(B+2); ASSERTKD(C);
+		GETADDR(PA,KC,X_WRITE_NIL);
+		{
+			auto v = (double *)ptr;
+			v[0] = reg.f[B];
+			v[1] = reg.f[B+1];
+			v[2] = reg.f[B+2];
+		}
+		NEXTOP;
+	OP(SV3_R):
+		ASSERTA(a); ASSERTF(B+2); ASSERTD(C);
+		GETADDR(PA,RC,X_WRITE_NIL);
+		{
+			auto v = (double *)ptr;
+			v[0] = reg.f[B];
+			v[1] = reg.f[B+1];
+			v[2] = reg.f[B+2];
+		}
+		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);
+		b = B;
+		reg.a[a] = reg.a[b];
+		NEXTOP;
+	}
+	OP(MOVEV2):
+	{
+		ASSERTF(a); ASSERTF(B);
+		b = B;
+		reg.f[a] = reg.f[b];
+		reg.f[a + 1] = reg.f[b + 1];
+		NEXTOP;
+	}
+	OP(MOVEV3):
+	{
+		ASSERTF(a); ASSERTF(B);
+		b = B;
+		reg.f[a] = reg.f[b];
+		reg.f[a + 1] = reg.f[b + 1];
+		reg.f[a + 2] = reg.f[b + 2];
+		NEXTOP;
+	}
+	OP(DYNCAST_R) :
+		ASSERTA(a); ASSERTA(B);	ASSERTA(C);
+		b = B;
+		reg.a[a] = (reg.a[b] && ((DObject*)(reg.a[b]))->IsKindOf((PClass*)(reg.a[C]))) ? reg.a[b] : nullptr;
+		NEXTOP;
+	OP(DYNCAST_K) :
+		ASSERTA(a); ASSERTA(B);	ASSERTKA(C);
+		b = B;
+		reg.a[a] = (reg.a[b] && ((DObject*)(reg.a[b]))->IsKindOf((PClass*)(konsta[C].o))) ? reg.a[b] : nullptr;
+		NEXTOP;
+	OP(DYNCASTC_R) :
+		ASSERTA(a); ASSERTA(B);	ASSERTA(C);
+		b = B;
+		reg.a[a] = (reg.a[b] && ((PClass*)(reg.a[b]))->IsDescendantOf((PClass*)(reg.a[C]))) ? reg.a[b] : nullptr;
+		NEXTOP;
+	OP(DYNCASTC_K) :
+		ASSERTA(a); ASSERTA(B);	ASSERTKA(C);
+		b = B;
+		reg.a[a] = (reg.a[b] && ((PClass*)(reg.a[b]))->IsDescendantOf((PClass*)(konsta[C].o))) ? reg.a[b] : nullptr;
+		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(CASTB):
+		if (C == CASTB_I)
+		{
+			ASSERTD(a); ASSERTD(B);
+			reg.d[a] = !!reg.d[B];
+		}
+		else if (C == CASTB_F)
+		{
+			ASSERTD(a); ASSERTF(B);
+			reg.d[a] = reg.f[B] != 0;
+		}
+		else if (C == CASTB_A)
+		{
+			ASSERTD(a); ASSERTA(B);
+			reg.d[a] = reg.a[B] != nullptr;
+		}
+		else
+		{
+			ASSERTD(a); ASSERTS(B);
+			reg.d[a] = reg.s[B].Len() > 0;
+		}
+		NEXTOP;
+	
+	OP(TEST):
+		ASSERTD(a);
+		if (reg.d[a] != BC)
+		{
+			pc++;
+		}
+		NEXTOP;
+	OP(TESTN):
+		ASSERTD(a);
+		if (-reg.d[a] != BC)
+		{
+			pc++;
+		}
+		NEXTOP;
+	OP(JMP):
+		pc += JMPOFS(pc);
+		NEXTOP;
+	OP(IJMP):
+		ASSERTD(a);
+		pc += (reg.d[a]);
+		assert(pc[1].op == OP_JMP);
+		pc += 1 + JMPOFS(pc+1);
+		NEXTOP;
+	OP(PARAMI):
+		assert(f->NumParam < sfunc->MaxParam);
+		{
+			VMValue *param = &reg.param[f->NumParam++];
+			::new(param) VMValue(ABCs);
+		}
+		NEXTOP;
+	OP(PARAM):
+		assert(f->NumParam < sfunc->MaxParam);
+		{
+			VMValue *param = &reg.param[f->NumParam++];
+			b = BC;
+			if (a == REGT_NIL)
+			{
+				::new(param) VMValue();
+			}
+			else
+			{
+				switch(a)
+				{
+				case REGT_INT:
+					assert(b < f->NumRegD);
+					::new(param) VMValue(reg.d[b]);
+					break;
+				case REGT_INT | REGT_ADDROF:
+					assert(b < f->NumRegD);
+					::new(param) VMValue(&reg.d[b]);
+					break;
+				case REGT_INT | REGT_KONST:
+					assert(b < sfunc->NumKonstD);
+					::new(param) VMValue(konstd[b]);
+					break;
+				case REGT_STRING:
+					assert(b < f->NumRegS);
+					::new(param) VMValue(&reg.s[b]);
+					break;
+				case REGT_STRING | REGT_ADDROF:
+					assert(b < f->NumRegS);
+					::new(param) VMValue((void*)&reg.s[b]);	// Note that this may not use the FString* version of the constructor!
+					break;
+				case REGT_STRING | REGT_KONST:
+					assert(b < sfunc->NumKonstS);
+					::new(param) VMValue(&konsts[b]);
+					break;
+				case REGT_POINTER:
+					assert(b < f->NumRegA);
+					::new(param) VMValue(reg.a[b]);
+					break;
+				case REGT_POINTER | REGT_ADDROF:
+					assert(b < f->NumRegA);
+					::new(param) VMValue(&reg.a[b]);
+					break;
+				case REGT_POINTER | REGT_KONST:
+					assert(b < sfunc->NumKonstA);
+					::new(param) VMValue(konsta[b].v);
+					break;
+				case REGT_FLOAT:
+					assert(b < f->NumRegF);
+					::new(param) VMValue(reg.f[b]);
+					break;
+				case REGT_FLOAT | REGT_MULTIREG2:
+					assert(b < f->NumRegF - 1);
+					assert(f->NumParam < sfunc->MaxParam);
+					::new(param) VMValue(reg.f[b]);
+					::new(param + 1) VMValue(reg.f[b + 1]);
+					f->NumParam++;
+					break;
+				case REGT_FLOAT | REGT_MULTIREG3:
+					assert(b < f->NumRegF - 2);
+					assert(f->NumParam < sfunc->MaxParam - 1);
+					::new(param) VMValue(reg.f[b]);
+					::new(param + 1) VMValue(reg.f[b + 1]);
+					::new(param + 2) VMValue(reg.f[b + 2]);
+					f->NumParam += 2;
+					break;
+				case REGT_FLOAT | REGT_ADDROF:
+					assert(b < f->NumRegF);
+					::new(param) VMValue(&reg.f[b]);
+					break;
+				case REGT_FLOAT | REGT_KONST:
+					assert(b < sfunc->NumKonstF);
+					::new(param) VMValue(konstf[b]);
+					break;
+				default:
+					assert(0);
+					break;
+				}
+			}
+		}
+		NEXTOP;
+	OP(VTBL):
+		ASSERTA(a); ASSERTA(B);
+		{
+			auto o = (DObject*)reg.a[B];
+			if (o == nullptr)
+			{
+				ThrowAbortException(X_READ_NIL, nullptr);
+				return 0;
+			}
+			auto p = o->GetClass();
+			assert(C < p->Virtuals.Size());
+			reg.a[a] = p->Virtuals[C];
+		}
+		NEXTOP;
+	OP(SCOPE):
+		{
+			ASSERTA(a); ASSERTKA(C);
+			auto o = (DObject*)reg.a[a];
+			if (o == nullptr)
+			{
+				ThrowAbortException(X_READ_NIL, nullptr);
+				return 0;
+			}
+			FScopeBarrier::ValidateCall(o->GetClass(), (VMFunction*)konsta[C].v, B - 1);
+		}
+		NEXTOP;
+
+	OP(CALL_K):
+		ASSERTKA(a);
+		ptr = konsta[a].o;
+		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;
+
+			b = B;
+			FillReturns(reg, f, returns, pc+1, C);
+			if (call->VarFlags & VARF_Native)
+			{
+				try
+				{
+					VMCycles[0].Unclock();
+					numret = static_cast<VMNativeFunction *>(call)->NativeCall(VM_INVOKE(reg.param + f->NumParam - b, b, returns, C, call->RegTypes));
+					VMCycles[0].Clock();
+				}
+				catch (CVMAbortException &err)
+				{
+					err.MaybePrintMessage();
+					err.stacktrace.AppendFormat("Called from %s\n", call->PrintableName.GetChars());
+					// PrintParameters(reg.param + f->NumParam - B, B);
+					throw;
+				}
+			}
+			else
+			{
+				auto sfunc = static_cast<VMScriptFunction *>(call);
+				numret = sfunc->ScriptCall(sfunc, reg.param + f->NumParam - b, b, returns, C);
+			}
+			assert(numret == C && "Number of parameters returned differs from what was expected by the caller");
+			f->NumParam -= B;
+			pc += C;			// Skip RESULTs
+		}
+		NEXTOP;
+	OP(RET):
+		if (B == REGT_NIL)
+		{ // No return values
+			return 0;
+		}
+		assert(ret != NULL || numret == 0);
+		{
+			int retnum = a & ~RET_FINAL;
+			if (retnum < numret)
+			{
+				SetReturn(reg, f, &ret[retnum], B, C);
+			}
+			if (a & RET_FINAL)
+			{
+				return retnum < numret ? retnum + 1 : numret;
+			}
+		}
+		NEXTOP;
+	OP(RETI):
+		assert(ret != NULL || numret == 0);
+		{
+			int retnum = a & ~RET_FINAL;
+			if (retnum < numret)
+			{
+				ret[retnum].SetInt(BCs);
+			}
+			if (a & RET_FINAL)
+			{
+				return retnum < numret ? retnum + 1 : numret;
+			}
+		}
+		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;
+
+#if 0
+	OP(TRY):
+		assert(try_depth < MAX_TRY_DEPTH);
+		if (try_depth >= MAX_TRY_DEPTH)
+		{
+			ThrowAbortException(X_TOO_MANY_TRIES, nullptr);
+		}
+		assert((pc + JMPOFS(pc) + 1)->op == OP_CATCH);
+		exception_frames[try_depth++] = pc + JMPOFS(pc) + 1;
+		NEXTOP;
+	OP(UNTRY):
+		assert(a <= try_depth);
+		try_depth -= a;
+		NEXTOP;
+#endif
+	OP(THROW):
+#if 0
+		if (a == 0)
+		{
+			ASSERTA(B);
+			ThrowVMException((VMException *)reg.a[B]);
+		}
+		else if (a == 1)
+		{
+			ASSERTKA(B);
+			assert(AssertObject(konsta[B].o));
+			ThrowVMException((VMException *)konsta[B].o);
+		}
+		else
+#endif
+		{
+			ThrowAbortException(EVMAbortException(BC), nullptr);
+		}
+		NEXTOP;
+#if 0
+	OP(CATCH):
+		// This instruction is handled by our own catch handler and should
+		// not be executed by the normal VM code.
+		assert(0);
+		NEXTOP;
+#endif
+
+	OP(BOUND):
+		if (reg.d[a] >= BC)
+		{
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", BC, reg.d[a]);
+			return 0;
+		}
+		else if (reg.d[a] < 0)
+		{
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", reg.d[a]);
+			return 0;
+		}
+		NEXTOP;
+
+	OP(BOUND_K):
+		ASSERTKD(BC);
+		if (reg.d[a] >= konstd[BC])
+		{
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", konstd[BC], reg.d[a]);
+			return 0;
+		}
+		else if (reg.d[a] < 0)
+		{
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", reg.d[a]);
+			return 0;
+		}
+		NEXTOP;
+
+	OP(BOUND_R):
+		ASSERTD(B);
+		if (reg.d[a] >= reg.d[B])
+		{
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", reg.d[B], reg.d[a]);
+			return 0;
+		}
+		else if (reg.d[a] < 0)
+		{
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", reg.d[a]);
+			return 0;
+		}
+		NEXTOP;
+
+	OP(CONCAT):
+		ASSERTS(a); ASSERTS(B); ASSERTS(C);
+		reg.s[a] = reg.s[B] + reg.s[C];
+		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[1].op == OP_JMP);
+				pc += 1 + JMPOFS(pc+1);
+			}
+			else
+			{
+				pc += 1;
+			}
+		}
+		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(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(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] >> reg.d[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(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);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = reg.d[B] / reg.d[C];
+		NEXTOP;
+	OP(DIV_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		if (konstd[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = reg.d[B] / konstd[C];
+		NEXTOP;
+	OP(DIV_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = konstd[B] / reg.d[C];
+		NEXTOP;
+
+	OP(DIVU_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = int((unsigned)reg.d[B] / (unsigned)reg.d[C]);
+		NEXTOP;
+	OP(DIVU_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		if (konstd[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = int((unsigned)reg.d[B] / (unsigned)konstd[C]);
+		NEXTOP;
+	OP(DIVU_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = int((unsigned)konstd[B] / (unsigned)reg.d[C]);
+		NEXTOP;
+
+	OP(MOD_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = reg.d[B] % reg.d[C];
+		NEXTOP;
+	OP(MOD_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		if (konstd[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = reg.d[B] % konstd[C];
+		NEXTOP;
+	OP(MOD_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = konstd[B] % reg.d[C];
+		NEXTOP;
+
+	OP(MODU_RR):
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = int((unsigned)reg.d[B] % (unsigned)reg.d[C]);
+		NEXTOP;
+	OP(MODU_RK):
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		if (konstd[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = int((unsigned)reg.d[B] % (unsigned)konstd[C]);
+		NEXTOP;
+	OP(MODU_KR):
+		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
+		if (reg.d[C] == 0)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.d[a] = int((unsigned)konstd[B] % (unsigned)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); ASSERTKD(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(MINU_RR) :
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = (unsigned)reg.d[B] < (unsigned)reg.d[C] ? reg.d[B] : reg.d[C];
+		NEXTOP;
+	OP(MINU_RK) :
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = (unsigned)reg.d[B] < (unsigned)konstd[C] ? reg.d[B] : konstd[C];
+		NEXTOP;
+	OP(MAXU_RR) :
+		ASSERTD(a); ASSERTD(B); ASSERTD(C);
+		reg.d[a] = (unsigned)reg.d[B] > (unsigned)reg.d[C] ? reg.d[B] : reg.d[C];
+		NEXTOP;
+	OP(MAXU_RK) :
+		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
+		reg.d[a] = (unsigned)reg.d[B] > (unsigned)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(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);
+		if (reg.f[C] == 0.)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.f[a] = reg.f[B] / reg.f[C];
+		NEXTOP;
+	OP(DIVF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		if (konstf[C] == 0.)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		reg.f[a] = reg.f[B] / konstf[C];
+		NEXTOP;
+	OP(DIVF_KR):
+		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
+		if (reg.f[C] == 0.)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		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:
+		if (fc == 0.)
+		{
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
+			return 0;
+		}
+		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] = g_pow(reg.f[B], reg.f[C]);
+		NEXTOP;
+	OP(POWF_RK):
+		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
+		reg.f[a] = g_pow(reg.f[B], konstf[C]);
+		NEXTOP;
+	OP(POWF_KR):
+		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
+		reg.f[a] = g_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(ATAN2):
+		ASSERTF(a); ASSERTF(B); ASSERTF(C);
+		reg.f[a] = g_atan2(reg.f[B], reg.f[C]) * (180 / M_PI);
+		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(NEGV2):
+		ASSERTF(a+1); ASSERTF(B+1);
+		reg.f[a] = -reg.f[B];
+		reg.f[a+1] = -reg.f[B+1];
+		NEXTOP;
+
+	OP(ADDV2_RR):
+		ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C+1);
+		fcp = &reg.f[C];
+		fbp = &reg.f[B];
+		reg.f[a] = fbp[0] + fcp[0];
+		reg.f[a+1] = fbp[1] + fcp[1];
+		NEXTOP;
+
+	OP(SUBV2_RR):
+		ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C+1);
+		fbp = &reg.f[B];
+		fcp = &reg.f[C];
+		reg.f[a] = fbp[0] - fcp[0];
+		reg.f[a+1] = fbp[1] - fcp[1];
+		NEXTOP;
+
+	OP(DOTV2_RR):
+		ASSERTF(a); ASSERTF(B+1); ASSERTF(C+1);
+		reg.f[a] = reg.f[B] * reg.f[C] + reg.f[B+1] * reg.f[C+1];
+		NEXTOP;
+
+	OP(MULVF2_RR):
+		ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C);
+		fc = reg.f[C];
+		fbp = &reg.f[B];
+	Do_MULV2:
+		reg.f[a] = fbp[0] * fc;
+		reg.f[a+1] = fbp[1] * fc;
+		NEXTOP;
+	OP(MULVF2_RK):
+		ASSERTF(a+1); ASSERTF(B+1); ASSERTKF(C);
+		fc = konstf[C];
+		fbp = &reg.f[B];
+		goto Do_MULV2;
+
+	OP(DIVVF2_RR):
+		ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C);
+		fc = reg.f[C];
+		fbp = &reg.f[B];
+	Do_DIVV2:
+		reg.f[a] = fbp[0] / fc;
+		reg.f[a+1] = fbp[1] / fc;
+		NEXTOP;
+	OP(DIVVF2_RK):
+		ASSERTF(a+1); ASSERTF(B+1); ASSERTKF(C);
+		fc = konstf[C];
+		fbp = &reg.f[B];
+		goto Do_DIVV2;
+
+	OP(LENV2):
+		ASSERTF(a); ASSERTF(B+1);
+		reg.f[a] = g_sqrt(reg.f[B] * reg.f[B] + reg.f[B+1] * reg.f[B+1]);
+		NEXTOP;
+
+	OP(EQV2_R):
+		ASSERTF(B+1); ASSERTF(C+1);
+		fcp = &reg.f[C];
+	Do_EQV2:
+		if (a & CMP_APPROX)
+		{
+			CMPJMP(fabs(reg.f[B  ] - fcp[0]) < VM_EPSILON &&
+				   fabs(reg.f[B+1] - fcp[1]) < VM_EPSILON);
+		}
+		else
+		{
+			CMPJMP(reg.f[B] == fcp[0] && reg.f[B+1] == fcp[1]);
+		}
+		NEXTOP;
+	OP(EQV2_K):
+		ASSERTF(B+1); ASSERTKF(C+1);
+		fcp = &konstf[C];
+		goto Do_EQV2;
+
+	OP(NEGV3):
+		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(ADDV3_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2);
+		fcp = &reg.f[C];
+		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(SUBV3_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2);
+		fbp = &reg.f[B];
+		fcp = &reg.f[C];
+		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(DOTV3_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(CROSSV_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2);
+		fbp = &reg.f[B];
+		fcp = &reg.f[C];
+		{
+			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(MULVF3_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C);
+		fc = reg.f[C];
+		fbp = &reg.f[B];
+	Do_MULV3:
+		reg.f[a] = fbp[0] * fc;
+		reg.f[a+1] = fbp[1] * fc;
+		reg.f[a+2] = fbp[2] * fc;
+		NEXTOP;
+	OP(MULVF3_RK):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C);
+		fc = konstf[C];
+		fbp = &reg.f[B];
+		goto Do_MULV3;
+
+	OP(DIVVF3_RR):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C);
+		fc = reg.f[C];
+		fbp = &reg.f[B];
+	Do_DIVV3:
+		reg.f[a] = fbp[0] / fc;
+		reg.f[a+1] = fbp[1] / fc;
+		reg.f[a+2] = fbp[2] / fc;
+		NEXTOP;
+	OP(DIVVF3_RK):
+		ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C);
+		fc = konstf[C];
+		fbp = &reg.f[B];
+		goto Do_DIVV3;
+
+	OP(LENV3):
+		ASSERTF(a); ASSERTF(B+2);
+		reg.f[a] = g_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(EQV3_R):
+		ASSERTF(B+2); ASSERTF(C+2);
+		fcp = &reg.f[C];
+	Do_EQV3:
+		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(EQV3_K):
+		ASSERTF(B+2); ASSERTKF(C+2);
+		fcp = &konstf[C];
+		goto Do_EQV3;
+
+	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;
+		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].v);
+		NEXTOP;
+
+	OP(NOP):
+		NEXTOP;
+	}
+	}
+#if 0
+	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->op == OP_CATCH);
+			while (pc->a > 1)
+			{
+				// CATCH must be followed by JMP if it doesn't terminate a catch chain.
+				assert(pc[1].op == OP_JMP);
+
+				PClass *type;
+				int b = pc->b;
+
+				if (pc->a == 2)
+				{
+					ASSERTA(b);
+					type = (PClass *)reg.a[b];
+				}
+				else
+				{
+					assert(pc->a == 3);
+					ASSERTKA(b);
+					type = (PClass *)konsta[b].o;
+				}
+				ASSERTA(pc->c);
+				if (type == extype)
+				{
+					// Found a handler. Store the exception in pC, skip the JMP,
+					// and begin executing its code.
+					reg.a[pc->c] = exception;
+					pc += 2;
+					goto begin;
+				}
+				// This catch didn't handle it. Try the next one.
+				pc += 1 + JMPOFS(pc + 1);
+				assert(pc->op == OP_CATCH);
+			}
+			if (pc->a == 1)
+			{
+				// Catch any type of VMException. This terminates the chain.
+				ASSERTA(pc->c);
+				reg.a[pc->c] = exception;
+				pc += 1;
+				goto begin;
+			}
+			// This frame failed. Try the next one out.
+		}
+		// Nothing caught it. Rethrow and let somebody else deal with it.
+		throw;
+	}
+#endif
+	catch (CVMAbortException &err)
+	{
+		err.MaybePrintMessage();
+		err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(pc));
+		// PrintParameters(reg.param + f->NumParam - B, B);
+		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_EXP:		return g_exp(v);
+	case FLOP_LOG:		return g_log(v);
+	case FLOP_LOG10:	return g_log10(v);
+	case FLOP_SQRT:		return g_sqrt(v);
+	case FLOP_CEIL:		return ceil(v);
+	case FLOP_FLOOR:	return floor(v);
+
+	case FLOP_ACOS:		return g_acos(v);
+	case FLOP_ASIN:		return g_asin(v);
+	case FLOP_ATAN:		return g_atan(v);
+	case FLOP_COS:		return g_cos(v);
+	case FLOP_SIN:		return g_sin(v);
+	case FLOP_TAN:		return g_tan(v);
+
+	case FLOP_ACOS_DEG:	return g_acos(v) * (180 / M_PI);
+	case FLOP_ASIN_DEG:	return g_asin(v) * (180 / M_PI);
+	case FLOP_ATAN_DEG:	return g_atan(v) * (180 / M_PI);
+	case FLOP_COS_DEG:	return g_cosdeg(v);
+	case FLOP_SIN_DEG:	return g_sindeg(v);
+	case FLOP_TAN_DEG:	return g_tan(v * (M_PI / 180));
+
+	case FLOP_COSH:		return g_cosh(v);
+	case FLOP_SINH:		return g_sinh(v);
+	case FLOP_TANH:		return g_tanh(v);
+
+	case FLOP_ROUND:	return round(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_U2F:
+		ASSERTF(a); ASSERTD(b);
+		reg.f[a] = unsigned(reg.d[b]);
+		break;
+	case CAST_I2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a].Format("%d", reg.d[b]);
+		break;
+	case CAST_U2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a].Format("%u", reg.d[b]);
+		break;
+
+	case CAST_F2I:
+		ASSERTD(a); ASSERTF(b);
+		reg.d[a] = (int)reg.f[b];
+		break;
+	case CAST_F2U:
+		ASSERTD(a); ASSERTF(b);
+		reg.d[a] = (int)(unsigned)reg.f[b];
+		break;
+	case CAST_F2S:
+		ASSERTS(a); ASSERTF(b);
+		reg.s[a].Format("%.5f", reg.f[b]);	// keep this small. For more precise conversion there should be a conversion function.
+		break;
+	case CAST_V22S:
+		ASSERTS(a); ASSERTF(b+1);
+		reg.s[a].Format("(%.5f, %.5f)", reg.f[b], reg.f[b + 1]);
+		break;
+	case CAST_V32S:
+		ASSERTS(a); ASSERTF(b + 2);
+		reg.s[a].Format("(%.5f, %.5f, %.5f)", reg.f[b], reg.f[b + 1], reg.f[b + 2]);
+		break;
+
+	case CAST_P2S:
+	{
+		ASSERTS(a); ASSERTA(b);
+		if (reg.a[b] == nullptr) reg.s[a] = "null";
+		else reg.s[a].Format("%p", 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;
+
+	case CAST_S2N:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = reg.s[b].Len() == 0? NAME_None : FName(reg.s[b]).GetIndex();
+		break;
+
+	case CAST_N2S:
+	{
+		ASSERTS(a); ASSERTD(b);
+		FName name = FName(ENamedName(reg.d[b]));
+		reg.s[a] = name.IsValidName() ? name.GetChars() : "";
+		break; 
+	}
+
+	case CAST_S2Co:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = V_GetColor(NULL, reg.s[b]);
+		break;
+
+	case CAST_Co2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a].Format("%02x %02x %02x", PalEntry(reg.d[b]).r, PalEntry(reg.d[b]).g, PalEntry(reg.d[b]).b);
+		break;
+
+	case CAST_S2So:
+		ASSERTD(a); ASSERTS(b);
+		reg.d[a] = FSoundID(reg.s[b]);
+		break;
+
+	case CAST_So2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a] = soundEngine->GetSoundName(reg.d[b]);
+		break;
+
+	case CAST_SID2S:
+		ASSERTS(a); ASSERTD(b);
+		reg.s[a] = "";// unsigned(reg.d[b]) >= sprites.Size() ? "TNT1" : sprites[reg.d[b]].name;
+		break;
+
+	case CAST_TID2S:
+	{
+		ASSERTS(a); ASSERTD(b);
+		auto tex = TexMan.GetTexture(*(FTextureID*)&(reg.d[b]));
+		reg.s[a] = tex == nullptr ? "(null)" : tex->GetName().GetChars();
+		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 VMOP *retval, int numret)
+{
+	int i, type, regnum;
+	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)
+	{
+		assert(retval->op == OP_RESULT);				// opcode
+		ret->RegType = type = retval->b;
+		regnum = retval->c;
+		assert(!(type & REGT_KONST));
+		type &= REGT_TYPE;
+		if (type < REGT_STRING)
+		{
+			if (type == REGT_INT)
+			{
+				assert(regnum < frame->NumRegD);
+				ret->Location = &reg.d[regnum];
+			}
+			else // type == REGT_FLOAT
+			{
+				assert(regnum < frame->NumRegF);
+				ret->Location = &reg.f[regnum];
+			}
+		}
+		else if (type == REGT_STRING)
+		{
+			assert(regnum < frame->NumRegS);
+			ret->Location = &reg.s[regnum];
+		}
+		else
+		{
+			assert(type == REGT_POINTER);
+			assert(regnum < frame->NumRegA);
+			ret->Location = &reg.a[regnum];
+		}
+	}
+}
+
+//===========================================================================
+//
+// 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;
+	VMScriptFunction *func = static_cast<VMScriptFunction *>(frame->Func);
+
+	assert(func != NULL && !(func->VarFlags & VARF_Native));
+	assert((regtype & ~REGT_KONST) == 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 < func->NumKonstF);
+			src = &func->KonstF[regnum];
+		}
+		else
+		{
+			assert(regnum < frame->NumRegF);
+			src = &reg.f[regnum];
+		}
+		if (regtype & REGT_MULTIREG3)
+		{
+			ret->SetVector((double *)src);
+		}
+		else if (regtype & REGT_MULTIREG2)
+		{
+			ret->SetVector2((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].v);
+		}
+		else
+		{
+			assert(regnum < frame->NumRegA);
+			ret->SetPointer(reg.a[regnum]);
+		}
+		break;
+	}
+}
+
+static int Exec(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret)
+{
+	VMCalls[0]++;
+	VMFrameStack *stack = &GlobalVMStack;
+	VMFrame *newf = stack->AllocFrame(static_cast<VMScriptFunction*>(func));
+	VMFillParams(params, newf, numparams);
+	try
+	{
+		numret = ExecScriptFunc(stack, ret, numret);
+	}
+	catch (...)
+	{
+		stack->PopFrame();
+		throw;
+	}
+	stack->PopFrame();
+	return numret;
+}
diff --git a/source/common/scripting/vm/vmframe.cpp b/source/common/scripting/vm/vmframe.cpp
new file mode 100644
index 000000000..7c2a63b1f
--- /dev/null
+++ b/source/common/scripting/vm/vmframe.cpp
@@ -0,0 +1,771 @@
+/*
+** vmframe.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright -2016 Randy Heit
+** Copyright 2016 Christoph Oelckers
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include <new>
+#include "dobject.h"
+#include "v_text.h"
+#include "stats.h"
+#include "c_dispatch.h"
+#include "templates.h"
+#include "vmintern.h"
+#include "types.h"
+#include "jit.h"
+#include "c_cvars.h"
+#include "version.h"
+
+#ifdef HAVE_VM_JIT
+CUSTOM_CVAR(Bool, vm_jit, true, CVAR_NOINITCALL)
+{
+	Printf("You must restart " GAMENAME " for this change to take effect.\n");
+	Printf("This cvar is currently not saved. You must specify it on the command line.");
+}
+#else
+CVAR(Bool, vm_jit, false, CVAR_NOINITCALL|CVAR_NOSET)
+FString JitCaptureStackTrace(int framesToSkip, bool includeNativeFrames) { return FString(); }
+void JitRelease() {}
+#endif
+
+cycle_t VMCycles[10];
+int VMCalls[10];
+
+#if 0
+IMPLEMENT_CLASS(VMException, false, false)
+#endif
+
+TArray<VMFunction *> VMFunction::AllFunctions;
+
+// Creates the register type list for a function.
+// Native functions only need this to assert their parameters in debug mode, script functions use this to load their registers from the VMValues.
+void VMFunction::CreateRegUse()
+{
+#ifdef NDEBUG
+	if (VarFlags & VARF_Native) return;	// we do not need this for native functions in release builds.
+#endif
+	int count = 0;
+	if (!Proto)
+	{
+		if (RegTypes) return;
+		Printf(TEXTCOLOR_ORANGE "Function without prototype needs register info manually set: %s\n", PrintableName.GetChars());
+		return;
+	}
+	assert(Proto->isPrototype());
+
+	for (auto arg : Proto->ArgumentTypes)
+	{
+		count += arg? arg->GetRegCount() : 1;
+	}
+	uint8_t *regp;
+	RegTypes = regp = (uint8_t*)ClassDataAllocator.Alloc(count);
+	count = 0;
+	for (unsigned i = 0; i < Proto->ArgumentTypes.Size(); i++)
+	{
+		auto arg = Proto->ArgumentTypes[i];
+		auto flg = ArgFlags.Size() > i ? ArgFlags[i] : 0;
+		if (arg == nullptr)
+		{
+			// Marker for start of varargs.
+			*regp++ = REGT_NIL;
+		}
+		else if ((flg & VARF_Out) && !arg->isPointer())
+		{
+			*regp++ = REGT_POINTER;
+		}
+		else for (int j = 0; j < arg->GetRegCount(); j++)
+		{
+			*regp++ = arg->GetRegType();
+		}
+	}
+}
+
+VMScriptFunction::VMScriptFunction(FName name)
+{
+	Name = name;
+	LineInfo = nullptr;
+	Code = NULL;
+	KonstD = NULL;
+	KonstF = NULL;
+	KonstS = NULL;
+	KonstA = NULL;
+	LineInfoCount = 0;
+	ExtraSpace = 0;
+	CodeSize = 0;
+	NumRegD = 0;
+	NumRegF = 0;
+	NumRegS = 0;
+	NumRegA = 0;
+	NumKonstD = 0;
+	NumKonstF = 0;
+	NumKonstS = 0;
+	NumKonstA = 0;
+	MaxParam = 0;
+	NumArgs = 0;
+	ScriptCall = &VMScriptFunction::FirstScriptCall;
+}
+
+VMScriptFunction::~VMScriptFunction()
+{
+	if (Code != NULL)
+	{
+		if (KonstS != NULL)
+		{
+			for (int i = 0; i < NumKonstS; ++i)
+			{
+				KonstS[i].~FString();
+			}
+		}
+	}
+}
+
+void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers)
+{
+	assert(Code == NULL);
+	assert(numops > 0);
+	assert(numkonstd >= 0 && numkonstd <= 65535);
+	assert(numkonstf >= 0 && numkonstf <= 65535);
+	assert(numkonsts >= 0 && numkonsts <= 65535);
+	assert(numkonsta >= 0 && numkonsta <= 65535);
+	assert(numlinenumbers >= 0 && numlinenumbers <= 65535);
+	void *mem = ClassDataAllocator.Alloc(numops * sizeof(VMOP) +
+						 numkonstd * sizeof(int) +
+						 numkonstf * sizeof(double) +
+						 numkonsts * sizeof(FString) +
+						 numkonsta * sizeof(FVoidObj) +
+						 numlinenumbers * sizeof(FStatementInfo));
+	Code = (VMOP *)mem;
+	mem = (void *)((VMOP *)mem + numops);
+
+	if (numlinenumbers > 0)
+	{
+		LineInfo = (FStatementInfo*)mem;
+		LineInfoCount = numlinenumbers;
+		mem = LineInfo + numlinenumbers;
+	}
+	else
+	{
+		LineInfo = nullptr;
+		LineInfoCount = 0;
+	}
+	if (numkonstd > 0)
+	{
+		KonstD = (int *)mem;
+		mem = (void *)((int *)mem + numkonstd);
+	}
+	else
+	{
+		KonstD = NULL;
+	}
+	if (numkonstf > 0)
+	{
+		KonstF = (double *)mem;
+		mem = (void *)((double *)mem + numkonstf);
+	}
+	else
+	{
+		KonstF = NULL;
+	}
+	if (numkonsts > 0)
+	{
+		KonstS = (FString *)mem;
+		for (int i = 0; i < numkonsts; ++i)
+		{
+			::new(&KonstS[i]) FString;
+		}
+		mem = (void *)((FString *)mem + numkonsts);
+	}
+	else
+	{
+		KonstS = NULL;
+	}
+	if (numkonsta > 0)
+	{
+		KonstA = (FVoidObj *)mem;
+	}
+	else
+	{
+		KonstA = NULL;
+	}
+	CodeSize = numops;
+	NumKonstD = numkonstd;
+	NumKonstF = numkonstf;
+	NumKonstS = numkonsts;
+	NumKonstA = numkonsta;
+}
+
+void VMScriptFunction::InitExtra(void *addr)
+{
+	char *caddr = (char*)addr;
+
+	for (auto tao : SpecialInits)
+	{
+		tao.first->InitializeValue(caddr + tao.second, nullptr);
+	}
+}
+
+void VMScriptFunction::DestroyExtra(void *addr)
+{
+	char *caddr = (char*)addr;
+
+	for (auto tao : SpecialInits)
+	{
+		tao.first->DestroyValue(caddr + tao.second);
+	}
+}
+
+int VMScriptFunction::AllocExtraStack(PType *type)
+{
+	int address = ((ExtraSpace + type->Align - 1) / type->Align) * type->Align;
+	ExtraSpace = address + type->Size;
+	type->SetDefaultValue(nullptr, address, &SpecialInits);
+	return address;
+}
+
+int VMScriptFunction::PCToLine(const VMOP *pc)
+{
+	int PCIndex = int(pc - Code);
+	if (LineInfoCount == 1) return LineInfo[0].LineNumber;
+	for (unsigned i = 1; i < LineInfoCount; i++)
+	{
+		if (LineInfo[i].InstructionIndex > PCIndex)
+		{
+			return LineInfo[i - 1].LineNumber;
+		}
+	}
+	return -1;
+}
+
+static bool CanJit(VMScriptFunction *func)
+{
+	// Asmjit has a 256 register limit. Stay safely away from it as the jit compiler uses a few for temporaries as well.
+	// Any function exceeding the limit will use the VM - a fair punishment to someone for writing a function so bloated ;)
+
+	int maxregs = 200;
+	if (func->NumRegA + func->NumRegD + func->NumRegF + func->NumRegS < maxregs)
+		return true;
+
+	Printf(TEXTCOLOR_ORANGE "%s is using too many registers (%d of max %d)! Function will not use native code.\n", func->PrintableName.GetChars(), func->NumRegA + func->NumRegD + func->NumRegF + func->NumRegS, maxregs);
+
+	return false;
+}
+
+int VMScriptFunction::FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret)
+{
+#ifdef HAVE_VM_JIT
+	if (vm_jit && CanJit(static_cast<VMScriptFunction*>(func)))
+	{
+		func->ScriptCall = JitCompile(static_cast<VMScriptFunction*>(func));
+		if (!func->ScriptCall)
+			func->ScriptCall = VMExec;
+	}
+	else
+#endif // HAVE_VM_JIT
+	{
+		func->ScriptCall = VMExec;
+	}
+
+	return func->ScriptCall(func, params, numparams, ret, numret);
+}
+
+int VMNativeFunction::NativeScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *returns, int numret)
+{
+	try
+	{
+		VMCycles[0].Unclock();
+		numret = static_cast<VMNativeFunction *>(func)->NativeCall(VM_INVOKE(params, numparams, returns, numret, func->RegTypes));
+		VMCycles[0].Clock();
+
+		return numret;
+	}
+	catch (CVMAbortException &err)
+	{
+		err.MaybePrintMessage();
+		err.stacktrace.AppendFormat("Called from %s\n", func->PrintableName.GetChars());
+		throw;
+	}
+}
+
+//===========================================================================
+//
+// 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;
+		}
+		Blocks = NULL;
+	}
+	if (UnusedBlocks != NULL)
+	{
+		BlockHeader *block, *next;
+		for (block = UnusedBlocks; block != NULL; block = next)
+		{
+			next = block->NextBlock;
+			delete[] (VM_UBYTE *)block;
+		}
+		UnusedBlocks = NULL;
+	}
+}
+
+//===========================================================================
+//
+// VMFrameStack :: AllocFrame
+//
+// Allocates a frame from the stack suitable for calling a particular
+// function.
+//
+//===========================================================================
+
+VMFrame *VMFrameStack::AllocFrame(VMScriptFunction *func)
+{
+	VMFrame *frame = Alloc(func->StackSize);
+	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();
+	if (func->SpecialInits.Size())
+	{
+		func->InitExtra(frame->GetExtra());
+	}
+	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->InitFreeSpace();
+		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;
+	}
+	auto Func = static_cast<VMScriptFunction *>(frame->Func);
+	if (Func->SpecialInits.Size())
+	{
+		Func->DestroyExtra(frame->GetExtra());
+	}
+	// Free any string registers this frame had.
+	FString *regs = frame->GetRegS();
+	for (int i = frame->NumRegS; i != 0; --i)
+	{
+		(regs++)->~FString();
+	}
+	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->InitFreeSpace();
+		}
+		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 VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap*/)
+{
+#if 0
+	try
+#endif
+	{	
+		if (func->VarFlags & VARF_Native)
+		{
+			return static_cast<VMNativeFunction *>(func)->NativeCall(VM_INVOKE(params, numparams, results, numresults, func->RegTypes));
+		}
+		else
+		{
+			auto code = static_cast<VMScriptFunction *>(func)->Code;
+			// handle empty functions consisting of a single return explicitly so that empty virtual callbacks do not need to set up an entire VM frame.
+			// code cann be null here in case of some non-fatal DECORATE errors.
+			if (code == nullptr || code->word == (0x00808000|OP_RET))
+			{
+				return 0;
+			}
+			else if (code->word == (0x00048000|OP_RET))
+			{
+				if (numresults == 0) return 0;
+				results[0].SetInt(static_cast<VMScriptFunction *>(func)->KonstD[0]);
+				return 1;
+			}
+			else
+			{
+				VMCycles[0].Clock();
+
+				auto sfunc = static_cast<VMScriptFunction *>(func);
+				int numret = sfunc->ScriptCall(sfunc, params, numparams, results, numresults);
+				VMCycles[0].Unclock();
+				return numret;
+			}
+		}
+	}
+#if 0
+	catch (VMException *exception)
+	{
+		if (trap != NULL)
+		{
+			*trap = exception;
+			return -1;
+		}
+		throw;
+	}
+#endif
+}
+
+int VMCallWithDefaults(VMFunction *func, TArray<VMValue> &params, VMReturn *results, int numresults/*, VMException **trap = NULL*/)
+{
+	if (func->DefaultArgs.Size() > params.Size())
+	{
+		auto oldp = params.Size();
+		params.Resize(func->DefaultArgs.Size());
+		for (unsigned i = oldp; i < params.Size(); i++)
+		{
+			params[i] = func->DefaultArgs[i];
+		}
+	}
+	return VMCall(func, params.Data(), params.Size(), results, numresults);
+}
+
+
+// Exception stuff for the VM is intentionally placed there, because having this in vmexec.cpp would subject it to inlining
+// which we do not want because it increases the local stack requirements of Exec which are already too high.
+FString CVMAbortException::stacktrace;
+
+CVMAbortException::CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap)
+{
+	SetMessage("VM execution aborted: ");
+	switch (reason)
+	{
+	case X_READ_NIL:
+		AppendMessage("tried to read from address zero.");
+		break;
+
+	case X_WRITE_NIL:
+		AppendMessage("tried to write to address zero.");
+		break;
+
+	case X_TOO_MANY_TRIES:
+		AppendMessage("too many try-catch blocks.");
+		break;
+
+	case X_ARRAY_OUT_OF_BOUNDS:
+		AppendMessage("array access out of bounds.");
+		break;
+
+	case X_DIVISION_BY_ZERO:
+		AppendMessage("division by zero.");
+		break;
+
+	case X_BAD_SELF:
+		AppendMessage("invalid self pointer.");
+		break;
+
+	case X_FORMAT_ERROR:
+		AppendMessage("string format failed.");
+		break;
+
+	case X_OTHER:
+		// no prepended message.
+		break;
+
+	default:
+	{
+		size_t len = strlen(m_Message);
+		mysnprintf(m_Message + len, MAX_ERRORTEXT - len, "Unknown reason %d", reason);
+		break;
+	}
+	}
+	if (moreinfo != nullptr)
+	{
+		AppendMessage(" ");
+		size_t len = strlen(m_Message);
+		myvsnprintf(m_Message + len, MAX_ERRORTEXT - len, moreinfo, ap);
+	}
+
+	if (vm_jit)
+		stacktrace = JitCaptureStackTrace(1, false);
+	else
+		stacktrace = "";
+}
+
+// Print this only once on the first catch block.
+void CVMAbortException::MaybePrintMessage()
+{
+	auto m = GetMessage();
+	if (m != nullptr)
+	{
+		Printf(TEXTCOLOR_RED);
+		Printf("%s\n", m);
+		SetMessage("");
+	}
+}
+
+
+void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...)
+{
+	va_list ap;
+	va_start(ap, moreinfo);
+	throw CVMAbortException(reason, moreinfo, ap);
+	va_end(ap);
+}
+
+void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...)
+{
+	va_list ap;
+	va_start(ap, moreinfo);
+
+	CVMAbortException err(reason, moreinfo, ap);
+
+	err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(line));
+	throw err;
+	va_end(ap);
+}
+
+DEFINE_ACTION_FUNCTION(DObject, ThrowAbortException)
+{
+	PARAM_PROLOGUE;
+	FString s = FStringFormat(VM_ARGS_NAMES);
+	ThrowAbortException(X_OTHER, s.GetChars());
+	return 0;
+}
+
+void NullParam(const char *varname)
+{
+	ThrowAbortException(X_READ_NIL, "In function parameter %s", varname);
+}
+
+#if 0
+void ThrowVMException(VMException *x)
+{
+	throw x;
+}
+#endif
+
+
+void ClearGlobalVMStack()
+{
+	while (GlobalVMStack.PopFrame() != nullptr)
+	{
+	}
+}
+
+
+ADD_STAT(VM)
+{
+	double added = 0;
+	int addedc = 0;
+	double peak = 0;
+	for (auto d : VMCycles)
+	{
+		added += d.TimeMS();
+		peak = MAX<double>(peak, d.TimeMS());
+	}
+	for (auto d : VMCalls) addedc += d;
+	memmove(&VMCycles[1], &VMCycles[0], 9 * sizeof(cycle_t));
+	memmove(&VMCalls[1], &VMCalls[0], 9 * sizeof(int));
+	VMCycles[0].Reset();
+	VMCalls[0] = 0;
+	return FStringf("VM time in last 10 tics: %f ms, %d calls, peak = %f ms", added, addedc, peak);
+}
+
+//-----------------------------------------------------------------------------
+//
+//
+//
+//-----------------------------------------------------------------------------
+CCMD(vmengine)
+{
+	if (argv.argc() == 2)
+	{
+		if (stricmp(argv[1], "default") == 0)
+		{
+			VMSelectEngine(VMEngine_Default);
+			return;
+		}
+		else if (stricmp(argv[1], "checked") == 0)
+		{
+			VMSelectEngine(VMEngine_Checked);
+			return;
+		}
+		else if (stricmp(argv[1], "unchecked") == 0)
+		{
+			VMSelectEngine(VMEngine_Unchecked);
+			return;
+		}
+	}
+	Printf("Usage: vmengine <default|checked|unchecked>\n");
+}
+
diff --git a/source/common/scripting/vm/vmintern.h b/source/common/scripting/vm/vmintern.h
new file mode 100644
index 000000000..b93f3e6ef
--- /dev/null
+++ b/source/common/scripting/vm/vmintern.h
@@ -0,0 +1,483 @@
+#pragma once
+
+#include "vm.h"
+#include <csetjmp>
+
+class VMScriptFunction;
+
+#ifdef __BIG_ENDIAN__
+#define VM_DEFINE_OP2(TYPE, ARG1, ARG2) TYPE ARG2, ARG1
+#define VM_DEFINE_OP4(TYPE, ARG1, ARG2, ARG3, ARG4) TYPE ARG4, ARG3, ARG2, ARG1
+#else // little endian
+#define VM_DEFINE_OP2(TYPE, ARG1, ARG2) TYPE ARG1, ARG2
+#define VM_DEFINE_OP4(TYPE, ARG1, ARG2, ARG3, ARG4) TYPE ARG1, ARG2, ARG3, ARG4
+#endif // __BIG_ENDIAN__
+
+union VMOP
+{
+	struct
+	{
+		VM_DEFINE_OP4(VM_UBYTE, op, a, b, c);
+	};
+	struct
+	{
+		VM_DEFINE_OP4(VM_SBYTE, pad0, as, bs, cs);
+	};
+	struct
+	{
+		VM_DEFINE_OP2(VM_SWORD, pad1:8, i24:24);
+	};
+	struct
+	{
+		VM_DEFINE_OP2(VM_SWORD, pad2:16, i16:16);
+	};
+	struct
+	{
+		VM_DEFINE_OP2(VM_UHALF, pad3, i16u);
+	};
+	VM_UWORD word;
+
+	// Interesting fact: VC++ produces better code for i16 when it's defined
+	// as a bitfield than when it's defined as two discrete units.
+	// Compare:
+	//		mov		eax,dword ptr [op]		; As two discrete units
+	//		shr		eax,10h
+	//		movsx	eax,ax
+	// versus:
+	//		mov		eax,dword ptr [op]		; As a bitfield
+	//		sar		eax,10h
+};
+
+#undef VM_DEFINE_OP4
+#undef VM_DEFINE_OP2
+
+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_EXP,
+	FLOP_LOG,
+	FLOP_LOG10,
+	FLOP_SQRT,
+	FLOP_CEIL,
+	FLOP_FLOOR,
+
+	FLOP_ACOS,			// This group works with radians
+	FLOP_ASIN,
+	FLOP_ATAN,
+	FLOP_COS,
+	FLOP_SIN,
+	FLOP_TAN,
+
+	FLOP_ACOS_DEG,		// This group works with degrees
+	FLOP_ASIN_DEG,
+	FLOP_ATAN_DEG,
+	FLOP_COS_DEG,
+	FLOP_SIN_DEG,
+	FLOP_TAN_DEG,
+
+	FLOP_COSH,
+	FLOP_SINH,
+	FLOP_TANH,
+
+	FLOP_ROUND,
+};
+
+// Cast operations
+enum
+{
+	CAST_I2F,
+	CAST_I2S,
+	CAST_U2F,
+	CAST_U2S,
+	CAST_F2I,
+	CAST_F2U,
+	CAST_F2S,
+	CAST_P2S,
+	CAST_S2I,
+	CAST_S2F,
+	CAST_S2N,
+	CAST_N2S,
+	CAST_S2Co,
+	CAST_S2So,
+	CAST_Co2S,
+	CAST_So2S,
+	CAST_V22S,
+	CAST_V32S,
+	CAST_SID2S,
+	CAST_TID2S,
+
+	CASTB_I,
+	CASTB_F,
+	CASTB_A,
+	CASTB_S
+};
+
+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_CMP,
+
+	MODE_PARAM,
+	MODE_PARAM24,
+	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_ACMP		= MODE_CMP << 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_ABCJOINT	= (MODE_JOINT << MODE_ASHIFT) | (MODE_JOINT << MODE_BSHIFT) | (MODE_JOINT << 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,
+};
+
+struct VMOpInfo
+{
+	const char *Name;
+	int Mode;
+};
+
+extern const VMOpInfo OpInfo[NUM_OPS];
+
+
+// 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 *);
+		size += numregs * sizeof(FString);
+		size += numregd * sizeof(int);
+		if (numextra != 0)
+		{
+			size = (size + 15) & ~15;
+			size += numextra;
+		}
+		return size;
+	}
+
+	VMValue *GetParam() const
+	{
+		assert(((size_t)this & 15) == 0 && "VM frame is unaligned");
+		return (VMValue *)(((size_t)(this + 1) + 15) & ~15);
+	}
+
+	double *GetRegF() const
+	{
+		return (double *)(GetParam() + MaxParam);
+	}
+
+	FString *GetRegS() const
+	{
+		return (FString *)(GetRegF() + NumRegF);
+	}
+
+	void **GetRegA() const
+	{
+		return (void **)(GetRegS() + NumRegS);
+	}
+
+	int *GetRegD() const
+	{
+		return (int *)(GetRegA() + NumRegA);
+	}
+
+	void *GetExtra() const
+	{
+		uint8_t *pbeg = (uint8_t*)(GetRegD() + NumRegD);
+		ptrdiff_t ofs = pbeg - (uint8_t *)this;
+		return (VM_UBYTE *)this + ((ofs + 15) & ~15);
+	}
+
+	void GetAllRegs(int *&d, double *&f, FString *&s, void **&a, 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);
+	}
+
+	void InitRegS();
+};
+
+struct VMRegisters
+{
+	VMRegisters(const VMFrame *frame)
+	{
+		frame->GetAllRegs(d, f, s, a, param);
+	}
+
+	VMRegisters(const VMRegisters &o)
+		: d(o.d), f(o.f), s(o.s), a(o.a), param(o.param)
+	{ }
+
+	int *d;
+	double *f;
+	FString *s;
+	void **a;
+	VMValue *param;
+};
+
+union FVoidObj
+{
+	DObject *o;
+	void *v;
+};
+
+struct FStatementInfo
+{
+	uint16_t InstructionIndex;
+	uint16_t LineNumber;
+};
+
+class VMFrameStack
+{
+public:
+	VMFrameStack();
+	~VMFrameStack();
+	VMFrame *AllocFrame(VMScriptFunction *func);
+	VMFrame *PopFrame();
+	VMFrame *TopFrame()
+	{
+		assert(Blocks != NULL && Blocks->LastFrame != NULL);
+		return Blocks->LastFrame;
+	}
+	static int OffsetLastFrame() { return (int)(ptrdiff_t)offsetof(BlockHeader, LastFrame); }
+private:
+	enum { BLOCK_SIZE = 4096 };		// Default block size
+	struct BlockHeader
+	{
+		BlockHeader *NextBlock;
+		VMFrame *LastFrame;
+		VM_UBYTE *FreeSpace;
+		int BlockSize;
+
+		void InitFreeSpace()
+		{
+			FreeSpace = (VM_UBYTE *)(((size_t)(this + 1) + 15) & ~15);
+		}
+	};
+	BlockHeader *Blocks;
+	BlockHeader *UnusedBlocks;
+	VMFrame *Alloc(int size);
+};
+
+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;
+		RegA++;
+	}
+
+	void ParamPointer(void *ptr)
+	{
+		Reg.a[RegA] = ptr;
+		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)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
+void VMFillParams(VMValue *params, VMFrame *callee, int numparam);
+
+void VMDumpConstants(FILE *out, const VMScriptFunction *func);
+void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func);
+
+extern thread_local VMFrameStack GlobalVMStack;
+
+typedef std::pair<const class PType *, unsigned> FTypeAndOffset;
+
+typedef int(*JitFuncPtr)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
+
+class VMScriptFunction : public VMFunction
+{
+public:
+	VMScriptFunction(FName name = NAME_None);
+	~VMScriptFunction();
+	void Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers);
+
+	VMOP *Code;
+	FStatementInfo *LineInfo;
+	FString SourceFileName;
+	int *KonstD;
+	double *KonstF;
+	FString *KonstS;
+	FVoidObj *KonstA;
+	int ExtraSpace;
+	int CodeSize;			// Size of code in instructions (not bytes)
+	unsigned LineInfoCount;
+	unsigned StackSize;
+	VM_UBYTE NumRegD;
+	VM_UBYTE NumRegF;
+	VM_UBYTE NumRegS;
+	VM_UBYTE NumRegA;
+	VM_UHALF NumKonstD;
+	VM_UHALF NumKonstF;
+	VM_UHALF NumKonstS;
+	VM_UHALF 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
+	TArray<FTypeAndOffset> SpecialInits;	// list of all contents on the extra stack which require construction and destruction
+
+	void InitExtra(void *addr);
+	void DestroyExtra(void *addr);
+	int AllocExtraStack(PType *type);
+	int PCToLine(const VMOP *pc);
+
+private:
+	static int FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
+};
diff --git a/source/common/scripting/vm/vmops.h b/source/common/scripting/vm/vmops.h
new file mode 100644
index 000000000..82b3cabc4
--- /dev/null
+++ b/source/common/scripting/vm/vmops.h
@@ -0,0 +1,258 @@
+#ifndef xx
+#define xx(op, name, mode, alt, kreg, ktype) OP_##op,
+#endif
+
+// first row is the opcode
+// second row is the disassembly name
+// third row is the disassembly flags
+// fourth row is the alternative opcode if all 256 constant registers are exhausted.
+// fifth row is the constant register index in the opcode
+// sixth row is the constant register type.
+// OP_PARAM and OP_CMPS need special treatment because they encode this information in the instruction.
+
+xx(NOP,		nop,	NOP,		NOP,	0, 0)		// no operation
+
+// Load constants.
+xx(LI,		li,		LI,			NOP,	0, 0)		// load immediate signed 16-bit constant
+xx(LK,		lk,		LKI,		NOP,	0, 0)		// load integer constant
+xx(LKF,		lk,		LKF,		NOP,	0, 0)		// load float constant
+xx(LKS,		lk,		LKS,		NOP,	0, 0)		// load string constant
+xx(LKP,		lk,		LKP,		NOP,	0, 0)		// load pointer constant
+xx(LK_R,	lk,		RIRII8,		NOP,	0, 0)		// load integer constant indexed
+xx(LKF_R,	lk,		RFRII8,		NOP,	0, 0)		// load float constant indexed
+xx(LKS_R,	lk,		RSRII8,		NOP,	0, 0)		// load string constant indexed
+xx(LKP_R,	lk,		RPRII8,		NOP,	0, 0)		// load pointer constant indexed
+xx(LFP,		lf,		LFP,		NOP,	0, 0)		// load frame pointer
+xx(META,	meta,	RPRP,		NOP,	0, 0)		// load a class's meta data address
+xx(CLSS,	clss,	RPRP,		NOP,	0, 0)		// load a class's descriptor address
+
+// Load from memory. rA = *(rB + rkC)
+xx(LB,		lb,		RIRPKI,		LB_R,	4, REGT_INT)	// load byte
+xx(LB_R,	lb,		RIRPRI,		NOP,	0, 0)
+xx(LH,		lh,		RIRPKI,		LH_R,	4, REGT_INT)	// load halfword
+xx(LH_R,	lh,		RIRPRI,		NOP,	0, 0)
+xx(LW,		lw,		RIRPKI,		LW_R,	4, REGT_INT)	// load word
+xx(LW_R,	lw,		RIRPRI,		NOP,	0, 0)
+xx(LBU,		lbu,	RIRPKI,		LBU_R,	4, REGT_INT)	// load byte unsigned
+xx(LBU_R,	lbu,	RIRPRI,		NOP,	0, 0)
+xx(LHU,		lhu,	RIRPKI,		LHU_R,	4, REGT_INT)	// load halfword unsigned
+xx(LHU_R,	lhu,	RIRPRI,		NOP,	0, 0)
+xx(LSP,		lsp,	RFRPKI,		LSP_R,	4, REGT_INT)	// load single-precision fp
+xx(LSP_R,	lsp,	RFRPRI,		NOP,	0, 0)
+xx(LDP,		ldp,	RFRPKI,		LDP_R,	4, REGT_INT)	// load double-precision fp
+xx(LDP_R,	ldp,	RFRPRI,		NOP,	0, 0)
+xx(LS,		ls,		RSRPKI,		LS_R,	4, REGT_INT)	// load string
+xx(LS_R,	ls,		RSRPRI,		NOP,	0, 0)
+xx(LO,		lo,		RPRPKI,		LO_R,	4, REGT_INT)	// load object
+xx(LO_R,	lo,		RPRPRI,		NOP,	0, 0)
+xx(LP,		lp,		RPRPKI,		LP_R,	4, REGT_INT)	// load pointer
+xx(LP_R,	lp,		RPRPRI,		NOP,	0, 0)
+xx(LV2,		lv2,	RVRPKI,		LV2_R,	4, REGT_INT)	// load vector2
+xx(LV2_R,	lv2,	RVRPRI,		NOP,	0, 0)
+xx(LV3,		lv3,	RVRPKI,		LV3_R,	4, REGT_INT)	// load vector3
+xx(LV3_R,	lv3,	RVRPRI,		NOP,	0, 0)
+xx(LCS,		lcs,	RSRPKI,		LCS_R,	4, REGT_INT)	// load string from char ptr.
+xx(LCS_R,	lcs,	RSRPRI,		NOP,	0, 0)
+
+xx(LBIT,	lbit,	RIRPI8,		NOP,	0, 0)	// rA = !!(*rB & C)  -- *rB is a byte
+
+// Store instructions. *(rA + rkC) = rB
+xx(SB,		sb,		RPRIKI,		SB_R,	4, REGT_INT)		// store byte
+xx(SB_R,	sb,		RPRIRI,		NOP,	0, 0)
+xx(SH,		sh,		RPRIKI,		SH_R,	4, REGT_INT)		// store halfword
+xx(SH_R,	sh,		RPRIRI,		NOP,	0, 0)
+xx(SW,		sw,		RPRIKI,		SW_R,	4, REGT_INT)		// store word
+xx(SW_R,	sw,		RPRIRI,		NOP,	0, 0)
+xx(SSP,		ssp,	RPRFKI,		SSP_R,	4, REGT_INT)		// store single-precision fp
+xx(SSP_R,	ssp,	RPRFRI,		NOP,	0, 0)
+xx(SDP,		sdp,	RPRFKI,		SDP_R,	4, REGT_INT)		// store double-precision fp
+xx(SDP_R,	sdp,	RPRFRI,		NOP,	0, 0)
+xx(SS,		ss,		RPRSKI,		SS_R,	4, REGT_INT)		// store string
+xx(SS_R,	ss,		RPRSRI,		NOP,	0, 0)
+xx(SP,		sp,		RPRPKI,		SP_R,	4, REGT_INT)		// store pointer
+xx(SP_R,	sp,		RPRPRI,		NOP,	0, 0)
+xx(SO,		so,		RPRPKI,		SO_R,	4, REGT_INT)		// store object pointer with write barrier (only needed for non thinkers and non types)
+xx(SO_R,	so,		RPRPRI,		NOP,	0, 0)
+xx(SV2,		sv2,	RPRVKI,		SV2_R,	4, REGT_INT)		// store vector2
+xx(SV2_R,	sv2,	RPRVRI,		NOP,	0, 0)
+xx(SV3,		sv3,	RPRVKI,		SV3_R,	4, REGT_INT)		// store vector3
+xx(SV3_R,	sv3,	RPRVRI,		NOP,	0, 0)
+
+xx(SBIT,	sbit,	RPRII8,		NOP,	0, 0)		// *rA |= C if rB is true, *rA &= ~C otherwise
+
+// Move instructions.
+xx(MOVE,	mov,	RIRI,		NOP,	0, 0)		// dA = dB
+xx(MOVEF,	mov,	RFRF,		NOP,	0, 0)		// fA = fB
+xx(MOVES,	mov,	RSRS,		NOP,	0, 0)		// sA = sB
+xx(MOVEA,	mov,	RPRP,		NOP,	0, 0)		// aA = aB
+xx(MOVEV2,	mov2,	RFRF,		NOP,	0, 0)		// fA = fB (2 elements)
+xx(MOVEV3,	mov3,	RFRF,		NOP,	0, 0)		// fA = fB (3 elements)
+xx(CAST,	cast,	CAST,		NOP,	0, 0)		// xA = xB, conversion specified by C
+xx(CASTB,	castb,	CAST,		NOP,	0, 0)		// xA = !!xB, type specified by C
+xx(DYNCAST_R,	dyncast, RPRPRP,	NOP,	0, 0)		// aA = dyn_cast<aC>(aB);
+xx(DYNCAST_K,	dyncast, RPRPKP,	NOP,	0, 0)		// aA = dyn_cast<aKC>(aB);
+xx(DYNCASTC_R,	dyncastc, RPRPRP,	NOP,	0, 0)		// aA = dyn_cast<aC>(aB); for class types
+xx(DYNCASTC_K,	dyncastc, RPRPKP,	NOP,	0, 0)		// aA = dyn_cast<aKC>(aB);
+
+// Control flow.
+xx(TEST,	test,	RII16,		NOP,	0, 0)		// if (dA != BC) then pc++
+xx(TESTN,	testn,	RII16,		NOP,	0, 0)		// if (dA != -BC) then pc++
+xx(JMP,		jmp,	I24,		NOP,	0, 0)		// pc += ABC		-- The ABC fields contain a signed 24-bit offset.
+xx(IJMP,	ijmp,	RII16,		NOP,	0, 0)		// pc += dA + BC	-- BC is a signed offset. The target instruction must be a JMP.
+xx(PARAM,	param,	__BCP,		NOP,	0, 0)		// push parameter encoded in BC for function call (B=regtype, C=regnum)
+xx(PARAMI,	parami,	I24,		NOP,	0, 0)		// push immediate, signed integer for function call
+xx(CALL,	call,	RPI8I8,		NOP,	0, 0)	// Call function pkA with parameter count B and expected result count C
+xx(CALL_K,	call,	KPI8I8,		CALL,	1, REGT_POINTER)
+xx(VTBL,	vtbl,	RPRPI8,		NOP,	0, 0)	// dereferences a virtual method table.
+xx(SCOPE,	scope,	RPI8,		NOP,	0, 0)		// Scope check at runtime.
+xx(RESULT,	result,	__BCP,		NOP,	0, 0)		// Result should go in register encoded in BC (in caller, after CALL)
+xx(RET,		ret,	I8BCP,		NOP,	0, 0)		// Copy value from register encoded in BC to return value A, possibly returning
+xx(RETI,	reti,	I8I16,		NOP,	0, 0)		// Copy immediate from BC to return value A, possibly returning
+//xx(TRY,		try,	I24,		NOP,	0, 0)		// When an exception is thrown, start searching for a handler at pc + ABC
+//xx(UNTRY,	untry,	I8,			NOP,	0, 0)		// Pop A entries off the exception stack
+xx(THROW,	throw,	THROW,		NOP,	0, 0)		// A == 0: Throw exception object pB
+												// A == 1: Throw exception object pkB
+												// A >= 2: Throw VM exception of type BC
+//xx(CATCH,	catch,	CATCH,		NOP,	0, 0)		// 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
+xx(BOUND,	bound,	RII16,		NOP,	0, 0)		// if rA < 0 or rA >= BC, throw exception
+xx(BOUND_K,	bound,	LKI,		NOP,	0, 0)		// if rA < 0 or rA >= const[BC], throw exception
+xx(BOUND_R,	bound,	RIRI,		NOP,	0, 0)		// if rA < 0 or rA >= rB, throw exception
+
+// String instructions.
+xx(CONCAT,		concat,	RSRSRS,		NOP,	0, 0)		// sA = sB..sC
+xx(LENS,		lens,	RIRS,		NOP,	0, 0)			// dA = sB.Length
+xx(CMPS,		cmps,	I8RXRX,		NOP,	0, 0)		// if ((skB op skC) != (A & 1)) then pc++
+
+// Integer math.
+xx(SLL_RR,		sll,	RIRIRI,		NOP,	0, 0)		// dA = dkB << diC
+xx(SLL_RI,		sll,	RIRII8,		NOP,	0, 0)
+xx(SLL_KR,		sll,	RIKIRI,		SLL_RR,	2, REGT_INT)
+xx(SRL_RR,		srl,	RIRIRI,		NOP,	0, 0)		// dA = dkB >> diC  -- unsigned
+xx(SRL_RI,		srl,	RIRII8,		NOP,	0, 0)
+xx(SRL_KR,		srl,	RIKIRI,		SRL_RR,	2, REGT_INT)
+xx(SRA_RR,		sra,	RIRIRI,		NOP,	0, 0)		// dA = dkB >> diC  -- signed
+xx(SRA_RI,		sra,	RIRII8,		NOP,	0, 0)
+xx(SRA_KR,		sra,	RIKIRI,		SRA_RR,	2, REGT_INT)
+xx(ADD_RR,		add,	RIRIRI,		NOP,	0, 0)		// dA = dB + dkC
+xx(ADD_RK,		add,	RIRIKI,		ADD_RR,	4, REGT_INT)
+xx(ADDI,		addi,	RIRIIs,		NOP,	0, 0)		// dA = dB + C		-- C is a signed 8-bit constant
+xx(SUB_RR,		sub,	RIRIRI,		NOP,	0, 0)		// dA = dkB - dkC
+xx(SUB_RK,		sub,	RIRIKI,		SUB_RR,	4, REGT_INT)
+xx(SUB_KR,		sub,	RIKIRI,		SUB_RR,	2, REGT_INT)
+xx(MUL_RR,		mul,	RIRIRI,		NOP,	0, 0)		// dA = dB * dkC
+xx(MUL_RK,		mul,	RIRIKI,		MUL_RR,	4, REGT_INT)
+xx(DIV_RR,		div,	RIRIRI,		NOP,	0, 0)		// dA = dkB / dkC (signed)
+xx(DIV_RK,		div,	RIRIKI,		DIV_RR,	4, REGT_INT)
+xx(DIV_KR,		div,	RIKIRI,		DIV_RR,	2, REGT_INT)
+xx(DIVU_RR,		divu,	RIRIRI,		NOP,	0, 0)		// dA = dkB / dkC (unsigned)
+xx(DIVU_RK,		divu,	RIRIKI,		DIVU_RR,4, REGT_INT)
+xx(DIVU_KR,		divu,	RIKIRI,		DIVU_RR,2, REGT_INT)
+xx(MOD_RR,		mod,	RIRIRI,		NOP,	0, 0)		// dA = dkB % dkC (signed)
+xx(MOD_RK,		mod,	RIRIKI,		MOD_RR,	4, REGT_INT)
+xx(MOD_KR,		mod,	RIKIRI,		MOD_RR,	2, REGT_INT)
+xx(MODU_RR,		modu,	RIRIRI,		NOP,	0, 0)		// dA = dkB % dkC (unsigned)
+xx(MODU_RK,		modu,	RIRIKI,		MODU_RR,4, REGT_INT)
+xx(MODU_KR,		modu,	RIKIRI,		MODU_RR,2, REGT_INT)
+xx(AND_RR,		and,	RIRIRI,		NOP,	0, 0)		// dA = dB & dkC
+xx(AND_RK,		and,	RIRIKI,		AND_RR,	4, REGT_INT)
+xx(OR_RR,		or,		RIRIRI,		NOP,	0, 0)		// dA = dB | dkC
+xx(OR_RK,		or,		RIRIKI,		OR_RR,	4, REGT_INT)
+xx(XOR_RR,		xor,	RIRIRI,		NOP,	0, 0)		// dA = dB ^ dkC
+xx(XOR_RK,		xor,	RIRIKI,		XOR_RR,	4, REGT_INT)
+xx(MIN_RR,		min,	RIRIRI,		NOP,	0, 0)		// dA = min(dB,dkC)
+xx(MIN_RK,		min,	RIRIKI,		MIN_RR,	4, REGT_INT)
+xx(MAX_RR,		max,	RIRIRI,		NOP,	0, 0)		// dA = max(dB,dkC)
+xx(MAX_RK,		max,	RIRIKI,		MAX_RR,	4, REGT_INT)
+xx(MINU_RR,		minu,	RIRIRI,		NOP,	0, 0)		// dA = min(dB,dkC) unsigned
+xx(MINU_RK,		minu,	RIRIKI,		MIN_RR,	4, REGT_INT)
+xx(MAXU_RR,		maxu,	RIRIRI,		NOP,	0, 0)		// dA = max(dB,dkC) unsigned
+xx(MAXU_RK,		maxu,	RIRIKI,		MAX_RR,	4, REGT_INT)
+xx(ABS,			abs,	RIRI,		NOP,	0, 0)			// dA = abs(dB)
+xx(NEG,			neg,	RIRI,		NOP,	0, 0)			// dA = -dB
+xx(NOT,			not,	RIRI,		NOP,	0, 0)			// dA = ~dB
+xx(EQ_R,		beq,	CIRR,		NOP,	0, 0)			// if ((dB == dkC) != A) then pc++
+xx(EQ_K,		beq,	CIRK,		EQ_R,	4, REGT_INT)
+xx(LT_RR,		blt,	CIRR,		NOP,	0, 0)			// if ((dkB < dkC) != A) then pc++
+xx(LT_RK,		blt,	CIRK,		LT_RR,	4, REGT_INT)
+xx(LT_KR,		blt,	CIKR,		LT_RR,	2, REGT_INT)
+xx(LE_RR,		ble,	CIRR,		NOP,	0, 0)			// if ((dkB <= dkC) != A) then pc++
+xx(LE_RK,		ble,	CIRK,		LE_RR,	4, REGT_INT)
+xx(LE_KR,		ble,	CIKR,		LE_RR,	2, REGT_INT)
+xx(LTU_RR,		bltu,	CIRR,		NOP,	0, 0)			// if ((dkB < dkC) != A) then pc++		-- unsigned
+xx(LTU_RK,		bltu,	CIRK,		LTU_RR,	4, REGT_INT)
+xx(LTU_KR,		bltu,	CIKR,		LTU_RR,	2, REGT_INT)
+xx(LEU_RR,		bleu,	CIRR,		NOP,	0, 0)			// if ((dkB <= dkC) != A) then pc++		-- unsigned
+xx(LEU_RK,		bleu,	CIRK,		LEU_RR,	4, REGT_INT)
+xx(LEU_KR,		bleu,	CIKR,		LEU_RR,	2, REGT_INT)
+
+// Double-precision floating point math.
+xx(ADDF_RR,		add,	RFRFRF,		NOP,	0, 0)		// fA = fB + fkC
+xx(ADDF_RK,		add,	RFRFKF,		ADDF_RR,4, REGT_FLOAT)
+xx(SUBF_RR,		sub,	RFRFRF,		NOP,	0, 0)		// fA = fkB - fkC
+xx(SUBF_RK,		sub,	RFRFKF,		SUBF_RR,4, REGT_FLOAT)
+xx(SUBF_KR,		sub,	RFKFRF,		SUBF_RR,2, REGT_FLOAT)
+xx(MULF_RR,		mul,	RFRFRF,		NOP,	0, 0)		// fA = fB * fkC
+xx(MULF_RK,		mul,	RFRFKF,		MULF_RR,4, REGT_FLOAT)
+xx(DIVF_RR,		div,	RFRFRF,		NOP,	0, 0)		// fA = fkB / fkC
+xx(DIVF_RK,		div,	RFRFKF,		DIVF_RR,4, REGT_FLOAT)
+xx(DIVF_KR,		div,	RFKFRF,		DIVF_RR,2, REGT_FLOAT)
+xx(MODF_RR,		mod,	RFRFRF,		NOP,	0, 0)		// fA = fkB % fkC
+xx(MODF_RK,		mod,	RFRFKF,		MODF_RR,4, REGT_FLOAT)
+xx(MODF_KR,		mod,	RFKFRF,		MODF_RR,4, REGT_FLOAT)
+xx(POWF_RR,		pow,	RFRFRF,		NOP,	0, 0)		// fA = fkB ** fkC
+xx(POWF_RK,		pow,	RFRFKF,		POWF_RR,4, REGT_FLOAT)
+xx(POWF_KR,		pow,	RFKFRF,		POWF_RR,2, REGT_FLOAT)
+xx(MINF_RR,		min,	RFRFRF,		NOP,	0, 0)		// fA = min(fB)fkC)
+xx(MINF_RK,		min,	RFRFKF,		MINF_RR,4, REGT_FLOAT)
+xx(MAXF_RR,		max,	RFRFRF,		NOP,	0, 0)		// fA = max(fB)fkC)
+xx(MAXF_RK,		max,	RFRFKF,		MAXF_RR,4, REGT_FLOAT)
+xx(ATAN2,		atan2,	RFRFRF,		NOP,	0, 0)		// fA = atan2(fB,fC) result is in degrees
+xx(FLOP,		flop,	RFRFI8,		NOP,	0, 0)		// fA = f(fB) where function is selected by C
+xx(EQF_R,		beq,	CFRR,		NOP,	0, 0)			// if ((fB == fkC) != (A & 1)) then pc++
+xx(EQF_K,		beq,	CFRK,		EQF_R,	4, REGT_FLOAT)
+xx(LTF_RR,		blt,	CFRR,		NOP,	0, 0)			// if ((fkB < fkC) != (A & 1)) then pc++
+xx(LTF_RK,		blt,	CFRK,		LTF_RR,	4, REGT_FLOAT)
+xx(LTF_KR,		blt,	CFKR,		LTF_RR,	2, REGT_FLOAT)
+xx(LEF_RR,		ble,	CFRR,		NOP,	0, 0)			// if ((fkb <= fkC) != (A & 1)) then pc++
+xx(LEF_RK,		ble,	CFRK,		LEF_RR,	4, REGT_FLOAT)
+xx(LEF_KR,		ble,	CFKR,		LEF_RR,	2, REGT_FLOAT)
+
+// Vector math. (2D)
+xx(NEGV2,		negv2,	RVRV,		NOP,	0, 0)			// vA = -vB
+xx(ADDV2_RR,	addv2,	RVRVRV,		NOP,	0, 0)		// vA = vB + vkC
+xx(SUBV2_RR,	subv2,	RVRVRV,		NOP,	0, 0)		// vA = vkB - vkC
+xx(DOTV2_RR,	dotv2,	RVRVRV,		NOP,	0, 0)		// va = vB dot vkC
+xx(MULVF2_RR,	mulv2,	RVRVRF,		NOP,	0, 0)		// vA = vkB * fkC
+xx(MULVF2_RK,	mulv2,	RVRVKF,		MULVF2_RR,4, REGT_FLOAT)
+xx(DIVVF2_RR,	divv2,	RVRVRF,		NOP,	0, 0)		// vA = vkB / fkC
+xx(DIVVF2_RK,	divv2,	RVRVKF,		DIVVF2_RR,4, REGT_FLOAT)
+xx(LENV2,		lenv2,	RFRV,		NOP,	0, 0)			// fA = vB.Length
+xx(EQV2_R,		beqv2,	CVRR,		NOP,	0, 0)			// if ((vB == vkC) != A) then pc++ (inexact if A & 32)
+xx(EQV2_K,		beqv2,	CVRK,		NOP,	0, 0)			// this will never be used.
+
+// Vector math (3D)
+xx(NEGV3,		negv3,	RVRV,		NOP,	0, 0)			// vA = -vB
+xx(ADDV3_RR,	addv3,	RVRVRV,		NOP,	0, 0)		// vA = vB + vkC
+xx(SUBV3_RR,	subv3,	RVRVRV,		NOP,	0, 0)		// vA = vkB - vkC
+xx(DOTV3_RR,	dotv3,	RVRVRV,		NOP,	0, 0)		// va = vB dot vkC
+xx(CROSSV_RR,	crossv,	RVRVRV,		NOP,	0, 0)		// vA = vkB cross vkC
+xx(MULVF3_RR,	mulv3,	RVRVRF,		NOP,	0, 0)		// vA = vkB * fkC
+xx(MULVF3_RK,	mulv3,	RVRVKF,		MULVF3_RR,4, REGT_FLOAT)
+xx(DIVVF3_RR,	divv3,	RVRVRF,		NOP,	0, 0)		// vA = vkB / fkC
+xx(DIVVF3_RK,	divv3,	RVRVKF,		DIVVF3_RR,4, REGT_FLOAT)
+xx(LENV3,		lenv3,	RFRV,		NOP,	0, 0)			// fA = vB.Length
+xx(EQV3_R,		beqv3,	CVRR,		NOP,	0, 0)			// if ((vB == vkC) != A) then pc++ (inexact if A & 33)
+xx(EQV3_K,		beqv3,	CVRK,		NOP,	0, 0)			// this will never be used.
+
+// Pointer math.
+xx(ADDA_RR,		add,	RPRPRI,		NOP,	0, 0)		// pA = pB + dkC
+xx(ADDA_RK,		add,	RPRPKI,		ADDA_RR,4, REGT_INT)
+xx(SUBA,		sub,	RIRPRP,		NOP,	0, 0)		// dA = pB - pC
+xx(EQA_R,		beq,	CPRR,		NOP,	0, 0)			// if ((pB == pkC) != A) then pc++
+xx(EQA_K,		beq,	CPRK,		EQA_R,	4, REGT_POINTER)
+
+#undef xx
diff --git a/source/common/utility/basics.h b/source/common/utility/basics.h
index 1ae1f1764..f5ecdd1a2 100644
--- a/source/common/utility/basics.h
+++ b/source/common/utility/basics.h
@@ -1,5 +1,4 @@
-#ifndef __BASICS_H
-#define __BASICS_H
+#pragma once
 
 #include <stddef.h>
 #include <stdint.h>
@@ -56,4 +55,52 @@ typedef uint32_t			angle_t;
 
 using INTBOOL = int;
 using BITFIELD = uint32_t;
+
+
+#if defined(_MSC_VER)
+#define NOVTABLE __declspec(novtable)
+#else
+#define NOVTABLE
 #endif
+
+// always use our own definition for consistency.
+#ifdef M_PI
+#undef M_PI
+#endif
+
+const double M_PI = 3.14159265358979323846;	// matches value in gcc v2 math.h
+
+inline float DEG2RAD(float deg)
+{
+	return deg * float(M_PI / 180.0);
+}
+
+inline double DEG2RAD(double deg)
+{
+	return deg * (M_PI / 180.0);
+}
+
+inline float RAD2DEG(float deg)
+{
+	return deg * float(180. / M_PI);
+}
+
+
+// Auto-registration sections for GCC.
+// Apparently, you cannot do string concatenation inside section attributes.
+#ifdef __MACH__
+#define SECTION_AREG "__DATA,areg"
+#define SECTION_CREG "__DATA,creg"
+#define SECTION_FREG "__DATA,freg"
+#define SECTION_GREG "__DATA,greg"
+#define SECTION_MREG "__DATA,mreg"
+#define SECTION_YREG "__DATA,yreg"
+#else
+#define SECTION_AREG "areg"
+#define SECTION_CREG "creg"
+#define SECTION_FREG "freg"
+#define SECTION_GREG "greg"
+#define SECTION_MREG "mreg"
+#define SECTION_YREG "yreg"
+#endif
+
diff --git a/source/core/raze_sound.h b/source/core/raze_sound.h
index 4ee5ec22c..dc7c809cf 100644
--- a/source/core/raze_sound.h
+++ b/source/core/raze_sound.h
@@ -22,16 +22,6 @@ inline void FX_SetReverbDelay(int delay)
 { 
 }
 
-inline int S_FindSoundByResID(int ndx)
-{
-	return soundEngine->FindSoundByResID(ndx);
-}
-
-inline int S_FindSound(const char* name)
-{
-	return soundEngine->FindSound(name);
-}
-
 int S_LookupSound(const char* fn);
 class FSerializer;
 void S_SerializeSounds(FSerializer& arc);
diff --git a/source/core/serializer.cpp b/source/core/serializer.cpp
index 2c0f11811..715103acb 100644
--- a/source/core/serializer.cpp
+++ b/source/core/serializer.cpp
@@ -43,6 +43,7 @@
 #include "rapidjson/prettywriter.h"
 #include "rapidjson/document.h"
 #include "serializer.h"
+#include "dobject.h"
 #include "filesystem.h"
 #include "v_font.h"
 #include "v_text.h"
@@ -50,6 +51,9 @@
 #include "utf8.h"
 #include "printf.h"
 #include "raze_sound.h"
+#include "engineerrors.h"
+#include "textures.h"
+#include "texturemanager.h"
 
 bool save_full = false;
 
@@ -145,10 +149,8 @@ struct FWriter
 	PrettyWriter *mWriter2;
 	TArray<bool> mInObject;
 	rapidjson::StringBuffer mOutString;
-#if 0
 	TArray<DObject *> mDObjects;
 	TMap<DObject *, int> mObjectMap;
-#endif
 	
 	FWriter(bool pretty)
 	{
@@ -287,9 +289,7 @@ struct FReader
 {
 	TArray<FJSONObject> mObjects;
 	rapidjson::Document mDoc;
-#if 0
 	TArray<DObject *> mDObjects;
-#endif
 	rapidjson::Value *mKeyValue = nullptr;
 	bool mObjectsRead = false;
 
@@ -740,7 +740,6 @@ const char *FSerializer::GetKey()
 	return (it++)->name.GetString();
 }
 
-#if 0
 //==========================================================================
 //
 // Writes out all collected objects
@@ -805,7 +804,7 @@ void FSerializer::ReadObjects(bool hubtravel)
 					{
 						Printf(TEXTCOLOR_RED "Unknown object class '%s' in savegame\n", clsname.GetChars());
 						founderrors = true;
-						r->mDObjects[i] = RUNTIME_CLASS(AActor)->CreateNew();	// make sure we got at least a valid pointer for the duration of the loading process.
+						r->mDObjects[i] = RUNTIME_CLASS(DObject)->CreateNew();	// make sure we got at least a valid pointer for the duration of the loading process.
 						r->mDObjects[i]->Destroy();								// but we do not want to keep this around, so destroy it right away.
 					}
 					else
@@ -836,7 +835,7 @@ void FSerializer::ReadObjects(bool hubtravel)
 								obj->SerializeUserVars(*this);
 								obj->Serialize(*this);
 							}
-							catch (CRecoverableError &err)
+							catch (CEngineError &err)
 							{
 								// In case something in here throws an error, let's continue and deal with it later.
 								Printf(TEXTCOLOR_RED "'%s'\n while restoring %s\n", err.GetMessage(), obj ? obj->GetClass()->TypeName.GetChars() : "invalid object");
@@ -870,7 +869,6 @@ void FSerializer::ReadObjects(bool hubtravel)
 		}
 	}
 }
-#endif
 
 //==========================================================================
 //
@@ -881,9 +879,7 @@ void FSerializer::ReadObjects(bool hubtravel)
 const char *FSerializer::GetOutput(unsigned *len)
 {
 	if (isReading()) return nullptr;
-#if 0
 	WriteObjects();
-#endif
 	EndObject();
 	if (len != nullptr)
 	{
@@ -902,9 +898,7 @@ FCompressedBuffer FSerializer::GetCompressedOutput()
 {
 	if (isReading()) return{ 0,0,0,0,0,nullptr };
 	FCompressedBuffer buff;
-#if 0
 	WriteObjects();
-#endif
 	EndObject();
 	buff.mSize = (unsigned)w->mOutString.GetSize();
 	buff.mZipFlags = 0;
@@ -1248,7 +1242,6 @@ FSerializer &SerializePointer(FSerializer &arc, const char *key, T *&value, T **
 	return arc;
 }
 
-#if 0
 //==========================================================================
 //
 //
@@ -1279,9 +1272,9 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
 			FTexture *pic = TexMan.GetTexture(chk);
 			const char *name;
 
-			if (Wads.GetLinkedTexture(pic->SourceLump) == pic)
+			if (fileSystem.GetLinkedTexture(pic->SourceLump) == pic)
 			{
-				name = Wads.GetLumpFullName(pic->SourceLump);
+				name = fileSystem.GetFileFullName(pic->SourceLump);
 			}
 			else
 			{
@@ -1290,7 +1283,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
 			arc.WriteKey(key);
 			arc.w->StartArray();
 			arc.w->String(name);
-			arc.w->Int(static_cast<int>(pic->UseType));
+			int ut = static_cast<int>(pic->GetUseType());
+			arc.w->Int(ut);
 			arc.w->EndArray();
 		}
 	}
@@ -1334,9 +1328,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe
 	}
 	return arc;
 }
-#endif
 
-#if 0
 //==========================================================================
 //
 // This never uses defval and instead uses 'null' as default
@@ -1352,11 +1344,13 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje
 		if (value != nullptr && !(value->ObjectFlags & (OF_EuthanizeMe | OF_Transient)))
 		{
 			int ndx;
+			/*
 			if (value == WP_NOCHANGE)
 			{
 				ndx = -1;
 			}
 			else
+			*/
 			{
 				int *pndx = arc.w->mObjectMap.CheckKey(value);
 				if (pndx != nullptr)
@@ -1397,7 +1391,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje
 				int index = val->GetInt();
 				if (index == -1)
 				{
-					value = WP_NOCHANGE;
+					value = nullptr;// WP_NOCHANGE;
 				}
 				else
 				{
@@ -1429,7 +1423,6 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje
 	}
 	return arc;
 }
-#endif
 
 //==========================================================================
 //
@@ -1519,6 +1512,54 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI
 
 }
 
+//==========================================================================
+//
+// almost, but not quite the same as the above.
+//
+//==========================================================================
+
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&clst, PClass **def)
+{
+	if (arc.isWriting())
+	{
+		if (!arc.w->inObject() || def == nullptr || clst != *def)
+		{
+			arc.WriteKey(key);
+			if (clst == nullptr)
+			{
+				arc.w->Null();
+			}
+			else
+			{
+				arc.w->String(clst->TypeName.GetChars());
+			}
+		}
+	}
+	else
+	{
+		auto val = arc.r->FindKey(key);
+		if (val != nullptr)
+		{
+			if (val->IsString())
+			{
+				clst = PClass::FindClass(UnicodeToString(val->GetString()));
+			}
+			else if (val->IsNull())
+			{
+				clst = nullptr;
+			}
+			else
+			{
+				Printf(TEXTCOLOR_RED "string type expected for '%s'\n", key);
+				clst = nullptr;
+				arc.mErrors++;
+			}
+		}
+	}
+	return arc;
+
+}
+
 //==========================================================================
 //
 //
@@ -1561,6 +1602,29 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &pstr, FString
 
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&font, FFont **def)
+{
+	if (arc.isWriting())
+	{
+		FName n = font? font->GetName() : NAME_None;
+		return arc(key, n);
+	}
+	else
+	{
+		FName n = NAME_None;
+		arc(key, n);
+		font = n == NAME_None? nullptr : V_GetFont(n.GetChars());
+		return arc;
+	}
+
+}
+
 //==========================================================================
 //
 // Handler to retrieve a numeric value of any kind.
@@ -1618,4 +1682,10 @@ FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &value, N
 	return arc;
 }
 
+#include "renderstyle.h"
+FSerializer& Serialize(FSerializer& arc, const char* key, FRenderStyle& style, FRenderStyle* def)
+{
+	return arc.Array(key, &style.BlendOp, def ? &def->BlendOp : nullptr, 4);
+}
+
 SaveRecords saveRecords;
diff --git a/source/core/serializer.h b/source/core/serializer.h
index 24bb18c38..4a4199b33 100644
--- a/source/core/serializer.h
+++ b/source/core/serializer.h
@@ -14,9 +14,12 @@ extern bool save_full;
 
 struct FWriter;
 struct FReader;
+class PClass;
 class FFont;
 class FSoundID;
 struct FRenderStyle;
+class DObject;
+class FTextureID;
 
 inline bool nullcmp(const void *buffer, size_t length)
 {
@@ -181,13 +184,14 @@ FSerializer &Serialize(FSerializer &arc, const char *key, int16_t &value, int16_
 FSerializer &Serialize(FSerializer &arc, const char *key, uint16_t &value, uint16_t *defval);
 FSerializer &Serialize(FSerializer &arc, const char *key, double &value, double *defval);
 FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *defval);
+FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTextureID *defval);
+FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObject ** /*defval*/, bool *retcode = nullptr);
 FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *defval);
 FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def);
 FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def);
 FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def);
 
 
-#if 0
 template<class T>
 FSerializer &Serialize(FSerializer &arc, const char *key, T *&value, T **)
 {
@@ -197,20 +201,6 @@ FSerializer &Serialize(FSerializer &arc, const char *key, T *&value, T **)
 	return arc;
 }
 
-template<class T>
-FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr<T> &value, TObjPtr<T> *)
-{
-	Serialize(arc, key, value.o, nullptr);
-	return arc; 
-}
-
-template<class T>
-FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr<T> &value, T *)
-{
-	Serialize(arc, key, value.o, nullptr);
-	return arc;
-}
-#endif
 
 template<class T, class TT>
 FSerializer &Serialize(FSerializer &arc, const char *key, TArray<T, TT> &value, TArray<T, TT> *def)
@@ -237,6 +227,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, TArray<T, TT> &value,
 	return arc;
 }
 
+template<> FSerializer& Serialize(FSerializer& arc, const char* key, PClass*& clst, PClass** def);
+template<> FSerializer& Serialize(FSerializer& arc, const char* key, FFont*& font, FFont** def);
 
 inline FSerializer &Serialize(FSerializer &arc, const char *key, DVector3 &p, DVector3 *def)
 {