mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-26 11:40:44 +00:00
- 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:
parent
c1f7cf1c3a
commit
c9b2399cd0
34 changed files with 15258 additions and 109 deletions
|
@ -177,11 +177,12 @@ find_package( ZLIB )
|
|||
|
||||
include( TargetArch )
|
||||
|
||||
# Things for later. Currently we have no VM and no Vulkan
|
||||
#if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" )
|
||||
# set( HAVE_VM_JIT ON )
|
||||
# option (HAVE_VULKAN "Enable Vulkan support" ON)
|
||||
#endif()
|
||||
target_architecture(TARGET_ARCHITECTURE)
|
||||
|
||||
if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" )
|
||||
set( HAVE_VM_JIT ON )
|
||||
#option (HAVE_VULKAN "Enable Vulkan support" ON)
|
||||
endif()
|
||||
|
||||
# no, we're not using external asmjit for now, we made too many modifications to our's.
|
||||
# if the asmjit author uses our changes then we'll update this.
|
||||
|
@ -306,8 +307,8 @@ set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${DEB_C_FLAGS} -D_DEBUG" )
|
|||
option(FORCE_INTERNAL_ZLIB "Use internal zlib")
|
||||
option(FORCE_INTERNAL_JPEG "Use internal jpeg")
|
||||
option(FORCE_INTERNAL_BZIP2 "Use internal bzip2")
|
||||
#option(FORCE_INTERNAL_ASMJIT "Use internal asmjit" ON)
|
||||
#mark_as_advanced( FORCE_INTERNAL_ASMJIT )
|
||||
option(FORCE_INTERNAL_ASMJIT "Use internal asmjit" ON)
|
||||
mark_as_advanced( FORCE_INTERNAL_ASMJIT )
|
||||
|
||||
if (HAVE_VULKAN)
|
||||
add_subdirectory( libraries/glslang/glslang)
|
||||
|
@ -334,31 +335,31 @@ else()
|
|||
set( ZLIB_LIBRARY z )
|
||||
endif()
|
||||
|
||||
#if( HAVE_VM_JIT AND UNIX )
|
||||
# check_symbol_exists( "backtrace" "execinfo.h" HAVE_BACKTRACE )
|
||||
# if( NOT HAVE_BACKTRACE )
|
||||
# set( CMAKE_REQUIRED_FLAGS "-lexecinfo" )
|
||||
# check_symbol_exists( "backtrace" "execinfo.h" HAVE_LIBEXECINFO )
|
||||
# if( HAVE_LIBEXECINFO )
|
||||
# set( ALL_C_FLAGS "${ALL_C_FLAGS} -lexecinfo" )
|
||||
# else( HAVE_LIBEXECINFO )
|
||||
# set( HAVE_VM_JIT NO )
|
||||
# endif( HAVE_LIBEXECINFO )
|
||||
# endif( NOT HAVE_BACKTRACE )
|
||||
#endif( HAVE_VM_JIT AND UNIX )
|
||||
if( HAVE_VM_JIT AND UNIX )
|
||||
check_symbol_exists( "backtrace" "execinfo.h" HAVE_BACKTRACE )
|
||||
if( NOT HAVE_BACKTRACE )
|
||||
set( CMAKE_REQUIRED_FLAGS "-lexecinfo" )
|
||||
check_symbol_exists( "backtrace" "execinfo.h" HAVE_LIBEXECINFO )
|
||||
if( HAVE_LIBEXECINFO )
|
||||
set( ALL_C_FLAGS "${ALL_C_FLAGS} -lexecinfo" )
|
||||
else( HAVE_LIBEXECINFO )
|
||||
set( HAVE_VM_JIT NO )
|
||||
endif( HAVE_LIBEXECINFO )
|
||||
endif( NOT HAVE_BACKTRACE )
|
||||
endif( HAVE_VM_JIT AND UNIX )
|
||||
|
||||
#if( ${HAVE_VM_JIT} )
|
||||
# if( ASMJIT_FOUND AND NOT FORCE_INTERNAL_ASMJIT )
|
||||
# message( STATUS "Using system asmjit, includes found at ${ASMJIT_INCLUDE_DIR}" )
|
||||
# else()
|
||||
# message( STATUS "Using internal asmjit" )
|
||||
# set( SKIP_INSTALL_ALL TRUE ) # Avoid installing asmjit
|
||||
# add_subdirectory( libraries/asmjit )
|
||||
# set( ASMJIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/asmjit )
|
||||
# set( ASMJIT_LIBRARIES asmjit )
|
||||
# set( ASMJIT_LIBRARY asmjit )
|
||||
# endif()
|
||||
#endif()
|
||||
if( ${HAVE_VM_JIT} )
|
||||
if( ASMJIT_FOUND AND NOT FORCE_INTERNAL_ASMJIT )
|
||||
message( STATUS "Using system asmjit, includes found at ${ASMJIT_INCLUDE_DIR}" )
|
||||
else()
|
||||
message( STATUS "Using internal asmjit" )
|
||||
set( SKIP_INSTALL_ALL TRUE ) # Avoid installing asmjit alongside zdoom
|
||||
add_subdirectory( libraries/asmjit )
|
||||
set( ASMJIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/asmjit )
|
||||
set( ASMJIT_LIBRARIES asmjit )
|
||||
set( ASMJIT_LIBRARY asmjit )
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if( JPEG_FOUND AND NOT FORCE_INTERNAL_JPEG )
|
||||
message( STATUS "Using system jpeg library, includes found at ${JPEG_INCLUDE_DIR}" )
|
||||
|
|
|
@ -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/.+")
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 *);
|
||||
|
|
224
source/common/scripting/core/scopebarrier.cpp
Normal file
224
source/common/scripting/core/scopebarrier.cpp
Normal 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));
|
||||
}
|
70
source/common/scripting/core/scopebarrier.h
Normal file
70
source/common/scripting/core/scopebarrier.h
Normal 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);
|
||||
|
||||
};
|
||||
|
605
source/common/scripting/core/symbols.cpp
Normal file
605
source/common/scripting/core/symbols.cpp
Normal 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);
|
||||
}
|
269
source/common/scripting/core/symbols.h
Normal file
269
source/common/scripting/core/symbols.h
Normal 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();
|
2480
source/common/scripting/core/types.cpp
Normal file
2480
source/common/scripting/core/types.cpp
Normal file
File diff suppressed because it is too large
Load diff
625
source/common/scripting/core/types.h
Normal file
625
source/common/scripting/core/types.h
Normal 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;
|
||||
|
687
source/common/scripting/core/vmdisasm.cpp
Normal file
687
source/common/scripting/core/vmdisasm.cpp
Normal 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);
|
||||
}
|
||||
|
278
source/common/scripting/interface/stringformat.cpp
Normal file
278
source/common/scripting/interface/stringformat.cpp
Normal 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;
|
||||
}
|
564
source/common/scripting/jit/jit.cpp
Normal file
564
source/common/scripting/jit/jit.cpp
Normal 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();
|
||||
}
|
8
source/common/scripting/jit/jit.h
Normal file
8
source/common/scripting/jit/jit.h
Normal 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);
|
690
source/common/scripting/jit/jit_call.cpp
Normal file
690
source/common/scripting/jit/jit_call.cpp
Normal 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 ¶m = *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 = ®S[bc]; break;
|
||||
case REGT_POINTER: reg = ®A[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;
|
||||
}
|
319
source/common/scripting/jit/jit_flow.cpp
Normal file
319
source/common/scripting/jit/jit_flow.cpp
Normal 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);
|
||||
}
|
||||
}
|
360
source/common/scripting/jit/jit_load.cpp
Normal file
360
source/common/scripting/jit/jit_load.cpp
Normal 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]);
|
||||
}
|
1524
source/common/scripting/jit/jit_math.cpp
Normal file
1524
source/common/scripting/jit/jit_math.cpp
Normal file
File diff suppressed because it is too large
Load diff
272
source/common/scripting/jit/jit_move.cpp
Normal file
272
source/common/scripting/jit/jit_move.cpp
Normal 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);
|
||||
}
|
970
source/common/scripting/jit/jit_runtime.cpp
Normal file
970
source/common/scripting/jit/jit_runtime.cpp
Normal 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;
|
||||
}
|
176
source/common/scripting/jit/jit_store.cpp
Normal file
176
source/common/scripting/jit/jit_store.cpp
Normal 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);
|
||||
}
|
337
source/common/scripting/jit/jitintern.h
Normal file
337
source/common/scripting/jit/jitintern.h
Normal 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();
|
790
source/common/scripting/vm/vm.h
Normal file
790
source/common/scripting/vm/vm.h
Normal 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> ¶ms, 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
|
253
source/common/scripting/vm/vmexec.cpp
Normal file
253
source/common/scripting/vm/vmexec.cpp
Normal 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
|
2017
source/common/scripting/vm/vmexec.h
Normal file
2017
source/common/scripting/vm/vmexec.h
Normal file
File diff suppressed because it is too large
Load diff
771
source/common/scripting/vm/vmframe.cpp
Normal file
771
source/common/scripting/vm/vmframe.cpp
Normal 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(®s[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> ¶ms, 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");
|
||||
}
|
||||
|
483
source/common/scripting/vm/vmintern.h
Normal file
483
source/common/scripting/vm/vmintern.h
Normal 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 *¶m) 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);
|
||||
};
|
258
source/common/scripting/vm/vmops.h
Normal file
258
source/common/scripting/vm/vmops.h
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue