- added a first bunch of ZScript code.

# Conflicts:
#	source/CMakeLists.txt
#	source/common/utility/basics.h
#	source/core/serializer.h
This commit is contained in:
Christoph Oelckers 2020-04-06 21:10:07 +02:00
parent c1f7cf1c3a
commit c9b2399cd0
34 changed files with 15258 additions and 109 deletions

View file

@ -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 )
target_architecture(TARGET_ARCHITECTURE)
if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" )
set( HAVE_VM_JIT ON )
#option (HAVE_VULKAN "Enable Vulkan support" ON)
#endif()
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}" )

View file

@ -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/.+")

View file

@ -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);
}

View file

@ -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.

View file

@ -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.

View file

@ -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 *);

View file

@ -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));
}

View file

@ -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);
};

View file

@ -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);
}

View file

@ -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();

File diff suppressed because it is too large Load diff

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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]);
}

File diff suppressed because it is too large Load diff

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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();

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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");
}

View file

@ -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);
};

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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)
{