diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fdda2df9..7b54851b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,11 +177,12 @@ find_package( ZLIB ) include( TargetArch ) -# Things for later. Currently we have no VM and no Vulkan -#if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" ) -# set( HAVE_VM_JIT ON ) -# option (HAVE_VULKAN "Enable Vulkan support" ON) -#endif() +target_architecture(TARGET_ARCHITECTURE) + +if( ${TARGET_ARCHITECTURE} MATCHES "x86_64" ) + set( HAVE_VM_JIT ON ) + #option (HAVE_VULKAN "Enable Vulkan support" ON) +endif() # no, we're not using external asmjit for now, we made too many modifications to our's. # if the asmjit author uses our changes then we'll update this. @@ -306,8 +307,8 @@ set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${DEB_C_FLAGS} -D_DEBUG" ) option(FORCE_INTERNAL_ZLIB "Use internal zlib") option(FORCE_INTERNAL_JPEG "Use internal jpeg") option(FORCE_INTERNAL_BZIP2 "Use internal bzip2") -#option(FORCE_INTERNAL_ASMJIT "Use internal asmjit" ON) -#mark_as_advanced( FORCE_INTERNAL_ASMJIT ) +option(FORCE_INTERNAL_ASMJIT "Use internal asmjit" ON) +mark_as_advanced( FORCE_INTERNAL_ASMJIT ) if (HAVE_VULKAN) add_subdirectory( libraries/glslang/glslang) @@ -334,31 +335,31 @@ else() set( ZLIB_LIBRARY z ) endif() -#if( HAVE_VM_JIT AND UNIX ) -# check_symbol_exists( "backtrace" "execinfo.h" HAVE_BACKTRACE ) -# if( NOT HAVE_BACKTRACE ) -# set( CMAKE_REQUIRED_FLAGS "-lexecinfo" ) -# check_symbol_exists( "backtrace" "execinfo.h" HAVE_LIBEXECINFO ) -# if( HAVE_LIBEXECINFO ) -# set( ALL_C_FLAGS "${ALL_C_FLAGS} -lexecinfo" ) -# else( HAVE_LIBEXECINFO ) -# set( HAVE_VM_JIT NO ) -# endif( HAVE_LIBEXECINFO ) -# endif( NOT HAVE_BACKTRACE ) -#endif( HAVE_VM_JIT AND UNIX ) +if( HAVE_VM_JIT AND UNIX ) + check_symbol_exists( "backtrace" "execinfo.h" HAVE_BACKTRACE ) + if( NOT HAVE_BACKTRACE ) + set( CMAKE_REQUIRED_FLAGS "-lexecinfo" ) + check_symbol_exists( "backtrace" "execinfo.h" HAVE_LIBEXECINFO ) + if( HAVE_LIBEXECINFO ) + set( ALL_C_FLAGS "${ALL_C_FLAGS} -lexecinfo" ) + else( HAVE_LIBEXECINFO ) + set( HAVE_VM_JIT NO ) + endif( HAVE_LIBEXECINFO ) + endif( NOT HAVE_BACKTRACE ) +endif( HAVE_VM_JIT AND UNIX ) -#if( ${HAVE_VM_JIT} ) -# if( ASMJIT_FOUND AND NOT FORCE_INTERNAL_ASMJIT ) -# message( STATUS "Using system asmjit, includes found at ${ASMJIT_INCLUDE_DIR}" ) -# else() -# message( STATUS "Using internal asmjit" ) -# set( SKIP_INSTALL_ALL TRUE ) # Avoid installing asmjit -# add_subdirectory( libraries/asmjit ) -# set( ASMJIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/asmjit ) -# set( ASMJIT_LIBRARIES asmjit ) -# set( ASMJIT_LIBRARY asmjit ) -# endif() -#endif() +if( ${HAVE_VM_JIT} ) + if( ASMJIT_FOUND AND NOT FORCE_INTERNAL_ASMJIT ) + message( STATUS "Using system asmjit, includes found at ${ASMJIT_INCLUDE_DIR}" ) + else() + message( STATUS "Using internal asmjit" ) + set( SKIP_INSTALL_ALL TRUE ) # Avoid installing asmjit alongside zdoom + add_subdirectory( libraries/asmjit ) + set( ASMJIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/asmjit ) + set( ASMJIT_LIBRARIES asmjit ) + set( ASMJIT_LIBRARY asmjit ) + endif() +endif() if( JPEG_FOUND AND NOT FORCE_INTERNAL_JPEG ) message( STATUS "Using system jpeg library, includes found at ${JPEG_INCLUDE_DIR}" ) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 93b6c92f3..9dbbdd373 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -611,7 +611,11 @@ file( GLOB HEADER_FILES common/textures/*.h common/thirdparty/*.h common/thirdparty/rapidjson/*.h - common/thirdparty/math./*h + common/thirdparty/math/*h + common/scripting/core/*h + common/scripting/vm/*h + common/scripting/jit/*h + common/scripting/interface/*.h build/src/*.h platform/win32/*.h @@ -775,6 +779,21 @@ set (PCH_SOURCES common/objects/dobject.cpp common/objects/dobjgc.cpp common/objects/dobjtype.cpp + common/scripting/core/symbols.cpp + common/scripting/core/types.cpp + common/scripting/core/scopebarrier.cpp + common/scripting/core/vmdisasm.cpp + common/scripting/vm/vmexec.cpp + common/scripting/vm/vmframe.cpp + common/scripting/jit/jit.cpp + common/scripting/jit/jit_call.cpp + common/scripting/jit/jit_flow.cpp + common/scripting/jit/jit_load.cpp + common/scripting/jit/jit_math.cpp + common/scripting/jit/jit_move.cpp + common/scripting/jit/jit_runtime.cpp + common/scripting/jit/jit_store.cpp + common/scripting/interface/stringformat.cpp core/utility/stats.cpp @@ -927,6 +946,11 @@ include_directories( common/utility common/console common/engine + common/objects + common/scripting/interface + common/scripting/core + common/scripting/vm + common/scripting/jit ${CMAKE_BINARY_DIR}/libraries/gdtoa ${SYSTEM_SOURCES_DIR} @@ -1042,7 +1066,11 @@ source_group("Common\\Engine" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/c source_group("Common\\Objects" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/objects/.+") source_group("Common\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/fonts/.+") source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") -source_group("Common\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/.+") +source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") +source_group("Common\\Scripting" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/.+") +source_group("Common\\Scripting\\Core" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/core/.+") +source_group("Common\\Scripting\\VM" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/vm/.+") +source_group("Common\\Scripting\\JIT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/jit/.+") source_group("Common\\Third Party" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/.+") source_group("Common\\Third Party\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/math/.+") source_group("Common\\Third Party\\RapidJSON" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/rapidjson/.+") diff --git a/source/common/audio/sound/s_soundinternal.h b/source/common/audio/sound/s_soundinternal.h index 0d0060e9f..b1c55b308 100644 --- a/source/common/audio/sound/s_soundinternal.h +++ b/source/common/audio/sound/s_soundinternal.h @@ -91,8 +91,8 @@ enum ROLLOFF_Custom // Lookup volume from SNDCURVE }; -int S_FindSound(const char *logicalname); -int S_FindSoundByResID(int snd_id); +inline int S_FindSoundByResID(int ndx); +inline int S_FindSound(const char* name); // An index into the S_sfx[] array. class FSoundID @@ -430,3 +430,12 @@ struct FReverbField }; +inline int S_FindSoundByResID(int ndx) +{ + return soundEngine->FindSoundByResID(ndx); +} + +inline int S_FindSound(const char* name) +{ + return soundEngine->FindSound(name); +} diff --git a/source/common/objects/dobjgc.cpp b/source/common/objects/dobjgc.cpp index 201e69fc7..96ff138c8 100644 --- a/source/common/objects/dobjgc.cpp +++ b/source/common/objects/dobjgc.cpp @@ -264,7 +264,7 @@ void MarkArray(DObject **obj, size_t count) static void MarkRoot() { - int i; + //int i; Gray = NULL; // Time to propagate the marks. diff --git a/source/common/objects/dobjtype.cpp b/source/common/objects/dobjtype.cpp index a68cb11be..293685f4f 100644 --- a/source/common/objects/dobjtype.cpp +++ b/source/common/objects/dobjtype.cpp @@ -42,6 +42,8 @@ #include "autosegs.h" #include "v_text.h" #include "c_cvars.h" +#include "symbols.h" +#include "types.h" // MACROS ------------------------------------------------------------------ @@ -89,7 +91,6 @@ static const size_t TheEnd = ~(size_t)0; static void RecurseWriteFields(const PClass *type, FSerializer &ar, const void *addr) { -#if 0 if (type != nullptr) { RecurseWriteFields(type->ParentClass, ar, addr); @@ -113,7 +114,6 @@ static void RecurseWriteFields(const PClass *type, FSerializer &ar, const void * } } } -#endif } // Same as WriteValue, but does not create a new object in the serializer @@ -133,7 +133,6 @@ bool PClass::ReadAllFields(FSerializer &ar, void *addr) const { bool readsomething = false; bool foundsomething = false; -#if 0 const char *key; key = ar.GetKey(); if (strcmp(key, "classtype")) @@ -175,7 +174,6 @@ bool PClass::ReadAllFields(FSerializer &ar, void *addr) const key+6, TypeName.GetChars()); } } -#endif return readsomething || !foundsomething; } @@ -285,7 +283,7 @@ void PClass::StaticShutdown () for (auto cls : AllClasses) delete cls; // Unless something went wrong, anything left here should be class and type objects only, which do not own any scripts. bShutdown = true; - //TypeTable.Clear(); + TypeTable.Clear(); ClassDataAllocator.FreeAllBlocks(); AllClasses.Clear(); //PClassActor::AllActorClasses.Clear(); @@ -492,12 +490,10 @@ void PClass::InitializeSpecials(void *addr, void *defaults, TArrayInitializeSpecials(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 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 ScriptPointers; // Collect all arrays to pointers in scripted fields. -#if 0 for (auto field : Fields) { if (!(field->Flags & VARF_Native)) @@ -899,7 +884,6 @@ void PClass::BuildArrayPointers() field->Type->SetPointerArray(Defaults, unsigned(field->Offset), &ScriptPointers); } } -#endif if (ScriptPointers.Size() == 0) { // No new pointers: Just use the same ArrayPointers as the parent. @@ -948,7 +932,6 @@ const PClass *PClass::NativeClass() const return cls; } -#if 0 VMFunction *PClass::FindFunction(FName clsname, FName funcname) { auto cls = PClass::FindClass(clsname); @@ -968,6 +951,7 @@ void PClass::FindFunction(VMFunction **pptr, FName clsname, FName funcname) FunctionPtrList.Push(pptr); } +#if 0 unsigned GetVirtualIndex(PClass *cls, const char *funcname) { // Look up the virtual function index in the defining class because this may have gotten overloaded in subclasses with something different than a virtual override. diff --git a/source/common/objects/dobjtype.h b/source/common/objects/dobjtype.h index e09f7714e..7e3fcec84 100644 --- a/source/common/objects/dobjtype.h +++ b/source/common/objects/dobjtype.h @@ -17,14 +17,14 @@ class VMException : public DObject // An action function ------------------------------------------------------- -struct FState; -struct StateCallData; class VMFrameStack; struct VMValue; struct VMReturn; class VMFunction; class PClassType; struct FNamespaceManager; +class PSymbol; +class PField; enum { @@ -44,9 +44,9 @@ public: void InitializeDefaults(); #if 0 int FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc); +#endif PSymbol *FindSymbol(FName symname, bool searchparents) const; PField *AddField(FName name, PType *type, uint32_t flags); -#endif static void StaticInit(); static void StaticShutdown(); @@ -69,9 +69,7 @@ public: TArray Virtuals; // virtual function table TArray MetaInits; TArray SpecialInits; -#if 0 TArray Fields; -#endif PClassType *VMType = nullptr; void (*ConstructNative)(void *); diff --git a/source/common/scripting/core/scopebarrier.cpp b/source/common/scripting/core/scopebarrier.cpp new file mode 100644 index 000000000..f4d584c7b --- /dev/null +++ b/source/common/scripting/core/scopebarrier.cpp @@ -0,0 +1,224 @@ +/* +** scopebarrier.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2017 ZZYZX +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "dobject.h" +#include "scopebarrier.h" +#include "types.h" +#include "vmintern.h" + + +// Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. +int FScopeBarrier::SideFromFlags(int flags) +{ + if (flags & VARF_UI) + return Side_UI; + if (flags & VARF_Play) + return Side_Play; + if (flags & VARF_VirtualScope) + return Side_Virtual; + if (flags & VARF_ClearScope) + return Side_Clear; + return Side_PlainData; +} + +// same as above, but from object flags +int FScopeBarrier::SideFromObjectFlags(EScopeFlags flags) +{ + if (flags & Scope_UI) + return Side_UI; + if (flags & Scope_Play) + return Side_Play; + return Side_PlainData; +} + +// +int FScopeBarrier::FlagsFromSide(int side) +{ + switch (side) + { + case Side_Play: + return VARF_Play; + case Side_UI: + return VARF_UI; + case Side_Virtual: + return VARF_VirtualScope; + case Side_Clear: + return VARF_ClearScope; + default: + return 0; + } +} + +EScopeFlags FScopeBarrier::ObjectFlagsFromSide(int side) +{ + switch (side) + { + case Side_Play: + return Scope_Play; + case Side_UI: + return Scope_UI; + default: + return Scope_All; + } +} + +// used for errors +const char* FScopeBarrier::StringFromSide(int side) +{ + switch (side) + { + case Side_PlainData: + return "data"; + case Side_UI: + return "ui"; + case Side_Play: + return "play"; + case Side_Virtual: + return "virtualscope"; // should not happen! + case Side_Clear: + return "clearscope"; // should not happen! + default: + return "unknown"; + } +} + +// this modifies VARF_ flags and sets the side properly. +int FScopeBarrier::ChangeSideInFlags(int flags, int side) +{ + flags &= ~(VARF_UI | VARF_Play | VARF_VirtualScope | VARF_ClearScope); + flags |= FlagsFromSide(side); + return flags; +} + +// this modifies OF_ flags and sets the side properly. +EScopeFlags FScopeBarrier::ChangeSideInObjectFlags(EScopeFlags flags, int side) +{ + int f = int(flags); + f &= ~(Scope_UI | Scope_Play); + f |= ObjectFlagsFromSide(side); + return (EScopeFlags)f; +} + +FScopeBarrier::FScopeBarrier() +{ + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; +} + +FScopeBarrier::FScopeBarrier(int flags1, int flags2, const char* name) +{ + sidefrom = -1; + sidelast = -1; + callable = true; + readable = true; + writable = true; + + AddFlags(flags1, flags2, name); +} + +// AddFlags modifies ALLOWED actions by flags1->flags2. +// This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed. +// This struct is used so that the logic is in a single place. +void FScopeBarrier::AddFlags(int flags1, int flags2, const char* name) +{ + // note: if it's already non-readable, don't even try advancing + if (!readable) + return; + + // we aren't interested in any other flags + // - update: including VARF_VirtualScope. inside the function itself, we treat it as if it's PlainData. + flags1 &= VARF_UI | VARF_Play; + flags2 &= VARF_UI | VARF_Play | VARF_ReadOnly; + + if (sidefrom < 0) sidefrom = SideFromFlags(flags1); + if (sidelast < 0) sidelast = sidefrom; + + // flags1 = what's trying to access + // flags2 = what's being accessed + + int sideto = SideFromFlags(flags2); + + // plain data inherits whatever scope modifiers that context or field container has. + // i.e. play String bla; is play, and all non-specified methods/fields inside it are play as well. + if (sideto != Side_PlainData) + sidelast = sideto; + else sideto = sidelast; + + if ((sideto == Side_UI) && (sidefrom != Side_UI)) // only ui -> ui is readable + { + readable = false; + if (name) readerror.Format("Can't read %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } + + if (!readable) + { + writable = false; + callable = false; + if (name) + { + writeerror.Format("Can't write %s field %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom)); + callerror.Format("Can't call %s function %s from %s context (not readable)", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } + return; + } + + if (writable && (sidefrom != sideto)) // only matching types are writable (plain data implicitly takes context type by default, unless overridden) + { + writable = false; + if (name) writeerror.Format("Can't write %s field %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } + + if (callable && (sidefrom != sideto) && !(flags2 & VARF_ReadOnly)) // readonly on methods is used for plain data stuff that can be called from ui/play context. + { + callable = false; + if (name) callerror.Format("Can't call %s function %s from %s context", StringFromSide(sideto), name, StringFromSide(sidefrom)); + } +} + +// these are for vmexec.h +void FScopeBarrier::ValidateNew(PClass* cls, int outerside) +{ + int innerside = FScopeBarrier::SideFromObjectFlags(cls->VMType->ScopeFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) // "cannot construct ui class ... from data context" + ThrowAbortException(X_OTHER, "Cannot construct %s class %s from %s context", FScopeBarrier::StringFromSide(innerside), cls->TypeName.GetChars(), FScopeBarrier::StringFromSide(outerside)); +} + +void FScopeBarrier::ValidateCall(PClass* selftype, VMFunction *calledfunc, int outerside) +{ + int innerside = FScopeBarrier::SideFromObjectFlags(selftype->VMType->ScopeFlags); + if ((outerside != innerside) && (innerside != FScopeBarrier::Side_PlainData)) + ThrowAbortException(X_OTHER, "Cannot call %s function %s from %s context", FScopeBarrier::StringFromSide(innerside), calledfunc->PrintableName.GetChars(), FScopeBarrier::StringFromSide(outerside)); +} \ No newline at end of file diff --git a/source/common/scripting/core/scopebarrier.h b/source/common/scripting/core/scopebarrier.h new file mode 100644 index 000000000..fb931d223 --- /dev/null +++ b/source/common/scripting/core/scopebarrier.h @@ -0,0 +1,70 @@ +#pragma once + +#include "zstring.h" + +enum EScopeFlags +{ + Scope_All = 0, + Scope_UI = 1, // Marks a class that defaults to VARF_UI for its fields/methods + Scope_Play = 2, // Marks a class that defaults to VARF_Play for its fields/methods +}; + +class PClass; +class VMFunction; + +// +// [ZZ] this really should be in codegen.h, but vmexec needs to access it +struct FScopeBarrier +{ + bool callable; + bool readable; + bool writable; + + // this is the error message + FString callerror; + FString readerror; + FString writeerror; + + // this is used to make the error message. + enum Side + { + Side_PlainData = 0, + Side_UI = 1, + Side_Play = 2, + Side_Virtual = 3, // do NOT change the value + Side_Clear = 4 + }; + int sidefrom; + int sidelast; + + // Note: the same object can't be both UI and Play. This is checked explicitly in the field construction and will cause esoteric errors here if found. + static int SideFromFlags(int flags); + + // same as above, but from object flags + static int SideFromObjectFlags(EScopeFlags flags); + + // + static int FlagsFromSide(int side); + static EScopeFlags ObjectFlagsFromSide(int side); + + // used for errors + static const char* StringFromSide(int side); + + // this modifies VARF_ flags and sets the side properly. + static int ChangeSideInFlags(int flags, int side); + // this modifies OF_ flags and sets the side properly. + static EScopeFlags ChangeSideInObjectFlags(EScopeFlags flags, int side); + FScopeBarrier(); + FScopeBarrier(int flags1, int flags2, const char* name); + + // AddFlags modifies ALLOWED actions by flags1->flags2. + // This is used for comparing a.b.c.d access - if non-allowed field is seen anywhere in the chain, anything after it is non-allowed. + // This struct is used so that the logic is in a single place. + void AddFlags(int flags1, int flags2, const char* name); + + // this is called from vmexec.h + static void ValidateNew(PClass* cls, int scope); + static void ValidateCall(PClass* selftype, VMFunction *calledfunc, int outerside); + +}; + diff --git a/source/common/scripting/core/symbols.cpp b/source/common/scripting/core/symbols.cpp new file mode 100644 index 000000000..c6e38551f --- /dev/null +++ b/source/common/scripting/core/symbols.cpp @@ -0,0 +1,605 @@ +/* +** symbols.cpp +** Implements the symbol types and symbol table +** +**--------------------------------------------------------------------------- +** Copyright 1998-2016 Randy Heit +** Copyright 2006-2017 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#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 &argflags, TArray &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 &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(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(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(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(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(sym)->Type->ReadValue(ar, nullptr, + (uint8_t *)addr + static_cast(sym)->Offset); + } + } + return readsomething || !foundsomething; +} + +//========================================================================== +// +// +// +//========================================================================== + +void PSymbolTable::RemoveSymbol(PSymbol *sym) +{ + auto mysym = Symbols.CheckKey(sym->SymbolName); + if (mysym == nullptr || *mysym != sym) return; + Symbols.Remove(sym->SymbolName); + delete sym; +} + +//========================================================================== +// +// +// +//========================================================================== + +void PSymbolTable::ReplaceSymbol(PSymbol *newsym) +{ + // If a symbol with a matching name exists, take its place and return it. + PSymbol **symslot = Symbols.CheckKey(newsym->SymbolName); + if (symslot != nullptr) + { + PSymbol *oldsym = *symslot; + delete oldsym; + *symslot = newsym; + } + // Else, just insert normally and return nullptr since there was no + // symbol to replace. + newsym->Release(); // no more GC, please! + Symbols.Insert(newsym->SymbolName, newsym); +} + +//========================================================================== +// +// +// +//========================================================================== + +//========================================================================== +// +// +// +//========================================================================== + +PNamespace::PNamespace(int filenum, PNamespace *parent) +{ + Parent = parent; + if (parent) Symbols.SetParentTable(&parent->Symbols); + FileNum = filenum; +} + +//========================================================================== +// +// +// +//========================================================================== + +FNamespaceManager::FNamespaceManager() +{ + GlobalNamespace = nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +PNamespace *FNamespaceManager::NewNamespace(int filenum) +{ + PNamespace *parent = nullptr; + // The parent will be the last namespace with this or a lower filenum. + // This ensures that DECORATE won't see the symbols of later files. + for (int i = AllNamespaces.Size() - 1; i >= 0; i--) + { + if (AllNamespaces[i]->FileNum <= filenum) + { + parent = AllNamespaces[i]; + break; + } + } + auto newns = new PNamespace(filenum, parent); + AllNamespaces.Push(newns); + return newns; +} + +//========================================================================== +// +// Deallocate the entire namespace manager. +// +//========================================================================== + +void FNamespaceManager::ReleaseSymbols() +{ + for (auto ns : AllNamespaces) + { + delete ns; + } + GlobalNamespace = nullptr; + AllNamespaces.Clear(); +} + +//========================================================================== +// +// removes all symbols from the symbol tables. +// After running the compiler these are not needed anymore. +// Only the namespaces themselves are kept because the type table references them. +// +//========================================================================== + +int FNamespaceManager::RemoveSymbols() +{ + int count = 0; + for (auto ns : AllNamespaces) + { + count += ns->Symbols.Symbols.CountUsed(); + ns->Symbols.ReleaseSymbols(); + } + return count; +} + +//========================================================================== +// +// Clean out all compiler-only data from the symbol tables +// +//========================================================================== + +void RemoveUnusedSymbols() +{ + int count = Namespaces.RemoveSymbols(); + + // We do not need any non-field and non-function symbols in structs and classes anymore. + // struct/class fields and functions are still needed so that the game can access the script data, + // but all the rest serves no purpose anymore and can be entirely removed. + for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i) + { + for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr; ty = ty->HashNext) + { + if (ty->isContainer()) + { + auto it = ty->Symbols.GetIterator(); + PSymbolTable::MapType::Pair *pair; + while (it.NextPair(pair)) + { + if ( !pair->Value->IsKindOf(RUNTIME_CLASS(PField)) + && !pair->Value->IsKindOf(RUNTIME_CLASS(PFunction)) + && !pair->Value->IsKindOf(RUNTIME_CLASS(PPropFlag)) ) + { + ty->Symbols.RemoveSymbol(pair->Value); + count++; + } + } + } + } + } + DPrintf(DMSG_SPAMMY, "%d symbols removed after compilation\n", count); +} diff --git a/source/common/scripting/core/symbols.h b/source/common/scripting/core/symbols.h new file mode 100644 index 000000000..2ae194e89 --- /dev/null +++ b/source/common/scripting/core/symbols.h @@ -0,0 +1,269 @@ +// Note: This must not be included by anything but dobject.h! +#pragma once + +#include "sc_man.h" + +class VMFunction; +class PType; +class PPrototype; +struct ZCC_TreeNode; +class PContainerType; + +// Symbol information ------------------------------------------------------- + +class PTypeBase +{ +public: + // Allocate everything on the global memory arena because all subtypes of this + // will live until the end of the game. + void *operator new(size_t size) + { + return ClassDataAllocator.Alloc(size); + } + + void operator delete(void *) + {} +}; + +class PSymbol : public DObject +{ + DECLARE_ABSTRACT_CLASS(PSymbol, DObject); +public: + FName SymbolName; + VersionInfo mVersion = { 0,0,0 }; + +protected: + PSymbol(FName name) { SymbolName = name; } +}; + +// A VM function ------------------------------------------------------------ + +// A symbol for a type ------------------------------------------------------ + +class PSymbolType : public PSymbol +{ + DECLARE_CLASS(PSymbolType, PSymbol); +public: + PType *Type; + + PSymbolType(FName name, class PType *ty) : PSymbol(name), Type(ty) {} + PSymbolType() : PSymbol(NAME_None) {} +}; + +// A symbol for a compiler tree node ---------------------------------------- + +class PSymbolTreeNode : public PSymbol +{ + DECLARE_CLASS(PSymbolTreeNode, PSymbol); +public: + struct ZCC_TreeNode *Node; + + PSymbolTreeNode(FName name, struct ZCC_TreeNode *node) : PSymbol(name), Node(node) {} + PSymbolTreeNode() : PSymbol(NAME_None) {} +}; + +// Struct/class fields ------------------------------------------------------ + +// A PField describes a symbol that takes up physical space in the struct. +class PField : public PSymbol +{ + DECLARE_CLASS(PField, PSymbol); + HAS_OBJECT_POINTERS +public: + PField(FName name, PType *type, uint32_t flags = 0, size_t offset = 0, int bitvalue = 0); + VersionInfo GetVersion(); + + size_t Offset; + PType *Type; + uint32_t Flags; + int BitValue; + FString DeprecationMessage; +protected: + PField(); +}; + +// Properties ------------------------------------------------------ + +// For setting properties in class defaults. +class PProperty : public PSymbol +{ + DECLARE_CLASS(PProperty, PSymbol); +public: + PProperty(FName name, TArray &variables); + + TArray 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 ArgFlags; // Should be the same length as Proto->ArgumentTypes + TArray ArgNames; // we need the names to access them later when the function gets compiled. + uint32_t Flags; + int UseFlags; + PContainerType *SelfClass; + FString DeprecationMessage; + }; + TArray Variants; + PContainerType *OwningClass = nullptr; + + unsigned AddVariant(PPrototype *proto, TArray &argflags, TArray &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 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 AllNamespaces; + + FNamespaceManager(); + PNamespace *NewNamespace(int filenum); + void ReleaseSymbols(); + int RemoveSymbols(); +}; + +extern FNamespaceManager Namespaces; +void RemoveUnusedSymbols(); diff --git a/source/common/scripting/core/types.cpp b/source/common/scripting/core/types.cpp new file mode 100644 index 000000000..302127989 --- /dev/null +++ b/source/common/scripting/core/types.cpp @@ -0,0 +1,2480 @@ +/* +** types.cpp +** Implements the VM type hierarchy +** +**--------------------------------------------------------------------------- +** Copyright 2008-2016 Randy Heit +** Copyright 2016-2017 Cheistoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "vmintern.h" +#include "s_soundinternal.h" +//#include "dthinker.h" +#include "types.h" +#include "printf.h" +#include "textureid.h" + + +FTypeTable TypeTable; + +PErrorType *TypeError; +PErrorType *TypeAuto; +PVoidType *TypeVoid; +PInt *TypeSInt8, *TypeUInt8; +PInt *TypeSInt16, *TypeUInt16; +PInt *TypeSInt32, *TypeUInt32; +PBool *TypeBool; +PFloat *TypeFloat32, *TypeFloat64; +PString *TypeString; +PName *TypeName; +PSound *TypeSound; +PColor *TypeColor; +PTextureID *TypeTextureID; +PPointer *TypeFont; +PStateLabel *TypeStateLabel; +PStruct *TypeVector2; +PStruct *TypeVector3; +PStruct *TypeColorStruct; +PStruct *TypeStringStruct; +PPointer *TypeNullPtr; +PPointer *TypeVoidPtr; + + +// CODE -------------------------------------------------------------------- + +void DumpTypeTable() +{ + int used = 0; + int min = INT_MAX; + int max = 0; + int all = 0; + int lens[10] = {0}; + for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i) + { + int len = 0; + Printf("%4zu:", i); + for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr; ty = ty->HashNext) + { + Printf(" -> %s", ty->DescriptiveName()); + len++; + all++; + } + if (len != 0) + { + used++; + if (len < min) + min = len; + if (len > max) + max = len; + } + if (len < (int)countof(lens)) + { + lens[len]++; + } + Printf("\n"); + } + Printf("Used buckets: %d/%lu (%.2f%%) for %d entries\n", used, countof(TypeTable.TypeHash), double(used)/countof(TypeTable.TypeHash)*100, all); + Printf("Min bucket size: %d\n", min); + Printf("Max bucket size: %d\n", max); + Printf("Avg bucket size: %.2f\n", double(all) / used); + int j,k; + for (k = countof(lens)-1; k > 0; --k) + if (lens[k]) + break; + for (j = 0; j <= k; ++j) + Printf("Buckets of len %d: %d (%.2f%%)\n", j, lens[j], j!=0?double(lens[j])/used*100:-1.0); +} + +/* PType ******************************************************************/ + +//========================================================================== +// +// PType Parameterized Constructor +// +//========================================================================== + +PType::PType(unsigned int size, unsigned int align) +: Size(size), Align(align), HashNext(nullptr) +{ + mDescriptiveName = "Type"; + loadOp = OP_NOP; + storeOp = OP_NOP; + moveOp = OP_NOP; + RegType = REGT_NIL; + RegCount = 1; +} + +//========================================================================== +// +// PType Destructor +// +//========================================================================== + +PType::~PType() +{ +} + +//========================================================================== +// +// PType :: WriteValue +// +//========================================================================== + +void PType::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + assert(0 && "Cannot write value for this type"); +} + +//========================================================================== +// +// PType :: ReadValue +// +//========================================================================== + +bool PType::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + assert(0 && "Cannot read value for this type"); + return false; +} + +//========================================================================== +// +// PType :: SetDefaultValue +// +//========================================================================== + +void PType::SetDefaultValue(void *base, unsigned offset, TArray *stroffs) +{ +} + +//========================================================================== +// +// PType :: SetDefaultValue +// +//========================================================================== + +void PType::SetPointer(void *base, unsigned offset, TArray *stroffs) +{ +} + +void PType::SetPointerArray(void *base, unsigned offset, TArray *stroffs) +{ +} + +//========================================================================== +// +// PType :: InitializeValue +// +//========================================================================== + +void PType::InitializeValue(void *addr, const void *def) const +{ +} + +//========================================================================== +// +// PType :: DestroyValue +// +//========================================================================== + +void PType::DestroyValue(void *addr) const +{ +} + +//========================================================================== +// +// PType :: SetValue +// +//========================================================================== + +void PType::SetValue(void *addr, int val) +{ + assert(0 && "Cannot set int value for this type"); +} + +void PType::SetValue(void *addr, double val) +{ + assert(0 && "Cannot set float value for this type"); +} + +//========================================================================== +// +// PType :: GetValue +// +//========================================================================== + +int PType::GetValueInt(void *addr) const +{ + assert(0 && "Cannot get value for this type"); + return 0; +} + +double PType::GetValueFloat(void *addr) const +{ + assert(0 && "Cannot get value for this type"); + return 0; +} + +//========================================================================== +// +// PType :: IsMatch +// +//========================================================================== + +bool PType::IsMatch(intptr_t id1, intptr_t id2) const +{ + return false; +} + +//========================================================================== +// +// PType :: GetTypeIDs +// +//========================================================================== + +void PType::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = 0; + id2 = 0; +} + +//========================================================================== +// +// PType :: GetTypeIDs +// +//========================================================================== + +const char *PType::DescriptiveName() const +{ + return mDescriptiveName.GetChars(); +} + +//========================================================================== +// +// PType :: StaticInit STATIC +// +//========================================================================== + +void PType::StaticInit() +{ + // Create types and add them type the type table. + TypeTable.AddType(TypeError = new PErrorType, NAME_None); + TypeTable.AddType(TypeAuto = new PErrorType(2), NAME_None); + TypeTable.AddType(TypeVoid = new PVoidType, NAME_Void); + TypeTable.AddType(TypeSInt8 = new PInt(1, false), NAME_Int); + TypeTable.AddType(TypeUInt8 = new PInt(1, true), NAME_Int); + TypeTable.AddType(TypeSInt16 = new PInt(2, false), NAME_Int); + TypeTable.AddType(TypeUInt16 = new PInt(2, true), NAME_Int); + TypeTable.AddType(TypeSInt32 = new PInt(4, false), NAME_Int); + TypeTable.AddType(TypeUInt32 = new PInt(4, true), NAME_Int); + TypeTable.AddType(TypeBool = new PBool, NAME_Bool); + TypeTable.AddType(TypeFloat32 = new PFloat(4), NAME_Float); + TypeTable.AddType(TypeFloat64 = new PFloat(8), NAME_Float); + TypeTable.AddType(TypeString = new PString, NAME_String); + TypeTable.AddType(TypeName = new PName, NAME_Name); + TypeTable.AddType(TypeSound = new PSound, NAME_Sound); + TypeTable.AddType(TypeColor = new PColor, NAME_Color); + TypeTable.AddType(TypeStateLabel = new PStateLabel, NAME_Label); + TypeTable.AddType(TypeNullPtr = new PPointer, NAME_Pointer); + TypeTable.AddType(TypeTextureID = new PTextureID, NAME_TextureID); + + TypeVoidPtr = NewPointer(TypeVoid, false); + TypeColorStruct = NewStruct("@ColorStruct", nullptr); //This name is intentionally obfuscated so that it cannot be used explicitly. The point of this type is to gain access to the single channels of a color value. + TypeStringStruct = NewStruct("Stringstruct", nullptr, true); + TypeFont = NewPointer(NewStruct("Font", nullptr, true)); +#ifdef __BIG_ENDIAN__ + TypeColorStruct->AddField(NAME_a, TypeUInt8); + TypeColorStruct->AddField(NAME_r, TypeUInt8); + TypeColorStruct->AddField(NAME_g, TypeUInt8); + TypeColorStruct->AddField(NAME_b, TypeUInt8); +#else + TypeColorStruct->AddField(NAME_b, TypeUInt8); + TypeColorStruct->AddField(NAME_g, TypeUInt8); + TypeColorStruct->AddField(NAME_r, TypeUInt8); + TypeColorStruct->AddField(NAME_a, TypeUInt8); +#endif + + TypeVector2 = new PStruct(NAME_Vector2, nullptr); + TypeVector2->AddField(NAME_X, TypeFloat64); + TypeVector2->AddField(NAME_Y, TypeFloat64); + TypeTable.AddType(TypeVector2, NAME_Struct); + TypeVector2->loadOp = OP_LV2; + TypeVector2->storeOp = OP_SV2; + TypeVector2->moveOp = OP_MOVEV2; + TypeVector2->RegType = REGT_FLOAT; + TypeVector2->RegCount = 2; + + TypeVector3 = new PStruct(NAME_Vector3, nullptr); + TypeVector3->AddField(NAME_X, TypeFloat64); + TypeVector3->AddField(NAME_Y, TypeFloat64); + TypeVector3->AddField(NAME_Z, TypeFloat64); + // allow accessing xy as a vector2. This is not supposed to be serialized so it's marked transient + TypeVector3->Symbols.AddSymbol(Create(NAME_XY, TypeVector2, VARF_Transient, 0)); + TypeTable.AddType(TypeVector3, NAME_Struct); + TypeVector3->loadOp = OP_LV3; + TypeVector3->storeOp = OP_SV3; + TypeVector3->moveOp = OP_MOVEV3; + TypeVector3->RegType = REGT_FLOAT; + TypeVector3->RegCount = 3; + + + + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_sByte, TypeSInt8)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Byte, TypeUInt8)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Short, TypeSInt16)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_uShort, TypeUInt16)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Int, TypeSInt32)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_uInt, TypeUInt32)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Bool, TypeBool)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Float, TypeFloat64)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Double, TypeFloat64)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Float32, TypeFloat32)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Float64, TypeFloat64)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_String, TypeString)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Name, TypeName)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Sound, TypeSound)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Color, TypeColor)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Vector2, TypeVector2)); + Namespaces.GlobalNamespace->Symbols.AddSymbol(Create(NAME_Vector3, TypeVector3)); +} + + +/* PBasicType *************************************************************/ + +//========================================================================== +// +// PBasicType Parameterized Constructor +// +//========================================================================== + +PBasicType::PBasicType(unsigned int size, unsigned int align) +: PType(size, align) +{ + mDescriptiveName = "BasicType"; + Flags |= TYPE_Scalar; +} + +/* PCompoundType **********************************************************/ + +//========================================================================== +// +// PBasicType Parameterized Constructor +// +//========================================================================== + +PCompoundType::PCompoundType(unsigned int size, unsigned int align) + : PType(size, align) +{ + mDescriptiveName = "CompoundType"; +} + +/* PContainerType *************************************************************/ + +//========================================================================== +// +// PContainerType :: IsMatch +// +//========================================================================== + +bool PContainerType::IsMatch(intptr_t id1, intptr_t id2) const +{ + const PTypeBase *outer = (const PTypeBase *)id1; + FName name = (ENamedName)(intptr_t)id2; + + return Outer == outer && TypeName == name; +} + +//========================================================================== +// +// PContainerType :: GetTypeIDs +// +//========================================================================== + +void PContainerType::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)Outer; + id2 = TypeName.GetIndex(); +} + +/* PInt *******************************************************************/ + +//========================================================================== +// +// PInt Parameterized Constructor +// +//========================================================================== + +PInt::PInt(unsigned int size, bool unsign, bool compatible) +: PBasicType(size, size), Unsigned(unsign), IntCompatible(compatible) +{ + mDescriptiveName.Format("%cInt%d", unsign? 'U':'S', size); + Flags |= TYPE_Int; + + MemberOnly = (size < 4); + if (!unsign) + { + int maxval = (1u << ((8 * size) - 1)) - 1; // compute as unsigned to prevent overflow before -1 + int minval = -maxval - 1; + Symbols.AddSymbol(Create(NAME_Min, this, minval)); + Symbols.AddSymbol(Create(NAME_Max, this, maxval)); + } + else + { + Symbols.AddSymbol(Create(NAME_Min, this, 0u)); + Symbols.AddSymbol(Create(NAME_Max, this, (1u << ((8 * size) - 1)))); + } + SetOps(); +} + +void PInt::SetOps() +{ + moveOp = OP_MOVE; + RegType = REGT_INT; + if (Size == 4) + { + storeOp = OP_SW; + loadOp = OP_LW; + } + else if (Size == 1) + { + storeOp = OP_SB; + loadOp = Unsigned ? OP_LBU : OP_LB; + } + else if (Size == 2) + { + storeOp = OP_SH; + loadOp = Unsigned ? OP_LHU : OP_LH; + } + else + { + assert(0 && "Unhandled integer size"); + storeOp = OP_NOP; + } +} + +//========================================================================== +// +// PInt :: WriteValue +// +//========================================================================== + +void PInt::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + if (Size == 8 && Unsigned) + { + // this is a special case that cannot be represented by an int64_t. + uint64_t val = *(uint64_t*)addr; + ar(key, val); + } + else + { + int64_t val; + switch (Size) + { + case 1: + val = Unsigned ? *(uint8_t*)addr : *(int8_t*)addr; + break; + + case 2: + val = Unsigned ? *(uint16_t*)addr : *(int16_t*)addr; + break; + + case 4: + val = Unsigned ? *(uint32_t*)addr : *(int32_t*)addr; + break; + + case 8: + val = *(int64_t*)addr; + break; + + default: + return; // something invalid + } + ar(key, val); + } +} + +//========================================================================== +// +// PInt :: ReadValue +// +//========================================================================== + +bool PInt::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + NumericValue val; + + ar(key, val); + if (val.type == NumericValue::NM_invalid) return false; // not found or usable + if (val.type == NumericValue::NM_float) val.signedval = (int64_t)val.floatval; + + // No need to check the unsigned state here. Downcasting to smaller types will yield the same result for both. + switch (Size) + { + case 1: + *(uint8_t*)addr = (uint8_t)val.signedval; + break; + + case 2: + *(uint16_t*)addr = (uint16_t)val.signedval; + break; + + case 4: + *(uint32_t*)addr = (uint32_t)val.signedval; + break; + + case 8: + *(uint64_t*)addr = (uint64_t)val.signedval; + break; + + default: + return false; // something invalid + } + + return true; +} + +//========================================================================== +// +// PInt :: SetValue +// +//========================================================================== + +void PInt::SetValue(void *addr, int val) +{ + assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address"); + if (Size == 4) + { + *(int *)addr = val; + } + else if (Size == 1) + { + *(uint8_t *)addr = val; + } + else if (Size == 2) + { + *(uint16_t *)addr = val; + } + else if (Size == 8) + { + *(uint64_t *)addr = val; + } + else + { + assert(0 && "Unhandled integer size"); + } +} + +void PInt::SetValue(void *addr, double val) +{ + SetValue(addr, (int)val); +} + +//========================================================================== +// +// PInt :: GetValueInt +// +//========================================================================== + +int PInt::GetValueInt(void *addr) const +{ + assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address"); + if (Size == 4) + { + return *(int *)addr; + } + else if (Size == 1) + { + return Unsigned ? *(uint8_t *)addr : *(int8_t *)addr; + } + else if (Size == 2) + { + return Unsigned ? *(uint16_t *)addr : *(int16_t *)addr; + } + else if (Size == 8) + { // truncated output + return (int)*(uint64_t *)addr; + } + else + { + assert(0 && "Unhandled integer size"); + return 0; + } +} + +//========================================================================== +// +// PInt :: GetValueFloat +// +//========================================================================== + +double PInt::GetValueFloat(void *addr) const +{ + return GetValueInt(addr); +} + +//========================================================================== +// +// PInt :: GetStoreOp +// +//========================================================================== + +/* PBool ******************************************************************/ + +//========================================================================== +// +// PInt :: SetValue +// +//========================================================================== + +void PBool::SetValue(void *addr, int val) +{ + *(bool*)addr = !!val; +} + +void PBool::SetValue(void *addr, double val) +{ + *(bool*)addr = val != 0.; +} + +int PBool::GetValueInt(void *addr) const +{ + return *(bool *)addr; +} + +double PBool::GetValueFloat(void *addr) const +{ + return *(bool *)addr; +} + +//========================================================================== +// +// PBool Default Constructor +// +//========================================================================== + +PBool::PBool() +: PInt(sizeof(bool), true) +{ + mDescriptiveName = "Bool"; + MemberOnly = false; + Flags |= TYPE_IntNotInt; +} + +/* PFloat *****************************************************************/ + +//========================================================================== +// +// PFloat Parameterized Constructor +// +//========================================================================== + +PFloat::PFloat(unsigned int size) +: PBasicType(size, size) +{ + mDescriptiveName.Format("Float%d", size); + Flags |= TYPE_Float; + if (size == 8) + { + if (sizeof(void*) == 4) + { + // Some ABIs for 32-bit platforms define alignment of double type as 4 bytes + // Intel POSIX (System V ABI) and PowerPC Macs are examples of those + struct AlignmentCheck { uint8_t i; double d; }; + Align = static_cast(offsetof(AlignmentCheck, d)); + } + + SetDoubleSymbols(); + } + else + { + assert(size == 4); + MemberOnly = true; + SetSingleSymbols(); + } + SetOps(); +} + +//========================================================================== +// +// PFloat :: SetDoubleSymbols +// +// Setup constant values for 64-bit floats. +// +//========================================================================== + +void PFloat::SetDoubleSymbols() +{ + static const SymbolInitF symf[] = + { + { NAME_Min_Normal, DBL_MIN }, + { NAME_Max, DBL_MAX }, + { NAME_Epsilon, DBL_EPSILON }, + { NAME_NaN, std::numeric_limits::quiet_NaN() }, + { NAME_Infinity, std::numeric_limits::infinity() }, + { NAME_Min_Denormal, std::numeric_limits::denorm_min() } + }; + static const SymbolInitI symi[] = + { + { NAME_Dig, DBL_DIG }, + { NAME_Min_Exp, DBL_MIN_EXP }, + { NAME_Max_Exp, DBL_MAX_EXP }, + { NAME_Mant_Dig, DBL_MANT_DIG }, + { NAME_Min_10_Exp, DBL_MIN_10_EXP }, + { NAME_Max_10_Exp, DBL_MAX_10_EXP } + }; + SetSymbols(symf, countof(symf)); + SetSymbols(symi, countof(symi)); +} + +//========================================================================== +// +// PFloat :: SetSingleSymbols +// +// Setup constant values for 32-bit floats. +// +//========================================================================== + +void PFloat::SetSingleSymbols() +{ + static const SymbolInitF symf[] = + { + { NAME_Min_Normal, FLT_MIN }, + { NAME_Max, FLT_MAX }, + { NAME_Epsilon, FLT_EPSILON }, + { NAME_NaN, std::numeric_limits::quiet_NaN() }, + { NAME_Infinity, std::numeric_limits::infinity() }, + { NAME_Min_Denormal, std::numeric_limits::denorm_min() } + }; + static const SymbolInitI symi[] = + { + { NAME_Dig, FLT_DIG }, + { NAME_Min_Exp, FLT_MIN_EXP }, + { NAME_Max_Exp, FLT_MAX_EXP }, + { NAME_Mant_Dig, FLT_MANT_DIG }, + { NAME_Min_10_Exp, FLT_MIN_10_EXP }, + { NAME_Max_10_Exp, FLT_MAX_10_EXP } + }; + SetSymbols(symf, countof(symf)); + SetSymbols(symi, countof(symi)); +} + +//========================================================================== +// +// PFloat :: SetSymbols +// +//========================================================================== + +void PFloat::SetSymbols(const PFloat::SymbolInitF *sym, size_t count) +{ + for (size_t i = 0; i < count; ++i) + { + Symbols.AddSymbol(Create(sym[i].Name, this, sym[i].Value)); + } +} + +void PFloat::SetSymbols(const PFloat::SymbolInitI *sym, size_t count) +{ + for (size_t i = 0; i < count; ++i) + { + Symbols.AddSymbol(Create(sym[i].Name, this, sym[i].Value)); + } +} + +//========================================================================== +// +// PFloat :: WriteValue +// +//========================================================================== + +void PFloat::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + if (Size == 8) + { + ar(key, *(double*)addr); + } + else + { + ar(key, *(float*)addr); + } +} + +//========================================================================== +// +// PFloat :: ReadValue +// +//========================================================================== + +bool PFloat::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + NumericValue val; + + ar(key, val); + if (val.type == NumericValue::NM_invalid) return false; // not found or usable + else if (val.type == NumericValue::NM_signed) val.floatval = (double)val.signedval; + else if (val.type == NumericValue::NM_unsigned) val.floatval = (double)val.unsignedval; + + if (Size == 8) + { + *(double*)addr = val.floatval; + } + else + { + *(float*)addr = (float)val.floatval; + } + return true; +} + +//========================================================================== +// +// PFloat :: SetValue +// +//========================================================================== + +void PFloat::SetValue(void *addr, int val) +{ + return SetValue(addr, (double)val); +} + +void PFloat::SetValue(void *addr, double val) +{ + assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address"); + if (Size == 4) + { + *(float *)addr = (float)val; + } + else + { + assert(Size == 8); + *(double *)addr = val; + } +} + +//========================================================================== +// +// PFloat :: GetValueInt +// +//========================================================================== + +int PFloat::GetValueInt(void *addr) const +{ + return xs_ToInt(GetValueFloat(addr)); +} + +//========================================================================== +// +// PFloat :: GetValueFloat +// +//========================================================================== + +double PFloat::GetValueFloat(void *addr) const +{ + assert(((intptr_t)addr & (Align - 1)) == 0 && "unaligned address"); + if (Size == 4) + { + return *(float *)addr; + } + else + { + assert(Size == 8); + return *(double *)addr; + } +} + +//========================================================================== +// +// PFloat :: GetStoreOp +// +//========================================================================== + +void PFloat::SetOps() +{ + if (Size == 4) + { + storeOp = OP_SSP; + loadOp = OP_LSP; + } + else + { + assert(Size == 8); + storeOp = OP_SDP; + loadOp = OP_LDP; + } + moveOp = OP_MOVEF; + RegType = REGT_FLOAT; +} + +/* PString ****************************************************************/ + +//========================================================================== +// +// PString Default Constructor +// +//========================================================================== + +PString::PString() +: PBasicType(sizeof(FString), alignof(FString)) +{ + mDescriptiveName = "String"; + storeOp = OP_SS; + loadOp = OP_LS; + moveOp = OP_MOVES; + RegType = REGT_STRING; + +} + +//========================================================================== +// +// PString :: WriteValue +// +//========================================================================== + +void PString::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + ar(key, *(FString*)addr); +} + +//========================================================================== +// +// PString :: ReadValue +// +//========================================================================== + +bool PString::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + const char *cptr; + ar.StringPtr(key, cptr); + if (cptr == nullptr) + { + return false; + } + else + { + *(FString*)addr = cptr; + return true; + } +} + +//========================================================================== +// +// PString :: SetDefaultValue +// +//========================================================================== + +void PString::SetDefaultValue(void *base, unsigned offset, TArray *special) +{ + if (base != nullptr) new((uint8_t *)base + offset) FString; + if (special != nullptr) + { + special->Push(std::make_pair(this, offset)); + } +} + +//========================================================================== +// +// PString :: InitializeValue +// +//========================================================================== + +void PString::InitializeValue(void *addr, const void *def) const +{ + if (def != nullptr) + { + new(addr) FString(*(FString *)def); + } + else + { + new(addr) FString; + } +} + +//========================================================================== +// +// PString :: DestroyValue +// +//========================================================================== + +void PString::DestroyValue(void *addr) const +{ + ((FString *)addr)->~FString(); +} + +/* PName ******************************************************************/ + +//========================================================================== +// +// PName Default Constructor +// +//========================================================================== + +PName::PName() +: PInt(sizeof(FName), true, false) +{ + mDescriptiveName = "Name"; + Flags |= TYPE_IntNotInt; + assert(sizeof(FName) == alignof(FName)); +} + +//========================================================================== +// +// PName :: WriteValue +// +//========================================================================== + +void PName::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + const char *cptr = ((const FName*)addr)->GetChars(); + ar.StringPtr(key, cptr); +} + +//========================================================================== +// +// PName :: ReadValue +// +//========================================================================== + +bool PName::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + const char *cptr; + ar.StringPtr(key, cptr); + if (cptr == nullptr) + { + return false; + } + else + { + *(FName*)addr = FName(cptr); + return true; + } +} + +/* PTextureID ******************************************************************/ + +//========================================================================== +// +// PTextureID Default Constructor +// +//========================================================================== + +PTextureID::PTextureID() + : PInt(sizeof(FTextureID), true, false) +{ + mDescriptiveName = "TextureID"; + Flags |= TYPE_IntNotInt; + assert(sizeof(FTextureID) == alignof(FTextureID)); +} + +//========================================================================== +// +// PTextureID :: WriteValue +// +//========================================================================== + +void PTextureID::WriteValue(FSerializer &ar, const char *key, const void *addr) const +{ + FTextureID val = *(FTextureID*)addr; + ar(key, val); +} + +//========================================================================== +// +// PTextureID :: ReadValue +// +//========================================================================== + +bool PTextureID::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + FTextureID val; + ar(key, val); + *(FTextureID*)addr = val; + return true; +} + +/* PSound *****************************************************************/ + +//========================================================================== +// +// PSound Default Constructor +// +//========================================================================== + +PSound::PSound() +: PInt(sizeof(FSoundID), true) +{ + mDescriptiveName = "Sound"; + Flags |= TYPE_IntNotInt; + assert(sizeof(FSoundID) == alignof(FSoundID)); +} + +//========================================================================== +// +// PSound :: WriteValue +// +//========================================================================== + +void PSound::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + const char *cptr = soundEngine->GetSoundName(*(const FSoundID *)addr); + ar.StringPtr(key, cptr); +} + +//========================================================================== +// +// PSound :: ReadValue +// +//========================================================================== + +bool PSound::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + const char *cptr; + ar.StringPtr(key, cptr); + if (cptr == nullptr) + { + return false; + } + else + { + *(FSoundID *)addr = FSoundID(cptr); + return true; + } +} + +/* PColor *****************************************************************/ + +//========================================================================== +// +// PColor Default Constructor +// +//========================================================================== + +PColor::PColor() +: PInt(sizeof(PalEntry), true) +{ + mDescriptiveName = "Color"; + Flags |= TYPE_IntNotInt; + assert(sizeof(PalEntry) == alignof(PalEntry)); +} + +/* PStateLabel *****************************************************************/ + +//========================================================================== +// +// PStateLabel Default Constructor +// +//========================================================================== + +PStateLabel::PStateLabel() + : PInt(sizeof(int), false, false) +{ + Flags |= TYPE_IntNotInt; + mDescriptiveName = "StateLabel"; +} + +/* PPointer ***************************************************************/ + +//========================================================================== +// +// PPointer - Default Constructor +// +//========================================================================== + +PPointer::PPointer() +: PBasicType(sizeof(void *), alignof(void *)), PointedType(nullptr), IsConst(false) +{ + mDescriptiveName = "NullPointer"; + loadOp = OP_LP; + storeOp = OP_SP; + moveOp = OP_MOVEA; + RegType = REGT_POINTER; + Flags |= TYPE_Pointer; +} + +//========================================================================== +// +// PPointer - Parameterized Constructor +// +//========================================================================== + +PPointer::PPointer(PType *pointsat, bool isconst) +: PBasicType(sizeof(void *), alignof(void *)), PointedType(pointsat), IsConst(isconst) +{ + if (pointsat != nullptr) + { + mDescriptiveName.Format("Pointer<%s%s>", pointsat->DescriptiveName(), isconst ? "readonly " : ""); + mVersion = pointsat->mVersion; + } + else + { + mDescriptiveName = "Pointer"; + mVersion = 0; + } + loadOp = OP_LP; + storeOp = OP_SP; + moveOp = OP_MOVEA; + RegType = REGT_POINTER; + Flags |= TYPE_Pointer; +} + +//========================================================================== +// +// PPointer :: IsMatch +// +//========================================================================== + +bool PPointer::IsMatch(intptr_t id1, intptr_t id2) const +{ + assert(id2 == 0 || id2 == 1); + PType *pointat = (PType *)id1; + + return pointat == PointedType && (!!id2) == IsConst; +} + +//========================================================================== +// +// PPointer :: GetTypeIDs +// +//========================================================================== + +void PPointer::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)PointedType; + id2 = 0; +} + +//========================================================================== +// +// PPointer :: WriteValue +// +//========================================================================== + +void PPointer::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + if (writer != nullptr) + { + writer(ar, key, addr); + } + else + { + I_Error("Attempt to save pointer to unhandled type %s", PointedType->DescriptiveName()); + } +} + +//========================================================================== +// +// PPointer :: ReadValue +// +//========================================================================== + +bool PPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + if (reader != nullptr) + { + return reader(ar, key, addr); + } + return false; +} + +/* PObjectPointer **********************************************************/ + +//========================================================================== +// +// PPointer :: GetStoreOp +// +//========================================================================== + +PObjectPointer::PObjectPointer(PClass *cls, bool isconst) + : PPointer(cls->VMType, isconst) +{ + loadOp = OP_LO; + Flags |= TYPE_ObjectPointer; + // Non-destroyed thinkers are always guaranteed to be linked into the thinker chain so we don't need the write barrier for them. + //if (cls && !cls->IsDescendantOf(RUNTIME_CLASS(DThinker))) storeOp = OP_SO; +} + +//========================================================================== +// +// PPointer :: SetPointer +// +//========================================================================== + +void PObjectPointer::SetPointer(void *base, unsigned offset, TArray *special) +{ + // Add to the list of pointers for this class. + special->Push(offset); +} + +//========================================================================== +// +// PPointer :: WriteValue +// +//========================================================================== + +void PObjectPointer::WriteValue(FSerializer &ar, const char *key, const void *addr) const +{ + ar(key, *(DObject **)addr); +} + +//========================================================================== +// +// PPointer :: ReadValue +// +//========================================================================== + +bool PObjectPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + bool res; + ::Serialize(ar, key, *(DObject **)addr, nullptr, &res); + return res; +} + +//========================================================================== +// +// NewPointer +// +// Returns a PPointer to an object of the specified type +// +//========================================================================== + +PPointer *NewPointer(PType *type, bool isconst) +{ + auto cp = PType::toClass(type); + if (cp) return NewPointer(cp->Descriptor, isconst); + + size_t bucket; + PType *ptype = TypeTable.FindType(NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, &bucket); + if (ptype == nullptr) + { + ptype = new PPointer(type, isconst); + TypeTable.AddType(ptype, NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, bucket); + } + return static_cast(ptype); +} + +PPointer *NewPointer(PClass *cls, bool isconst) +{ + assert(cls->VMType != nullptr); + + auto type = cls->VMType; + size_t bucket; + PType *ptype = TypeTable.FindType(NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, &bucket); + if (ptype == nullptr) + { + ptype = new PObjectPointer(cls, isconst); + TypeTable.AddType(ptype, NAME_Pointer, (intptr_t)type, isconst ? 1 : 0, bucket); + } + return static_cast(ptype); +} + + + +/* PClassPointer **********************************************************/ + +//========================================================================== +// +// PClassPointer - Parameterized Constructor +// +//========================================================================== + +PClassPointer::PClassPointer(PClass *restrict) +: PPointer(restrict->VMType), ClassRestriction(restrict) +{ + if (restrict) mDescriptiveName.Format("ClassPointer<%s>", restrict->TypeName.GetChars()); + else mDescriptiveName = "ClassPointer"; + loadOp = OP_LP; + storeOp = OP_SP; + Flags |= TYPE_ClassPointer; + mVersion = restrict->VMType->mVersion; +} + +//========================================================================== +// +// PPointer :: WriteValue +// +//========================================================================== + +void PClassPointer::WriteValue(FSerializer &ar, const char *key, const void *addr) const +{ + ar(key, *(PClass **)addr); +} + +//========================================================================== +// +// PPointer :: ReadValue +// +//========================================================================== + +bool PClassPointer::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + ::Serialize(ar, key, *(PClass **)addr, (PClass**)nullptr); + return false; +} + +//========================================================================== +// +// PClassPointer - isCompatible +// +//========================================================================== + +bool PClassPointer::isCompatible(PType *type) +{ + auto other = PType::toClassPointer(type); + return (other != nullptr && other->ClassRestriction->IsDescendantOf(ClassRestriction)); +} + +//========================================================================== +// +// PClassPointer :: SetPointer +// +//========================================================================== + +void PClassPointer::SetPointer(void *base, unsigned offset, TArray *special) +{ +} + +//========================================================================== +// +// PClassPointer :: IsMatch +// +//========================================================================== + +bool PClassPointer::IsMatch(intptr_t id1, intptr_t id2) const +{ + const PClass *classat = (const PClass *)id2; + return classat == ClassRestriction; +} + +//========================================================================== +// +// PClassPointer :: GetTypeIDs +// +//========================================================================== + +void PClassPointer::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = 0; + id2 = (intptr_t)ClassRestriction; +} + +//========================================================================== +// +// NewClassPointer +// +// Returns a PClassPointer for the restricted type. +// +//========================================================================== + +PClassPointer *NewClassPointer(PClass *restrict) +{ + size_t bucket; + PType *ptype = TypeTable.FindType(NAME_Class, 0, (intptr_t)restrict, &bucket); + if (ptype == nullptr) + { + ptype = new PClassPointer(restrict); + TypeTable.AddType(ptype, NAME_Class, 0, (intptr_t)restrict, bucket); + } + return static_cast(ptype); +} + +/* PEnum ******************************************************************/ + +//========================================================================== +// +// PEnum - Parameterized Constructor +// +//========================================================================== + +PEnum::PEnum(FName name, PTypeBase *outer) +: PInt(4, false), Outer(outer), EnumName(name) +{ + Flags |= TYPE_IntNotInt; + mDescriptiveName.Format("Enum<%s>", name.GetChars()); +} + +//========================================================================== +// +// NewEnum +// +// Returns a PEnum for the given name and container, making sure not to +// create duplicates. +// +//========================================================================== + +PEnum *NewEnum(FName name, PTypeBase *outer) +{ + size_t bucket; + if (outer == nullptr) outer = Namespaces.GlobalNamespace; + PType *etype = TypeTable.FindType(NAME_Enum, (intptr_t)outer, name.GetIndex(), &bucket); + if (etype == nullptr) + { + etype = new PEnum(name, outer); + TypeTable.AddType(etype, NAME_Enum, (intptr_t)outer, name.GetIndex(), bucket); + } + return static_cast(etype); +} + +/* PArray *****************************************************************/ + +//========================================================================== +// +// PArray - Parameterized Constructor +// +//========================================================================== + +PArray::PArray(PType *etype, unsigned int ecount) +: ElementType(etype), ElementCount(ecount) +{ + mDescriptiveName.Format("Array<%s>[%d]", etype->DescriptiveName(), ecount); + + Align = etype->Align; + // Since we are concatenating elements together, the element size should + // also be padded to the nearest alignment. + ElementSize = (etype->Size + (etype->Align - 1)) & ~(etype->Align - 1); + Size = ElementSize * ecount; + Flags |= TYPE_Array; +} + +//========================================================================== +// +// PArray :: IsMatch +// +//========================================================================== + +bool PArray::IsMatch(intptr_t id1, intptr_t id2) const +{ + const PType *elemtype = (const PType *)id1; + unsigned int count = (unsigned int)(intptr_t)id2; + + return elemtype == ElementType && count == ElementCount; +} + +//========================================================================== +// +// PArray :: GetTypeIDs +// +//========================================================================== + +void PArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)ElementType; + id2 = ElementCount; +} + +//========================================================================== +// +// PArray :: WriteValue +// +//========================================================================== + +void PArray::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + if (ar.BeginArray(key)) + { + const uint8_t *addrb = (const uint8_t *)addr; + for (unsigned i = 0; i < ElementCount; ++i) + { + ElementType->WriteValue(ar, nullptr, addrb); + addrb += ElementSize; + } + ar.EndArray(); + } +} + +//========================================================================== +// +// PArray :: ReadValue +// +//========================================================================== + +bool PArray::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + if (ar.BeginArray(key)) + { + bool readsomething = false; + unsigned count = ar.ArraySize(); + unsigned loop = std::min(count, ElementCount); + uint8_t *addrb = (uint8_t *)addr; + for(unsigned i=0;iReadValue(ar, nullptr, addrb); + addrb += ElementSize; + } + if (loop < count) + { + DPrintf(DMSG_WARNING, "Array on disk (%u) is bigger than in memory (%u)\n", + count, ElementCount); + } + ar.EndArray(); + return readsomething; + } + return false; +} + +//========================================================================== +// +// PArray :: SetDefaultValue +// +//========================================================================== + +void PArray::SetDefaultValue(void *base, unsigned offset, TArray *special) +{ + for (unsigned i = 0; i < ElementCount; ++i) + { + ElementType->SetDefaultValue(base, offset + i*ElementSize, special); + } +} + +//========================================================================== +// +// PArray :: SetDefaultValue +// +//========================================================================== + +void PArray::SetPointer(void *base, unsigned offset, TArray *special) +{ + for (unsigned i = 0; i < ElementCount; ++i) + { + ElementType->SetPointer(base, offset + i*ElementSize, special); + } +} + +//========================================================================== +// +// PArray :: SetPointerArray +// +//========================================================================== + +void PArray::SetPointerArray(void *base, unsigned offset, TArray *special) +{ + if (ElementType->isStruct()) + { + for (unsigned int i = 0; i < ElementCount; ++i) + { + ElementType->SetPointerArray(base, offset + ElementSize * i, special); + } + } +} + +//========================================================================== +// +// NewArray +// +// Returns a PArray for the given type and size, making sure not to create +// duplicates. +// +//========================================================================== + +PArray *NewArray(PType *type, unsigned int count) +{ + size_t bucket; + PType *atype = TypeTable.FindType(NAME_Array, (intptr_t)type, count, &bucket); + if (atype == nullptr) + { + atype = new PArray(type, count); + TypeTable.AddType(atype, NAME_Array, (intptr_t)type, count, bucket); + } + return (PArray *)atype; +} + +/* PArray *****************************************************************/ + +//========================================================================== +// +// PArray - Parameterized Constructor +// +//========================================================================== + +PStaticArray::PStaticArray(PType *etype) + : PArray(etype, 0) +{ + mDescriptiveName.Format("ResizableArray<%s>", etype->DescriptiveName()); +} + +//========================================================================== +// +// PArray :: IsMatch +// +//========================================================================== + +bool PStaticArray::IsMatch(intptr_t id1, intptr_t id2) const +{ + const PType *elemtype = (const PType *)id1; + unsigned int count = (unsigned int)(intptr_t)id2; + + return elemtype == ElementType && count == 0; +} + +//========================================================================== +// +// PArray :: GetTypeIDs +// +//========================================================================== + +void PStaticArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)ElementType; + id2 = 0; +} + +//========================================================================== +// +// NewStaticArray +// +// Returns a PArray for the given type and size, making sure not to create +// duplicates. +// +//========================================================================== + +PStaticArray *NewStaticArray(PType *type) +{ + size_t bucket; + PType *atype = TypeTable.FindType(NAME_StaticArray, (intptr_t)type, 0, &bucket); + if (atype == nullptr) + { + atype = new PStaticArray(type); + TypeTable.AddType(atype, NAME_StaticArray, (intptr_t)type, 0, bucket); + } + return (PStaticArray *)atype; +} + +/* PDynArray **************************************************************/ + +//========================================================================== +// +// PDynArray - Parameterized Constructor +// +//========================================================================== + +PDynArray::PDynArray(PType *etype,PStruct *backing) +: ElementType(etype), BackingType(backing) +{ + mDescriptiveName.Format("DynArray<%s>", etype->DescriptiveName()); + Size = sizeof(FArray); + Align = alignof(FArray); +} + +//========================================================================== +// +// PDynArray :: IsMatch +// +//========================================================================== + +bool PDynArray::IsMatch(intptr_t id1, intptr_t id2) const +{ + assert(id2 == 0); + const PType *elemtype = (const PType *)id1; + + return elemtype == ElementType; +} + +//========================================================================== +// +// PDynArray :: GetTypeIDs +// +//========================================================================== + +void PDynArray::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)ElementType; + id2 = 0; +} + +//========================================================================== +// +// PDynArray :: InitializeValue +// +//========================================================================== + +void PDynArray::InitializeValue(void *addr, const void *deff) const +{ + const FArray *def = (const FArray*)deff; + FArray *aray = (FArray*)addr; + + if (def == nullptr || def->Count == 0) + { + // Empty arrays do not need construction. + *aray = { nullptr, 0, 0 }; + } + else if (ElementType->GetRegType() != REGT_STRING) + { + // These are just integral values which can be done without any constructor hackery. + size_t blocksize = ElementType->Size * def->Count; + aray->Array = M_Malloc(blocksize); + memcpy(aray->Array, def->Array, blocksize); + aray->Most = aray->Count = def->Count; + } + else + { + // non-empty string arrays require explicit construction. + new(addr) TArray(*(TArray*)def); + } +} + +//========================================================================== +// +// PDynArray :: DestroyValue +// +//========================================================================== + +void PDynArray::DestroyValue(void *addr) const +{ + FArray *aray = (FArray*)addr; + + if (aray->Array != nullptr) + { + if (ElementType->GetRegType() != REGT_STRING) + { + M_Free(aray->Array); + } + else + { + // Damn those cursed strings again. :( + ((TArray*)addr)->~TArray(); + } + } + aray->Count = aray->Most = 0; + aray->Array = nullptr; +} + +//========================================================================== +// +// PDynArray :: SetDefaultValue +// +//========================================================================== + +void PDynArray::SetDefaultValue(void *base, unsigned offset, TArray *special) +{ + if (base != nullptr) memset((char*)base + offset, 0, sizeof(FArray)); // same as constructing an empty array. + if (special != nullptr) + { + special->Push(std::make_pair(this, offset)); + } +} + +//========================================================================== +// +// PDynArray :: SetPointer +// +//========================================================================== + +void PDynArray::SetPointerArray(void *base, unsigned offset, TArray *special) +{ + if (ElementType->isObjectPointer()) + { + // Add to the list of pointer arrays for this class. + special->Push(offset); + } +} + +//========================================================================== +// +// PDynArray :: WriteValue +// +//========================================================================== + +void PDynArray::WriteValue(FSerializer &ar, const char *key, const void *addr) const +{ + FArray *aray = (FArray*)addr; + // We may skip an empty array only if it gets stored under a named key. + // If no name is given, i.e. it's part of an outer array's element list, even empty arrays must be stored, + // because otherwise the array would lose its entry. + if (aray->Count > 0 || key == nullptr) + { + if (ar.BeginArray(key)) + { + const uint8_t *addrb = (const uint8_t *)aray->Array; + for (unsigned i = 0; i < aray->Count; ++i) + { + ElementType->WriteValue(ar, nullptr, addrb); + addrb += ElementType->Size; + } + ar.EndArray(); + } + } +} + +//========================================================================== +// +// PDynArray :: ReadValue +// +//========================================================================== + +bool PDynArray::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + FArray *aray = (FArray*)addr; + DestroyValue(addr); // note that even after calling this we still got a validly constructed empty array. + + if (ar.BeginArray(key)) + { + bool readsomething = false; + unsigned count = ar.ArraySize(); + + size_t blocksize = ElementType->Size * count; + aray->Array = M_Malloc(blocksize); + memset(aray->Array, 0, blocksize); + aray->Most = aray->Count = count; + + uint8_t *addrb = (uint8_t *)aray->Array; + for (unsigned i = 0; iGetRegType() == REGT_STRING) new(addrb) FString; + readsomething |= ElementType->ReadValue(ar, nullptr, addrb); + addrb += ElementType->Size; + } + ar.EndArray(); + return readsomething; + } + return false; +} + +//========================================================================== +// +// NewDynArray +// +// Creates a new DynArray of the given type, making sure not to create a +// duplicate. +// +//========================================================================== + +PDynArray *NewDynArray(PType *type) +{ + size_t bucket; + PType *atype = TypeTable.FindType(NAME_DynArray, (intptr_t)type, 0, &bucket); + if (atype == nullptr) + { + FString backingname; + + switch (type->GetRegType()) + { + case REGT_INT: + backingname.Format("DynArray_I%d", type->Size * 8); + break; + + case REGT_FLOAT: + backingname.Format("DynArray_F%d", type->Size * 8); + break; + + case REGT_STRING: + backingname = "DynArray_String"; + break; + + case REGT_POINTER: + if (type->isObjectPointer()) + backingname = "DynArray_Obj"; + else + backingname = "DynArray_Ptr"; + break; + + default: + I_Error("Unsupported dynamic array requested"); + break; + } + + auto backing = NewStruct(backingname, nullptr, true); + atype = new PDynArray(type, backing); + TypeTable.AddType(atype, NAME_DynArray, (intptr_t)type, 0, bucket); + } + return (PDynArray *)atype; +} + +/* PMap *******************************************************************/ + +//========================================================================== +// +// PMap - Parameterized Constructor +// +//========================================================================== + +PMap::PMap(PType *keytype, PType *valtype) +: KeyType(keytype), ValueType(valtype) +{ + mDescriptiveName.Format("Map<%s, %s>", keytype->DescriptiveName(), valtype->DescriptiveName()); + Size = sizeof(FMap); + Align = alignof(FMap); +} + +//========================================================================== +// +// PMap :: IsMatch +// +//========================================================================== + +bool PMap::IsMatch(intptr_t id1, intptr_t id2) const +{ + const PType *keyty = (const PType *)id1; + const PType *valty = (const PType *)id2; + + return keyty == KeyType && valty == ValueType; +} + +//========================================================================== +// +// PMap :: GetTypeIDs +// +//========================================================================== + +void PMap::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)KeyType; + id2 = (intptr_t)ValueType; +} + +//========================================================================== +// +// NewMap +// +// Returns a PMap for the given key and value types, ensuring not to create +// duplicates. +// +//========================================================================== + +PMap *NewMap(PType *keytype, PType *valuetype) +{ + size_t bucket; + PType *maptype = TypeTable.FindType(NAME_Map, (intptr_t)keytype, (intptr_t)valuetype, &bucket); + if (maptype == nullptr) + { + maptype = new PMap(keytype, valuetype); + TypeTable.AddType(maptype, NAME_Map, (intptr_t)keytype, (intptr_t)valuetype, bucket); + } + return (PMap *)maptype; +} + +/* PStruct ****************************************************************/ + +//========================================================================== +// +// PStruct - Parameterized Constructor +// +//========================================================================== + +PStruct::PStruct(FName name, PTypeBase *outer, bool isnative) +: PContainerType(name, outer) +{ + mDescriptiveName.Format("%sStruct<%s>", isnative? "Native" : "", name.GetChars()); + Size = 0; + isNative = isnative; +} + +//========================================================================== +// +// PStruct :: SetDefaultValue +// +//========================================================================== + +void PStruct::SetDefaultValue(void *base, unsigned offset, TArray *special) +{ + auto it = Symbols.GetIterator(); + PSymbolTable::MapType::Pair *pair; + while (it.NextPair(pair)) + { + auto field = dyn_cast(pair->Value); + if (field && !(field->Flags & VARF_Transient)) + { + field->Type->SetDefaultValue(base, unsigned(offset + field->Offset), special); + } + } +} + +//========================================================================== +// +// PStruct :: SetPointer +// +//========================================================================== + +void PStruct::SetPointer(void *base, unsigned offset, TArray *special) +{ + auto it = Symbols.GetIterator(); + PSymbolTable::MapType::Pair *pair; + while (it.NextPair(pair)) + { + auto field = dyn_cast(pair->Value); + if (field && !(field->Flags & VARF_Transient)) + { + field->Type->SetPointer(base, unsigned(offset + field->Offset), special); + } + } +} + +//========================================================================== +// +// PStruct :: SetPointerArray +// +//========================================================================== + +void PStruct::SetPointerArray(void *base, unsigned offset, TArray *special) +{ + auto it = Symbols.GetIterator(); + PSymbolTable::MapType::Pair *pair; + while (it.NextPair(pair)) + { + auto field = dyn_cast(pair->Value); + if (field && !(field->Flags & VARF_Transient)) + { + field->Type->SetPointerArray(base, unsigned(offset + field->Offset), special); + } + } +} + +//========================================================================== +// +// PStruct :: WriteValue +// +//========================================================================== + +void PStruct::WriteValue(FSerializer &ar, const char *key,const void *addr) const +{ + if (ar.BeginObject(key)) + { + Symbols.WriteFields(ar, addr); + ar.EndObject(); + } +} + +//========================================================================== +// +// PStruct :: ReadValue +// +//========================================================================== + +bool PStruct::ReadValue(FSerializer &ar, const char *key, void *addr) const +{ + if (ar.BeginObject(key)) + { + bool ret = Symbols.ReadFields(ar, addr, DescriptiveName()); + ar.EndObject(); + return ret; + } + return false; +} + +//========================================================================== +// +// PStruct :: AddField +// +// Appends a new field to the end of a struct. Returns either the new field +// or nullptr if a symbol by that name already exists. +// +//========================================================================== + +PField *PStruct::AddField(FName name, PType *type, uint32_t flags) +{ + assert(type->Size > 0); + return Symbols.AddField(name, type, flags, Size, &Align); +} + +//========================================================================== +// +// PStruct :: AddField +// +// Appends a new native field to the struct. Returns either the new field +// or nullptr if a symbol by that name already exists. +// +//========================================================================== + +PField *PStruct::AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue) +{ + return Symbols.AddNativeField(name, type, address, flags, bitvalue); +} + +//========================================================================== +// +// NewStruct +// Returns a PStruct for the given name and container, making sure not to +// create duplicates. +// +//========================================================================== + +PStruct *NewStruct(FName name, PTypeBase *outer, bool native) +{ + size_t bucket; + if (outer == nullptr) outer = Namespaces.GlobalNamespace; + PType *stype = TypeTable.FindType(NAME_Struct, (intptr_t)outer, name.GetIndex(), &bucket); + if (stype == nullptr) + { + stype = new PStruct(name, outer, native); + TypeTable.AddType(stype, NAME_Struct, (intptr_t)outer, name.GetIndex(), bucket); + } + return static_cast(stype); +} + + +/* PPrototype *************************************************************/ + +//========================================================================== +// +// PPrototype - Parameterized Constructor +// +//========================================================================== + +PPrototype::PPrototype(const TArray &rettypes, const TArray &argtypes) +: ArgumentTypes(argtypes), ReturnTypes(rettypes) +{ +} + +//========================================================================== +// +// PPrototype :: IsMatch +// +//========================================================================== + +bool PPrototype::IsMatch(intptr_t id1, intptr_t id2) const +{ + const TArray *args = (const TArray *)id1; + const TArray *rets = (const TArray *)id2; + + return *args == ArgumentTypes && *rets == ReturnTypes; +} + +//========================================================================== +// +// PPrototype :: GetTypeIDs +// +//========================================================================== + +void PPrototype::GetTypeIDs(intptr_t &id1, intptr_t &id2) const +{ + id1 = (intptr_t)&ArgumentTypes; + id2 = (intptr_t)&ReturnTypes; +} + +//========================================================================== +// +// NewPrototype +// +// Returns a PPrototype for the given return and argument types, making sure +// not to create duplicates. +// +//========================================================================== + +PPrototype *NewPrototype(const TArray &rettypes, const TArray &argtypes) +{ + size_t bucket; + PType *proto = TypeTable.FindType(NAME_Prototype, (intptr_t)&argtypes, (intptr_t)&rettypes, &bucket); + if (proto == nullptr) + { + proto = new PPrototype(rettypes, argtypes); + TypeTable.AddType(proto, NAME_Prototype, (intptr_t)&argtypes, (intptr_t)&rettypes, bucket); + } + return static_cast(proto); +} + +/* PClass *****************************************************************/ + +//========================================================================== +// +// +// +//========================================================================== + +PClassType::PClassType(PClass *cls) +{ + assert(cls->VMType == nullptr); + Descriptor = cls; + TypeName = cls->TypeName; + if (cls->ParentClass != nullptr) + { + ParentType = cls->ParentClass->VMType; + assert(ParentType != nullptr); + Symbols.SetParentTable(&ParentType->Symbols); + ScopeFlags = ParentType->ScopeFlags; + } + cls->VMType = this; + mDescriptiveName.Format("Class<%s>", cls->TypeName.GetChars()); +} + +//========================================================================== +// +// PClass :: AddField +// +//========================================================================== + +PField *PClassType::AddField(FName name, PType *type, uint32_t flags) +{ + return Descriptor->AddField(name, type, flags); +} + +//========================================================================== +// +// PClass :: AddNativeField +// +//========================================================================== + +PField *PClassType::AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue) +{ + auto field = Symbols.AddNativeField(name, type, address, flags, bitvalue); + if (field != nullptr) Descriptor->Fields.Push(field); + return field; +} + +//========================================================================== +// +// +// +//========================================================================== + +PClassType *NewClassType(PClass *cls) +{ + size_t bucket; + PType *ptype = TypeTable.FindType(NAME_Object, 0, cls->TypeName.GetIndex(), &bucket); + if (ptype == nullptr) + { + ptype = new PClassType(cls); + TypeTable.AddType(ptype, NAME_Object, 0, cls->TypeName.GetIndex(), bucket); + } + return static_cast(ptype); +} + + +/* FTypeTable **************************************************************/ + +//========================================================================== +// +// FTypeTable :: FindType +// +//========================================================================== + +PType *FTypeTable::FindType(FName type_name, intptr_t parm1, intptr_t parm2, size_t *bucketnum) +{ + size_t bucket = Hash(type_name, parm1, parm2) % HASH_SIZE; + if (bucketnum != nullptr) + { + *bucketnum = bucket; + } + for (PType *type = TypeHash[bucket]; type != nullptr; type = type->HashNext) + { + if (type->TypeTableType == type_name && type->IsMatch(parm1, parm2)) + { + return type; + } + } + return nullptr; +} + +//========================================================================== +// +// FTypeTable :: AddType - Fully Parameterized Version +// +//========================================================================== + +void FTypeTable::AddType(PType *type, FName type_name, intptr_t parm1, intptr_t parm2, size_t bucket) +{ +#ifdef _DEBUG + size_t bucketcheck; + assert(FindType(type_name, parm1, parm2, &bucketcheck) == nullptr && "Type must not be inserted more than once"); + assert(bucketcheck == bucket && "Passed bucket was wrong"); +#endif + type->TypeTableType = type_name; + type->HashNext = TypeHash[bucket]; + TypeHash[bucket] = type; +} + +//========================================================================== +// +// FTypeTable :: AddType - Simple Version +// +//========================================================================== + +void FTypeTable::AddType(PType *type, FName type_name) +{ + intptr_t parm1, parm2; + size_t bucket; + + // Type table stuff id only needed to let all classes hash to the same group. For all other types this is pointless. + type->TypeTableType = type_name; + type->GetTypeIDs(parm1, parm2); + bucket = Hash(type_name, parm1, parm2) % HASH_SIZE; + assert(FindType(type_name, parm1, parm2, nullptr) == nullptr && "Type must not be inserted more than once"); + + type->HashNext = TypeHash[bucket]; + TypeHash[bucket] = type; +} + +//========================================================================== +// +// FTypeTable :: Hash STATIC +// +//========================================================================== + +size_t FTypeTable::Hash(FName p1, intptr_t p2, intptr_t p3) +{ + size_t i1 = (size_t)p1.GetIndex(); + + // Swap the high and low halves of i1. The compiler should be smart enough + // to transform this into a ROR or ROL. + i1 = (i1 >> (sizeof(size_t)*4)) | (i1 << (sizeof(size_t)*4)); + + if (p1 != NAME_Prototype) + { + size_t i2 = (size_t)p2; + size_t i3 = (size_t)p3; + return (~i1 ^ i2) + i3 * 961748927; // i3 is multiplied by a prime + } + else + { // Prototypes need to hash the TArrays at p2 and p3 + const TArray *a2 = (const TArray *)p2; + const TArray *a3 = (const TArray *)p3; + for (unsigned i = 0; i < a2->Size(); ++i) + { + i1 = (i1 * 961748927) + (size_t)((*a2)[i]); + } + for (unsigned i = 0; i < a3->Size(); ++i) + { + i1 = (i1 * 961748927) + (size_t)((*a3)[i]); + } + return i1; + } +} + +//========================================================================== +// +// FTypeTable :: Clear +// +//========================================================================== + +void FTypeTable::Clear() +{ + for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i) + { + for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr;) + { + auto next = ty->HashNext; + delete ty; + ty = next; + } + } + memset(TypeHash, 0, sizeof(TypeHash)); +} + +#include "c_dispatch.h" +CCMD(typetable) +{ + DumpTypeTable(); +} + diff --git a/source/common/scripting/core/types.h b/source/common/scripting/core/types.h new file mode 100644 index 000000000..e40e8edf3 --- /dev/null +++ b/source/common/scripting/core/types.h @@ -0,0 +1,625 @@ +#pragma once + +#include "dobject.h" +#include "serializer.h" +#include "symbols.h" +#include "scopebarrier.h" + +// Variable/parameter/field flags ------------------------------------------- + +// Making all these different storage types use a common set of flags seems +// like the simplest thing to do. + +enum +{ + VARF_Optional = (1<<0), // func param is optional + VARF_Method = (1<<1), // func has an implied self parameter + VARF_Action = (1<<2), // func has implied owner and state parameters + VARF_Native = (1<<3), // func is native code, field is natively defined + VARF_ReadOnly = (1<<4), // field is read only, do not write to it + VARF_Private = (1<<5), // field is private to containing class + VARF_Protected = (1<<6), // field is only accessible by containing class and children. + VARF_Deprecated = (1<<7), // Deprecated fields should output warnings when used. + VARF_Virtual = (1<<8), // function is virtual + VARF_Final = (1<<9), // Function may not be overridden in subclasses + VARF_In = (1<<10), + VARF_Out = (1<<11), + VARF_Implicit = (1<<12), // implicitly created parameters (i.e. do not compare types when checking function signatures) + VARF_Static = (1<<13), + VARF_InternalAccess = (1<<14), // overrides VARF_ReadOnly for internal script code. + VARF_Override = (1<<15), // overrides a virtual function from the parent class. + VARF_Ref = (1<<16), // argument is passed by reference. + VARF_Transient = (1<<17), // don't auto serialize field. + VARF_Meta = (1<<18), // static class data (by necessity read only.) + VARF_VarArg = (1<<19), // [ZZ] vararg: don't typecheck values after ... in function signature + VARF_UI = (1<<20), // [ZZ] ui: object is ui-scope only (can't modify playsim) + VARF_Play = (1<<21), // [ZZ] play: object is playsim-scope only (can't access ui) + VARF_VirtualScope = (1<<22), // [ZZ] virtualscope: object should use the scope of the particular class it's being used with (methods only) + VARF_ClearScope = (1<<23), // [ZZ] clearscope: this method ignores the member access chain that leads to it and is always plain data. +}; + +// Basic information shared by all types ------------------------------------ + +// Only one copy of a type is ever instantiated at one time. +// - Enums, classes, and structs are defined by their names and outer classes. +// - Pointers are uniquely defined by the type they point at. +// - ClassPointers are also defined by their class restriction. +// - Arrays are defined by their element type and count. +// - DynArrays are defined by their element type. +// - Maps are defined by their key and value types. +// - Prototypes are defined by the argument and return types. +// - Functions are defined by their names and outer objects. +// In table form: +// Outer Name Type Type2 Count +// Enum * * +// Class * * +// Struct * * +// Function * * +// Pointer * +// ClassPointer + * +// Array * * +// DynArray * +// Map * * +// Prototype *+ *+ + +class PContainerType; +class PPointer; +class PClassPointer; +class PArray; +class PStruct; +class PClassType; + +struct ZCC_ExprConstant; +class PType : public PTypeBase +{ +protected: + + enum ETypeFlags + { + TYPE_Scalar = 1, + TYPE_Container = 2, + TYPE_Int = 4, + TYPE_IntNotInt = 8, // catch-all for subtypes that are not being checked by type directly. + TYPE_Float = 16, + TYPE_Pointer = 32, + TYPE_ObjectPointer = 64, + TYPE_ClassPointer = 128, + TYPE_Array = 256, + + TYPE_IntCompatible = TYPE_Int | TYPE_IntNotInt, // must be the combination of all flags that are subtypes of int and can be cast to an int. + }; + +public: + FName TypeTableType; // The type to use for hashing into the type table + unsigned int Size; // this type's size + unsigned int Align; // this type's preferred alignment + unsigned int Flags = 0; // What is this type? + PType *HashNext; // next type in this type table + PSymbolTable Symbols; + bool MemberOnly = false; // type may only be used as a struct/class member but not as a local variable or function argument. + FString mDescriptiveName; + VersionInfo mVersion = { 0,0,0 }; + uint8_t loadOp, storeOp, moveOp, RegType, RegCount; + EScopeFlags ScopeFlags = (EScopeFlags)0; + + PType(unsigned int size = 1, unsigned int align = 1); + virtual ~PType(); + virtual bool isNumeric() { return false; } + + // Writes the value of a variable of this type at (addr) to an archive, preceded by + // a tag indicating its type. The tag is there so that variable types can be changed + // without completely breaking savegames, provided that the change isn't between + // totally unrelated types. + virtual void WriteValue(FSerializer &ar, const char *key,const void *addr) const; + + // Returns true if the stored value was compatible. False otherwise. + // If the value was incompatible, then the memory at *addr is unchanged. + virtual bool ReadValue(FSerializer &ar, const char *key,void *addr) const; + + // Sets the default value for this type at (base + offset) + // If the default value is binary 0, then this function doesn't need + // to do anything, because PClass::Extend() takes care of that. + // + // The stroffs array is so that types that need special initialization + // and destruction (e.g. strings) can add their offsets to it for special + // initialization when the object is created and destruction when the + // object is destroyed. + virtual void SetDefaultValue(void *base, unsigned offset, TArray *special=NULL); + virtual void SetPointer(void *base, unsigned offset, TArray *ptrofs = NULL); + virtual void SetPointerArray(void *base, unsigned offset, TArray *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 *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 *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 *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 *special) override; + void SetPointer(void *base, unsigned offset, TArray *special) override; + void SetPointerArray(void *base, unsigned offset, TArray *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 *specials) override; + void InitializeValue(void *addr, const void *def) const override; + void DestroyValue(void *addr) const override; + void SetPointerArray(void *base, unsigned offset, TArray *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 *specials) override; + void SetPointer(void *base, unsigned offset, TArray *specials) override; + void SetPointerArray(void *base, unsigned offset, TArray *special) override; +}; + +class PPrototype : public PCompoundType +{ +public: + PPrototype(const TArray &rettypes, const TArray &argtypes); + + TArray ArgumentTypes; + TArray 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(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 &rettypes, const TArray &argtypes); +PClassType *NewClassType(PClass *cls); + +// Built-in types ----------------------------------------------------------- + +extern PErrorType *TypeError; +extern PErrorType *TypeAuto; +extern PVoidType *TypeVoid; +extern PInt *TypeSInt8, *TypeUInt8; +extern PInt *TypeSInt16, *TypeUInt16; +extern PInt *TypeSInt32, *TypeUInt32; +extern PBool *TypeBool; +extern PFloat *TypeFloat32, *TypeFloat64; +extern PString *TypeString; +extern PName *TypeName; +extern PSound *TypeSound; +extern PColor *TypeColor; +extern PTextureID *TypeTextureID; +extern PStruct *TypeVector2; +extern PStruct *TypeVector3; +extern PStruct *TypeColorStruct; +extern PStruct *TypeStringStruct; +extern PPointer *TypeFont; +extern PStateLabel *TypeStateLabel; +extern PPointer *TypeNullPtr; +extern PPointer *TypeVoidPtr; + +inline FString &DObject::StringVar(FName field) +{ + return *(FString*)ScriptVar(field, TypeString); +} + +// Type tables -------------------------------------------------------------- + +struct FTypeTable +{ + enum { HASH_SIZE = 1021 }; + + PType *TypeHash[HASH_SIZE]; + + PType *FindType(FName type_name, intptr_t parm1, intptr_t parm2, size_t *bucketnum); + void AddType(PType *type, FName type_name, intptr_t parm1, intptr_t parm2, size_t bucket); + void AddType(PType *type, FName type_name); + void Clear(); + + static size_t Hash(FName p1, intptr_t p2, intptr_t p3); +}; + + +extern FTypeTable TypeTable; + diff --git a/source/common/scripting/core/vmdisasm.cpp b/source/common/scripting/core/vmdisasm.cpp new file mode 100644 index 000000000..b3dbece6e --- /dev/null +++ b/source/common/scripting/core/vmdisasm.cpp @@ -0,0 +1,687 @@ +/* +** vmdisasm.cpp +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "dobject.h" +#include "c_console.h" +#include "templates.h" +#include "vmintern.h" +#include "printf.h" + +#define NOP MODE_AUNUSED | MODE_BUNUSED | MODE_CUNUSED + +#define LI MODE_AI | MODE_BCJOINT | MODE_BCIMMS +#define LKI MODE_AI | MODE_BCJOINT | MODE_BCKI +#define LKF MODE_AF | MODE_BCJOINT | MODE_BCKF +#define LKS MODE_AS | MODE_BCJOINT | MODE_BCKS +#define LKP MODE_AP | MODE_BCJOINT | MODE_BCKP +#define LFP MODE_AP | MODE_BUNUSED | MODE_CUNUSED + +#define RIRPKI MODE_AI | MODE_BP | MODE_CKI +#define RIRPRI MODE_AI | MODE_BP | MODE_CI +#define RFRPKI MODE_AF | MODE_BP | MODE_CKI +#define RFRPRI MODE_AF | MODE_BP | MODE_CI +#define RSRPKI MODE_AS | MODE_BP | MODE_CKI +#define RSRPRI MODE_AS | MODE_BP | MODE_CI +#define RPRPKI MODE_AP | MODE_BP | MODE_CKI +#define RPRPRI MODE_AP | MODE_BP | MODE_CI +#define RVRPKI MODE_AV | MODE_BP | MODE_CKI +#define RVRPRI MODE_AV | MODE_BP | MODE_CI +#define RIRPI8 MODE_AI | MODE_BP | MODE_CIMMZ + +#define RPRIKI MODE_AP | MODE_BI | MODE_CKI +#define RPRIRI MODE_AP | MODE_BI | MODE_CI +#define RPRFKI MODE_AP | MODE_BF | MODE_CKI +#define RPRFRI MODE_AP | MODE_BF | MODE_CI +#define RPRSKI MODE_AP | MODE_BS | MODE_CKI +#define RPRSRI MODE_AP | MODE_BS | MODE_CI +#define RPRPKI MODE_AP | MODE_BP | MODE_CKI +#define RPRPRI MODE_AP | MODE_BP | MODE_CI +#define RPRVKI MODE_AP | MODE_BV | MODE_CKI +#define RPRVRI MODE_AP | MODE_BV | MODE_CI +#define RPRII8 MODE_AP | MODE_BI | MODE_CIMMZ + +#define RIRI MODE_AI | MODE_BI | MODE_CUNUSED +#define RFRF MODE_AF | MODE_BF | MODE_CUNUSED +#define RSRS MODE_AS | MODE_BS | MODE_CUNUSED +#define RPRP MODE_AP | MODE_BP | MODE_CUNUSED +#define RPKP MODE_AP | MODE_BKP | MODE_CUNUSED +#define RXRXI8 MODE_AX | MODE_BX | MODE_CIMMZ +#define RPRPRP MODE_AP | MODE_BP | MODE_CP +#define RPRPKP MODE_AP | MODE_BP | MODE_CKP + +#define RII16 MODE_AI | MODE_BCJOINT | MODE_BCIMMS +#define I24 MODE_ABCJOINT +#define I8 MODE_AIMMZ | MODE_BUNUSED | MODE_CUNUSED +#define I8I16 MODE_AIMMZ | MODE_BCIMMZ +#define __BCP MODE_PARAM24 +#define RPI8 MODE_AP | MODE_BIMMZ | MODE_CUNUSED +#define KPI8 MODE_AKP | MODE_BIMMZ | MODE_CUNUSED +#define RPI8I8 MODE_AP | MODE_BIMMZ | MODE_CIMMZ +#define RPRPI8 MODE_AP | MODE_BP | MODE_CIMMZ +#define KPI8I8 MODE_AKP | MODE_BIMMZ | MODE_CIMMZ +#define I8BCP MODE_AIMMZ | MODE_BCJOINT | MODE_BCPARAM +#define THROW MODE_AIMMZ | MODE_BCTHROW +#define CATCH MODE_AIMMZ | MODE_BCCATCH +#define CAST MODE_AX | MODE_BX | MODE_CIMMZ | MODE_BCCAST +#define CASTB MODE_AI | MODE_BX | MODE_CIMMZ | MODE_BCCAST + +#define RSRSRS MODE_AS | MODE_BS | MODE_CS +#define RIRS MODE_AI | MODE_BS | MODE_CUNUSED +#define I8RXRX MODE_AIMMZ | MODE_BX | MODE_CX + +#define RIRIRI MODE_AI | MODE_BI | MODE_CI +#define RIRII8 MODE_AI | MODE_BI | MODE_CIMMZ +#define RFRII8 MODE_AF | MODE_BI | MODE_CIMMZ +#define RPRII8 MODE_AP | MODE_BI | MODE_CIMMZ +#define RSRII8 MODE_AS | MODE_BI | MODE_CIMMZ +#define RIRIKI MODE_AI | MODE_BI | MODE_CKI +#define RIKIRI MODE_AI | MODE_BKI | MODE_CI +#define RIKII8 MODE_AI | MODE_BKI | MODE_CIMMZ +#define RIRIIs MODE_AI | MODE_BI | MODE_CIMMS +#define I8RIRI MODE_AIMMZ | MODE_BI | MODE_CI +#define I8RIKI MODE_AIMMZ | MODE_BI | MODE_CKI +#define I8KIRI MODE_AIMMZ | MODE_BKI | MODE_CI + +#define RFRFRF MODE_AF | MODE_BF | MODE_CF +#define RFRFKF MODE_AF | MODE_BF | MODE_CKF +#define RFKFRF MODE_AF | MODE_BKF | MODE_CF +#define I8RFRF MODE_AIMMZ | MODE_BF | MODE_CF +#define I8RFKF MODE_AIMMZ | MODE_BF | MODE_CKF +#define I8KFRF MODE_AIMMZ | MODE_BKF | MODE_CF +#define RFRFI8 MODE_AF | MODE_BF | MODE_CIMMZ + +#define RVRV MODE_AV | MODE_BV | MODE_CUNUSED +#define RVRVRV MODE_AV | MODE_BV | MODE_CV +#define RVRVKV MODE_AV | MODE_BV | MODE_CKV +#define RVKVRV MODE_AV | MODE_BKV | MODE_CV +#define RVRVRF MODE_AV | MODE_BV | MODE_CF +#define RVRVKF MODE_AV | MODE_BV | MODE_CKF +#define RVKVRF MODE_AV | MODE_BKV | MODE_CF +#define RFRV MODE_AF | MODE_BV | MODE_CUNUSED +#define I8RVRV MODE_AIMMZ | MODE_BV | MODE_CV +#define I8RVKV MODE_AIMMZ | MODE_BV | MODE_CKV + +#define RPRPRI MODE_AP | MODE_BP | MODE_CI +#define RPRPKI MODE_AP | MODE_BP | MODE_CKI +#define RIRPRP MODE_AI | MODE_BP | MODE_CP +#define I8RPRP MODE_AIMMZ | MODE_BP | MODE_CP +#define I8RPKP MODE_AIMMZ | MODE_BP | MODE_CKP + +#define CIRR MODE_ACMP | MODE_BI | MODE_CI +#define CIRK MODE_ACMP | MODE_BI | MODE_CKI +#define CIKR MODE_ACMP | MODE_BKI | MODE_CI +#define CFRR MODE_ACMP | MODE_BF | MODE_CF +#define CFRK MODE_ACMP | MODE_BF | MODE_CKF +#define CFKR MODE_ACMP | MODE_BKF | MODE_CF +#define CVRR MODE_ACMP | MODE_BV | MODE_CV +#define CVRK MODE_ACMP | MODE_BV | MODE_CKV +#define CPRR MODE_ACMP | MODE_BP | MODE_CP +#define CPRK MODE_ACMP | MODE_BP | MODE_CKP + +const VMOpInfo OpInfo[NUM_OPS] = +{ +#define xx(op, name, mode, alt, kreg, ktype) { #name, mode }, +#include "vmops.h" +}; + +static const char *const FlopNames[] = +{ + "abs", + "neg", + "exp", + "log", + "log10", + "sqrt", + "ceil", + "floor", + + "acos rad", + "asin rad", + "atan rad", + "cos rad", + "sin rad", + "tan rad", + + "acos deg", + "asin deg", + "atan deg", + "cos deg", + "sin deg", + "tan deg", + + "cosh", + "sinh", + "tanh", + + "round", +}; + +static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func); + +static int printf_wrapper(FILE *f, const char *fmt, ...) +{ + va_list argptr; + int count; + + va_start(argptr, fmt); + if (f == NULL) + { + count = VPrintf(PRINT_HIGH, fmt, argptr); + } + else + { + count = vfprintf(f, fmt, argptr); + } + va_end(argptr); + return count; +} + +void VMDumpConstants(FILE *out, const VMScriptFunction *func) +{ + char tmp[30]; + int i, j, k, kk; + + if (func->KonstD != NULL && func->NumKonstD != 0) + { + printf_wrapper(out, "\nConstant integers:\n"); + kk = (func->NumKonstD + 3) / 4; + for (i = 0; i < kk; ++i) + { + for (j = 0, k = i; j < 4 && k < func->NumKonstD; j++, k += kk) + { + mysnprintf(tmp, countof(tmp), "%3d. %d", k, func->KonstD[k]); + printf_wrapper(out, "%-20s", tmp); + } + printf_wrapper(out, "\n"); + } + } + if (func->KonstF != NULL && func->NumKonstF != 0) + { + printf_wrapper(out, "\nConstant floats:\n"); + kk = (func->NumKonstF + 3) / 4; + for (i = 0; i < kk; ++i) + { + for (j = 0, k = i; j < 4 && k < func->NumKonstF; j++, k += kk) + { + mysnprintf(tmp, countof(tmp), "%3d. %.16f", k, func->KonstF[k]); + printf_wrapper(out, "%-20s", tmp); + } + printf_wrapper(out, "\n"); + } + } + if (func->KonstA != NULL && func->NumKonstA != 0) + { + printf_wrapper(out, "\nConstant addresses:\n"); + kk = (func->NumKonstA + 3) / 4; + for (i = 0; i < kk; ++i) + { + for (j = 0, k = i; j < 4 && k < func->NumKonstA; j++, k += kk) + { + mysnprintf(tmp, countof(tmp), "%3d. %p", k, func->KonstA[k].v); + printf_wrapper(out, "%-22s", tmp); + } + printf_wrapper(out, "\n"); + } + } + if (func->KonstS != NULL && func->NumKonstS != 0) + { + printf_wrapper(out, "\nConstant strings:\n"); + for (i = 0; i < func->NumKonstS; ++i) + { + printf_wrapper(out, "%3d. %s\n", i, func->KonstS[i].GetChars()); + } + } +} + +void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func) +{ + VMFunction *callfunc; + const char *name; + int col; + int mode; + int a; + bool cmp; + char cmpname[8]; + + for (int i = 0; i < codesize; ++i) + { + name = OpInfo[code[i].op].Name; + mode = OpInfo[code[i].op].Mode; + a = code[i].a; + cmp = (mode & MODE_ATYPE) == MODE_ACMP; + + // String comparison encodes everything in a single instruction. + if (code[i].op == OP_CMPS) + { + switch (a & CMP_METHOD_MASK) + { + case CMP_EQ: name = "beq"; break; + case CMP_LT: name = "blt"; break; + case CMP_LE: name = "ble"; break; + } + mode = MODE_AIMMZ; + mode |= (a & CMP_BK) ? MODE_BKS : MODE_BS; + mode |= (a & CMP_CK) ? MODE_CKS : MODE_CS; + a &= CMP_CHECK | CMP_APPROX; + cmp = true; + } + if (code[i].op == OP_PARAM && code[i].a & REGT_ADDROF) + { + name = "parama"; + } + if (cmp) + { // Comparison instruction. Modify name for inverted test. + if (!(a & CMP_CHECK)) + { + strcpy(cmpname, name); + if (name[1] == 'e') + { // eq -> ne + cmpname[1] = 'n', cmpname[2] = 'e'; + } + else if (name[2] == 't') + { // lt -> ge + cmpname[1] = 'g', cmpname[2] = 'e'; + } + else + { // le -> gt + cmpname[1] = 'g', cmpname[2] = 't'; + } + name = cmpname; + } + } + printf_wrapper(out, "%08x: %02x%02x%02x%02x %-8s", i << 2, code[i].op, code[i].a, code[i].b, code[i].c, name); + col = 0; + switch (code[i].op) + { + case OP_JMP: + //case OP_TRY: + col = printf_wrapper(out, "%08x", (i + 1 + code[i].i24) << 2); + break; + + case OP_PARAMI: + col = printf_wrapper(out, "%d", code[i].i24); + break; + + case OP_CALL_K: + { + callfunc = (VMFunction *)func->KonstA[code[i].a].o; + col = printf_wrapper(out, "[%p],%d", callfunc, code[i].b); + if (code[i].op == OP_CALL_K) + { + col += printf_wrapper(out, ",%d", code[i].c); + } + break; + } + + case OP_PARAM: + { + col = print_reg(out, col, code[i].i24 & 0xffffff, MODE_PARAM24, 16, func); + break; + } + + case OP_RESULT: + { + // Default handling for this broke after changing OP_PARAM... + col = print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func); + break; + } + + case OP_RET: + if (code[i].b != REGT_NIL) + { + if (a == RET_FINAL) + { + col = print_reg(out, 0, code[i].i16u, MODE_PARAM, 16, func); + } + else + { + col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func); + col += print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func); + if (a & RET_FINAL) + { + col += printf_wrapper(out, " [final]"); + } + } + } + break; + + case OP_RETI: + if (a == RET_FINAL) + { + col = printf_wrapper(out, "%d", code[i].i16); + } + else + { + col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func); + col += print_reg(out, col, code[i].i16, MODE_IMMS, 16, func); + if (a & RET_FINAL) + { + col += printf_wrapper(out, " [final]"); + } + } + break; + + case OP_FLOP: + col = printf_wrapper(out, "f%d,f%d,%d", code[i].a, code[i].b, code[i].c); + if (code[i].c < countof(FlopNames)) + { + col += printf_wrapper(out, " [%s]", FlopNames[code[i].c]); + } + break; + + default: + + + if ((mode & MODE_BCTYPE) == MODE_BCCAST) + { + switch (code[i].c) + { + case CASTB_I: + mode = MODE_AI | MODE_BI | MODE_CUNUSED; + break; + case CASTB_A: + mode = MODE_AI | MODE_BP | MODE_CUNUSED; + break; + case CAST_I2F: + case CAST_U2F: + mode = MODE_AF | MODE_BI | MODE_CUNUSED; + break; + case CAST_Co2S: + case CAST_So2S: + case CAST_N2S: + case CAST_I2S: + case CAST_U2S: + mode = MODE_AS | MODE_BI | MODE_CUNUSED; + break; + case CAST_F2I: + case CAST_F2U: + case CASTB_F: + mode = MODE_AI | MODE_BF | MODE_CUNUSED; + break; + case CAST_F2S: + case CAST_V22S: + case CAST_V32S: + mode = MODE_AS | MODE_BF | MODE_CUNUSED; + break; + case CAST_P2S: + mode = MODE_AS | MODE_BP | MODE_CUNUSED; + break; + case CAST_S2Co: + case CAST_S2So: + case CAST_S2N: + case CAST_S2I: + case CASTB_S: + mode = MODE_AI | MODE_BS | MODE_CUNUSED; + break; + case CAST_S2F: + mode = MODE_AF | MODE_BS | MODE_CUNUSED; + break; + default: + mode = MODE_AX | MODE_BX | MODE_CIMMZ; + break; + } + } + col = print_reg(out, 0, a, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func); + if ((mode & MODE_BCTYPE) == MODE_BCTHROW) + { + if (code[i].a == 0) + { + mode = (MODE_BP | MODE_CUNUSED); + } + else if (code[i].a == 1) + { + mode = (MODE_BKP | MODE_CUNUSED); + } + else + { + mode = (MODE_BCJOINT | MODE_BCIMMS); + } + } + else if ((mode & MODE_BCTYPE) == MODE_BCCATCH) + { + switch (code[i].a) + { + case 0: + mode = MODE_BUNUSED | MODE_CUNUSED; + break; + case 1: + mode = MODE_BUNUSED | MODE_CP; + break; + case 2: + mode = MODE_BP | MODE_CP; + break; + case 3: + mode = MODE_BKP | MODE_CP; + break; + default: + mode = MODE_BIMMZ | MODE_CIMMZ; + break; + } + } + if ((mode & (MODE_BTYPE | MODE_CTYPE)) == MODE_BCJOINT) + { + col += print_reg(out, col, code[i].i16u, (mode & MODE_BCTYPE) >> MODE_BCSHIFT, 16, func); + } + else + { + col += print_reg(out, col, code[i].b, (mode & MODE_BTYPE) >> MODE_BSHIFT, 24, func); + col += print_reg(out, col, code[i].c, (mode & MODE_CTYPE) >> MODE_CSHIFT, 24, func); + } + break; + } + if (cmp && i + 1 < codesize) + { + if (code[i+1].op != OP_JMP) + { // comparison instructions must be followed by jump + col += printf_wrapper(out, " => *!*!*!*\n"); + } + else + { + col += printf_wrapper(out, " => %08x", (i + 2 + code[i+1].i24) << 2); + } + } + if (col > 30) + { + col = 30; + } + printf_wrapper(out, "%*c", 30 - col, ';'); + if (!cmp && (code[i].op == OP_JMP || /*code[i].op == OP_TRY ||*/ code[i].op == OP_PARAMI)) + { + printf_wrapper(out, "%d\n", code[i].i24); + } + else + { + printf_wrapper(out, "%d,%d,%d", code[i].a, code[i].b, code[i].c); + if (cmp && i + 1 < codesize && code[i+1].op == OP_JMP) + { + printf_wrapper(out, ",%d\n", code[++i].i24); + } + else if (code[i].op == OP_CALL_K) + { + printf_wrapper(out, " [%s]\n", callfunc->PrintableName.GetChars()); + } + else + { + printf_wrapper(out, "\n"); + } + } + } +} + +static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func) +{ + if (mode == MODE_UNUSED || mode == MODE_CMP) + { + return 0; + } + if (col > 0) + { + col = printf_wrapper(out, ","); + } + switch(mode) + { + case MODE_I: + return col+printf_wrapper(out, "d%d", arg); + case MODE_F: + return col+printf_wrapper(out, "f%d", arg); + case MODE_S: + return col+printf_wrapper(out, "s%d", arg); + case MODE_P: + return col+printf_wrapper(out, "a%d", arg); + case MODE_V: + return col+printf_wrapper(out, "v%d", arg); + + case MODE_KI: + if (func != NULL) + { + return col+printf_wrapper(out, "%d", func->KonstD[arg]); + } + return printf_wrapper(out, "kd%d", arg); + case MODE_KF: + if (func != NULL) + { + return col+printf_wrapper(out, "%#g", func->KonstF[arg]); + } + return col+printf_wrapper(out, "kf%d", arg); + case MODE_KS: + if (func != NULL) + { + return col+printf_wrapper(out, "\"%.27s\"", func->KonstS[arg].GetChars()); + } + return col+printf_wrapper(out, "ks%d", arg); + case MODE_KP: + if (func != NULL) + { + return col+printf_wrapper(out, "%p", func->KonstA[arg]); + } + return col+printf_wrapper(out, "ka%d", arg); + case MODE_KV: + if (func != NULL) + { + return col+printf_wrapper(out, "(%f,%f,%f)", func->KonstF[arg], func->KonstF[arg+1], func->KonstF[arg+2]); + } + return col+printf_wrapper(out, "kv%d", arg); + + case MODE_IMMS: + return col+printf_wrapper(out, "%d", (arg << immshift) >> immshift); + + case MODE_IMMZ: + return col+printf_wrapper(out, "%d", arg); + + case MODE_PARAM: + case MODE_PARAM24: + { + int regtype, regnum; +#ifdef __BIG_ENDIAN__ + if (mode == MODE_PARAM) + { + regtype = (arg >> 8) & 255; + regnum = arg & 255; + } + else + { + regtype = (arg >> 16) & 255; + regnum = arg & 65535; + } +#else + if (mode == MODE_PARAM) + { + regtype = arg & 255; + regnum = (arg >> 8) & 255; + } + else + { + regtype = arg & 255; + regnum = (arg >> 8) & 65535; + } +#endif + switch (regtype & (REGT_TYPE | REGT_KONST | REGT_MULTIREG)) + { + case REGT_INT: + return col+printf_wrapper(out, "d%d", regnum); + case REGT_FLOAT: + return col+printf_wrapper(out, "f%d", regnum); + case REGT_STRING: + return col+printf_wrapper(out, "s%d", regnum); + case REGT_POINTER: + return col+printf_wrapper(out, "a%d", regnum); + case REGT_FLOAT | REGT_MULTIREG2: + return col+printf_wrapper(out, "v%d.2", regnum); + case REGT_FLOAT | REGT_MULTIREG3: + return col+printf_wrapper(out, "v%d.3", regnum); + case REGT_INT | REGT_KONST: + return col+print_reg(out, 0, regnum, MODE_KI, 0, func); + case REGT_FLOAT | REGT_KONST: + return col+print_reg(out, 0, regnum, MODE_KF, 0, func); + case REGT_STRING | REGT_KONST: + return col+print_reg(out, 0, regnum, MODE_KS, 0, func); + case REGT_POINTER | REGT_KONST: + return col+print_reg(out, 0, regnum, MODE_KP, 0, func); + case REGT_FLOAT | REGT_MULTIREG | REGT_KONST: + return col+print_reg(out, 0, regnum, MODE_KV, 0, func); + default: + if (regtype == REGT_NIL) + { + return col+printf_wrapper(out, "nil"); + } + return col+printf_wrapper(out, "param[t=%d,%c,%c,n=%d]", + regtype & REGT_TYPE, + regtype & REGT_KONST ? 'k' : 'r', + regtype & REGT_MULTIREG ? 'm' : 's', + regnum); + } + } + + default: + return col+printf_wrapper(out, "$%d", arg); + } + return col; +} + +//========================================================================== +// +// Do some postprocessing after everything has been defined +// +//========================================================================== + +void DumpFunction(FILE *dump, VMScriptFunction *sfunc, const char *label, int labellen) +{ + const char *marks = "======================================================="; + fprintf(dump, "\n%.*s %s %.*s", MAX(3, 38 - labellen / 2), marks, label, MAX(3, 38 - labellen / 2), marks); + fprintf(dump, "\nInteger regs: %-3d Float regs: %-3d Address regs: %-3d String regs: %-3d\nStack size: %d\n", + sfunc->NumRegD, sfunc->NumRegF, sfunc->NumRegA, sfunc->NumRegS, sfunc->MaxParam); + VMDumpConstants(dump, sfunc); + fprintf(dump, "\nDisassembly @ %p:\n", sfunc->Code); + VMDisasm(dump, sfunc->Code, sfunc->CodeSize, sfunc); +} + diff --git a/source/common/scripting/interface/stringformat.cpp b/source/common/scripting/interface/stringformat.cpp new file mode 100644 index 000000000..589698ee2 --- /dev/null +++ b/source/common/scripting/interface/stringformat.cpp @@ -0,0 +1,278 @@ +/* +** thingdef_data.cpp +** +** DECORATE data tables +** +**--------------------------------------------------------------------------- +** Copyright 2002-2008 Christoph Oelckers +** Copyright 2004-2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be +** covered by the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or (at +** your option) any later version. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "zstring.h" +#include "vm.h" +#include "gstrings.h" +#include "v_font.h" +#include "types.h" + + + +FString FStringFormat(VM_ARGS, int offset) +{ + PARAM_VA_POINTER(va_reginfo) // Get the hidden type information array + assert(va_reginfo[offset] == REGT_STRING); + + FString fmtstring = param[offset].s().GetChars(); + + param += offset; + numparam -= offset; + va_reginfo += offset; + + // note: we don't need a real printf format parser. + // enough to simply find the subtitution tokens and feed them to the real printf after checking types. + // https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification + FString output; + bool in_fmt = false; + FString fmt_current; + int argnum = 1; + int argauto = 1; + // % = starts + // [0-9], -, +, \s, 0, #, . continue + // %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate + // various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM. + // the only combination that is parsed locally is %n$... + bool haveargnums = false; + for (size_t i = 0; i < fmtstring.Len(); i++) + { + char c = fmtstring[i]; + if (in_fmt) + { + if (c == '*' && (fmt_current.Len() == 1 || (fmt_current.Len() == 2 && fmt_current[1] == '0'))) + { + fmt_current += c; + } + else if ((c >= '0' && c <= '9') || + c == '-' || c == '+' || (c == ' ' && fmt_current.Back() != ' ') || c == '#' || c == '.') + { + fmt_current += c; + } + else if (c == '$') // %number$format + { + if (!haveargnums && argauto > 1) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + FString argnumstr = fmt_current.Mid(1); + if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars()); + auto argnum64 = argnumstr.ToLong(); + if (argnum64 < 1 || argnum64 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum64, numparam); + fmt_current = "%"; + haveargnums = true; + argnum = int(argnum64); + } + else + { + fmt_current += c; + + switch (c) + { + // string + case 's': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (va_reginfo[argnum] != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].s().GetChars()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // pointer + case 'p': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (va_reginfo[argnum] != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].a); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // int formats (including char) + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'c': + case 'B': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // append + if (fmt_current[1] == '*' || fmt_current[2] == '*') + { + // fail if something was found, but it's not an int + if (argnum+1 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (va_reginfo[argnum] != REGT_INT && + va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + if (va_reginfo[argnum+1] != REGT_INT && + va_reginfo[argnum+1] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + + output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]), param[argnum + 1].ToInt(va_reginfo[argnum + 1])); + argauto++; + } + else + { + // fail if something was found, but it's not an int + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (va_reginfo[argnum] != REGT_INT && + va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum])); + } + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // double formats + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + if (fmt_current[1] == '*' || fmt_current[2] == '*') + { + // fail if something was found, but it's not an int + if (argnum + 1 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (va_reginfo[argnum] != REGT_INT && + va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + if (va_reginfo[argnum + 1] != REGT_INT && + va_reginfo[argnum + 1] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + + output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]), param[argnum + 1].ToDouble(va_reginfo[argnum + 1])); + argauto++; + } + else + { + // fail if something was found, but it's not a float + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (va_reginfo[argnum] != REGT_INT && + va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), param[argnum].ToDouble(va_reginfo[argnum])); + } + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + default: + // invalid character + output += fmt_current; + in_fmt = false; + break; + } + } + } + else + { + if (c == '%') + { + if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%') + { + output += '%'; + i++; + } + else + { + in_fmt = true; + fmt_current = "%"; + } + } + else + { + output += c; + } + } + } + + return output; +} + +DEFINE_ACTION_FUNCTION(FStringStruct, Format) +{ + PARAM_PROLOGUE; + FString s = FStringFormat(VM_ARGS_NAMES); + ACTION_RETURN_STRING(s); +} + +DEFINE_ACTION_FUNCTION(FStringStruct, AppendFormat) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + // first parameter is the self pointer + FString s = FStringFormat(VM_ARGS_NAMES, 1); + (*self) += s; + return 0; +} + +DEFINE_ACTION_FUNCTION(FStringStruct, AppendCharacter) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + PARAM_INT(c); + self->AppendCharacter(c); + return 0; +} + +DEFINE_ACTION_FUNCTION(FStringStruct, DeleteLastCharacter) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + self->DeleteLastCharacter(); + return 0; +} diff --git a/source/common/scripting/jit/jit.cpp b/source/common/scripting/jit/jit.cpp new file mode 100644 index 000000000..aa48585af --- /dev/null +++ b/source/common/scripting/jit/jit.cpp @@ -0,0 +1,564 @@ + +#include "jit.h" +#include "jitintern.h" +#include "printf.h" + +extern PString *TypeString; +extern PStruct *TypeVector2; +extern PStruct *TypeVector3; + +static void OutputJitLog(const asmjit::StringLogger &logger); + +JitFuncPtr JitCompile(VMScriptFunction *sfunc) +{ +#if 0 + if (strcmp(sfunc->PrintableName.GetChars(), "StatusScreen.drawNum") != 0) + return nullptr; +#endif + + using namespace asmjit; + StringLogger logger; + try + { + ThrowingErrorHandler errorHandler; + CodeHolder code; + code.init(GetHostCodeInfo()); + code.setErrorHandler(&errorHandler); + code.setLogger(&logger); + + JitCompiler compiler(&code, sfunc); + return reinterpret_cast(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()); + 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(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(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(&JitCompiler::ThrowException); + call->setArg(0, asmjit::imm(reason)); +} + +asmjit::Label JitCompiler::EmitThrowExceptionLabel(EVMAbortException reason) +{ + auto label = cc.newLabel(); + auto cursor = cc.getCursor(); + cc.bind(label); + EmitThrowException(reason); + cc.setCursor(cursor); + + JitLineInfo info; + info.Label = label; + info.LineNumber = sfunc->PCToLine(pc); + LineInfo.Push(info); + + return label; +} + +asmjit::X86Gp JitCompiler::CheckRegD(int r0, int r1) +{ + if (r0 != r1) + { + return regD[r0]; + } + else + { + auto copy = newTempInt32(); + cc.mov(copy, regD[r0]); + return copy; + } +} + +asmjit::X86Xmm JitCompiler::CheckRegF(int r0, int r1) +{ + if (r0 != r1) + { + return regF[r0]; + } + else + { + auto copy = newTempXmmSd(); + cc.movsd(copy, regF[r0]); + return copy; + } +} + +asmjit::X86Xmm JitCompiler::CheckRegF(int r0, int r1, int r2) +{ + if (r0 != r1 && r0 != r2) + { + return regF[r0]; + } + else + { + auto copy = newTempXmmSd(); + cc.movsd(copy, regF[r0]); + return copy; + } +} + +asmjit::X86Xmm JitCompiler::CheckRegF(int r0, int r1, int r2, int r3) +{ + if (r0 != r1 && r0 != r2 && r0 != r3) + { + return regF[r0]; + } + else + { + auto copy = newTempXmmSd(); + cc.movsd(copy, regF[r0]); + return copy; + } +} + +asmjit::X86Gp JitCompiler::CheckRegS(int r0, int r1) +{ + if (r0 != r1) + { + return regS[r0]; + } + else + { + auto copy = newTempIntPtr(); + cc.mov(copy, regS[r0]); + return copy; + } +} + +asmjit::X86Gp JitCompiler::CheckRegA(int r0, int r1) +{ + if (r0 != r1) + { + return regA[r0]; + } + else + { + auto copy = newTempIntPtr(); + cc.mov(copy, regA[r0]); + return copy; + } +} + +void JitCompiler::EmitNOP() +{ + cc.nop(); +} diff --git a/source/common/scripting/jit/jit.h b/source/common/scripting/jit/jit.h new file mode 100644 index 000000000..faaf65112 --- /dev/null +++ b/source/common/scripting/jit/jit.h @@ -0,0 +1,8 @@ + +#pragma once + +#include "vmintern.h" + +JitFuncPtr JitCompile(VMScriptFunction *func); +void JitDumpLog(FILE *file, VMScriptFunction *func); +FString JitCaptureStackTrace(int framesToSkip, bool includeNativeFrames); diff --git a/source/common/scripting/jit/jit_call.cpp b/source/common/scripting/jit/jit_call.cpp new file mode 100644 index 000000000..ee35fc139 --- /dev/null +++ b/source/common/scripting/jit/jit_call.cpp @@ -0,0 +1,690 @@ + +#include "jitintern.h" +#include +#include + +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(konsta[A].v); + + VMNativeFunction *ntarget = nullptr; + if (target && (target->VarFlags & VARF_Native)) + ntarget = static_cast(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()); + 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>> argsCache; + +asmjit::FuncSignature JitCompiler::CreateFuncSignature() +{ + using namespace asmjit; + + TArray 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::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::kTypeId); + key += "v"; + break; + case REGT_INT: + case REGT_INT | REGT_KONST: + args.Push(TypeIdOf::kTypeId); + key += "i"; + break; + case REGT_STRING: + case REGT_STRING | REGT_KONST: + args.Push(TypeIdOf::kTypeId); + key += "s"; + break; + case REGT_FLOAT: + case REGT_FLOAT | REGT_KONST: + args.Push(TypeIdOf::kTypeId); + key += "f"; + break; + case REGT_FLOAT | REGT_MULTIREG2: + args.Push(TypeIdOf::kTypeId); + args.Push(TypeIdOf::kTypeId); + key += "ff"; + break; + case REGT_FLOAT | REGT_MULTIREG3: + args.Push(TypeIdOf::kTypeId); + args.Push(TypeIdOf::kTypeId); + args.Push(TypeIdOf::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::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::kTypeId; + key += "ri"; + break; + case REGT_FLOAT: + rettype = TypeIdOf::kTypeId; + key += "rf"; + break; + case REGT_POINTER: + rettype = TypeIdOf::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::kTypeId); + key += "v"; + } + + // FuncSignature only keeps a pointer to its args array. Store a copy of each args array variant. + std::unique_ptr> &cachedArgs = argsCache[key]; + if (!cachedArgs) cachedArgs.reset(new TArray(args)); + + FuncSignature signature; + signature.init(CallConv::kIdHost, rettype, cachedArgs->Data(), cachedArgs->Size()); + return signature; +} diff --git a/source/common/scripting/jit/jit_flow.cpp b/source/common/scripting/jit/jit_flow.cpp new file mode 100644 index 000000000..5db91818a --- /dev/null +++ b/source/common/scripting/jit/jit_flow.cpp @@ -0,0 +1,319 @@ + +#include "jitintern.h" + +void JitCompiler::EmitTEST() +{ + int i = (int)(ptrdiff_t)(pc - sfunc->Code); + cc.cmp(regD[A], BC); + cc.jne(GetLabel(i + 2)); +} + +void JitCompiler::EmitTESTN() +{ + int bc = BC; + int i = (int)(ptrdiff_t)(pc - sfunc->Code); + cc.cmp(regD[A], -bc); + cc.jne(GetLabel(i + 2)); +} + +void JitCompiler::EmitJMP() +{ + auto dest = pc + JMPOFS(pc) + 1; + int i = (int)(ptrdiff_t)(dest - sfunc->Code); + cc.jmp(GetLabel(i)); +} + +void JitCompiler::EmitIJMP() +{ + int base = (int)(ptrdiff_t)(pc - sfunc->Code) + 1; + auto val = newTempInt32(); + cc.mov(val, regD[A]); + + for (int i = 0; i < (int)BCs; i++) + { + if (sfunc->Code[base +i].op == OP_JMP) + { + int target = base + i + JMPOFS(&sfunc->Code[base + i]) + 1; + + cc.cmp(val, i); + cc.je(GetLabel(target)); + } + } + pc += BCs; + + // This should never happen. It means we are jumping to something that is not a JMP instruction! + EmitThrowException(X_OTHER); +} + +static void ValidateCall(DObject *o, VMFunction *f, int b) +{ + FScopeBarrier::ValidateCall(o->GetClass(), f, b - 1); +} + +void JitCompiler::EmitSCOPE() +{ + auto label = EmitThrowExceptionLabel(X_READ_NIL); + cc.test(regA[A], regA[A]); + cc.jz(label); + + auto f = newTempIntPtr(); + cc.mov(f, asmjit::imm_ptr(konsta[C].v)); + + typedef int(*FuncPtr)(DObject*, VMFunction*, int); + auto call = CreateCall(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(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(&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(&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(&JitCompiler::ThrowArrayOutOfBounds); + call->setArg(0, regD[A]); + call->setArg(1, regD[B]); + cc.setCursor(cursor); + + cc.cmp(regD[A], regD[B]); + cc.jae(label); + + JitLineInfo info; + info.Label = label; + info.LineNumber = sfunc->PCToLine(pc); + LineInfo.Push(info); +} + +void JitCompiler::ThrowArrayOutOfBounds(int index, int size) +{ + if (index >= size) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", size, index); + } + else + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", index); + } +} diff --git a/source/common/scripting/jit/jit_load.cpp b/source/common/scripting/jit/jit_load.cpp new file mode 100644 index 000000000..0881cfd21 --- /dev/null +++ b/source/common/scripting/jit/jit_load.cpp @@ -0,0 +1,360 @@ + +#include "jitintern.h" + +///////////////////////////////////////////////////////////////////////////// +// Load constants. + +void JitCompiler::EmitLI() +{ + cc.mov(regD[A], BCs); +} + +void JitCompiler::EmitLK() +{ + cc.mov(regD[A], konstd[BC]); +} + +void JitCompiler::EmitLKF() +{ + auto base = newTempIntPtr(); + cc.mov(base, asmjit::imm_ptr(konstf + BC)); + cc.movsd(regF[A], asmjit::x86::qword_ptr(base)); +} + +void JitCompiler::EmitLKS() +{ + auto call = CreateCall(&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(&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(&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(&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(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(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(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(SetString); + call->setArg(0, regS[A]); + call->setArg(1, ptr); +} + +void JitCompiler::EmitLBIT() +{ + EmitNullPointerThrow(B, X_READ_NIL); + cc.movsx(regD[A], asmjit::x86::byte_ptr(regA[B])); + cc.and_(regD[A], C); + cc.cmp(regD[A], 0); + cc.setne(regD[A]); +} diff --git a/source/common/scripting/jit/jit_math.cpp b/source/common/scripting/jit/jit_math.cpp new file mode 100644 index 000000000..0b142563d --- /dev/null +++ b/source/common/scripting/jit/jit_math.cpp @@ -0,0 +1,1524 @@ + +#include "jitintern.h" +#include "basics.h" + +///////////////////////////////////////////////////////////////////////////// +// String instructions. + +static void ConcatString(FString* to, FString* first, FString* second) +{ + *to = *first + *second; +} + +void JitCompiler::EmitCONCAT() +{ + auto rc = CheckRegS(C, A); + auto call = CreateCall(ConcatString); + call->setArg(0, regS[A]); + call->setArg(1, regS[B]); + call->setArg(2, rc); +} + +static int StringLength(FString* str) +{ + return static_cast(str->Len()); +} + +void JitCompiler::EmitLENS() +{ + auto result = newResultInt32(); + auto call = CreateCall(StringLength); + call->setRet(0, result); + call->setArg(0, regS[B]); + cc.mov(regD[A], result); +} + +static int StringCompareNoCase(FString* first, FString* second) +{ + return first->CompareNoCase(*second); +} + +static int StringCompare(FString* first, FString* second) +{ + return first->Compare(*second); +} + +void JitCompiler::EmitCMPS() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + + auto call = CreateCall(static_cast(A & CMP_APPROX) ? StringCompareNoCase : StringCompare); + + auto result = newResultInt32(); + call->setRet(0, result); + + if (static_cast(A & CMP_BK)) call->setArg(0, asmjit::imm_ptr(&konsts[B])); + else call->setArg(0, regS[B]); + + if (static_cast(A & CMP_CK)) call->setArg(1, asmjit::imm_ptr(&konsts[C])); + else call->setArg(1, regS[C]); + + int method = A & CMP_METHOD_MASK; + if (method == CMP_EQ) { + cc.test(result, result); + if (check) cc.jz(fail); + else cc.jnz(fail); + } + else if (method == CMP_LT) { + cc.cmp(result, 0); + if (check) cc.jl(fail); + else cc.jnl(fail); + } + else { + cc.cmp(result, 0); + if (check) cc.jle(fail); + else cc.jnle(fail); + } + }); +} + +///////////////////////////////////////////////////////////////////////////// +// Integer math. + +void JitCompiler::EmitSLL_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.shl(regD[A], rc); +} + +void JitCompiler::EmitSLL_RI() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.shl(regD[A], C); +} + +void JitCompiler::EmitSLL_KR() +{ + auto rc = CheckRegD(C, A); + cc.mov(regD[A], konstd[B]); + cc.shl(regD[A], rc); +} + +void JitCompiler::EmitSRL_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.shr(regD[A], rc); +} + +void JitCompiler::EmitSRL_RI() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.shr(regD[A], C); +} + +void JitCompiler::EmitSRL_KR() +{ + auto rc = CheckRegD(C, A); + cc.mov(regD[A], konstd[B]); + cc.shr(regD[A], rc); +} + +void JitCompiler::EmitSRA_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.sar(regD[A], rc); +} + +void JitCompiler::EmitSRA_RI() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.sar(regD[A], C); +} + +void JitCompiler::EmitSRA_KR() +{ + auto rc = CheckRegD(C, A); + cc.mov(regD[A], konstd[B]); + cc.sar(regD[A], rc); +} + +void JitCompiler::EmitADD_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.add(regD[A], rc); +} + +void JitCompiler::EmitADD_RK() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.add(regD[A], konstd[C]); +} + +void JitCompiler::EmitADDI() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.add(regD[A], Cs); +} + +void JitCompiler::EmitSUB_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.sub(regD[A], rc); +} + +void JitCompiler::EmitSUB_RK() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.sub(regD[A], konstd[C]); +} + +void JitCompiler::EmitSUB_KR() +{ + auto rc = CheckRegD(C, A); + cc.mov(regD[A], konstd[B]); + cc.sub(regD[A], rc); +} + +void JitCompiler::EmitMUL_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.imul(regD[A], rc); +} + +void JitCompiler::EmitMUL_RK() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.imul(regD[A], konstd[C]); +} + +void JitCompiler::EmitDIV_RR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, regD[B]); + cc.cdq(tmp1, tmp0); + cc.idiv(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp0); +} + +void JitCompiler::EmitDIV_RK() +{ + if (konstd[C] != 0) + { + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + auto konstTmp = newTempIntPtr(); + cc.mov(tmp0, regD[B]); + cc.cdq(tmp1, tmp0); + cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C])); + cc.idiv(tmp1, tmp0, asmjit::x86::ptr(konstTmp)); + cc.mov(regD[A], tmp0); + } + else + { + EmitThrowException(X_DIVISION_BY_ZERO); + } +} + +void JitCompiler::EmitDIV_KR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, konstd[B]); + cc.cdq(tmp1, tmp0); + cc.idiv(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp0); +} + +void JitCompiler::EmitDIVU_RR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, regD[B]); + cc.mov(tmp1, 0); + cc.div(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp0); +} + +void JitCompiler::EmitDIVU_RK() +{ + if (konstd[C] != 0) + { + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + auto konstTmp = newTempIntPtr(); + cc.mov(tmp0, regD[B]); + cc.mov(tmp1, 0); + cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C])); + cc.div(tmp1, tmp0, asmjit::x86::ptr(konstTmp)); + cc.mov(regD[A], tmp0); + } + else + { + EmitThrowException(X_DIVISION_BY_ZERO); + } +} + +void JitCompiler::EmitDIVU_KR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, konstd[B]); + cc.mov(tmp1, 0); + cc.div(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp0); +} + +void JitCompiler::EmitMOD_RR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, regD[B]); + cc.cdq(tmp1, tmp0); + cc.idiv(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp1); +} + +void JitCompiler::EmitMOD_RK() +{ + if (konstd[C] != 0) + { + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + auto konstTmp = newTempIntPtr(); + cc.mov(tmp0, regD[B]); + cc.cdq(tmp1, tmp0); + cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C])); + cc.idiv(tmp1, tmp0, asmjit::x86::ptr(konstTmp)); + cc.mov(regD[A], tmp1); + } + else + { + EmitThrowException(X_DIVISION_BY_ZERO); + } +} + +void JitCompiler::EmitMOD_KR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, konstd[B]); + cc.cdq(tmp1, tmp0); + cc.idiv(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp1); +} + +void JitCompiler::EmitMODU_RR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, regD[B]); + cc.mov(tmp1, 0); + cc.div(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp1); +} + +void JitCompiler::EmitMODU_RK() +{ + if (konstd[C] != 0) + { + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + auto konstTmp = newTempIntPtr(); + cc.mov(tmp0, regD[B]); + cc.mov(tmp1, 0); + cc.mov(konstTmp, asmjit::imm_ptr(&konstd[C])); + cc.div(tmp1, tmp0, asmjit::x86::ptr(konstTmp)); + cc.mov(regD[A], tmp1); + } + else + { + EmitThrowException(X_DIVISION_BY_ZERO); + } +} + +void JitCompiler::EmitMODU_KR() +{ + auto tmp0 = newTempInt32(); + auto tmp1 = newTempInt32(); + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + cc.test(regD[C], regD[C]); + cc.je(label); + + cc.mov(tmp0, konstd[B]); + cc.mov(tmp1, 0); + cc.div(tmp1, tmp0, regD[C]); + cc.mov(regD[A], tmp1); +} + +void JitCompiler::EmitAND_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.and_(regD[A], rc); +} + +void JitCompiler::EmitAND_RK() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.and_(regD[A], konstd[C]); +} + +void JitCompiler::EmitOR_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.or_(regD[A], rc); +} + +void JitCompiler::EmitOR_RK() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.or_(regD[A], konstd[C]); +} + +void JitCompiler::EmitXOR_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.xor_(regD[A], rc); +} + +void JitCompiler::EmitXOR_RK() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.xor_(regD[A], konstd[C]); +} + +void JitCompiler::EmitMIN_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.cmp(rc, regD[A]); + cc.cmovl(regD[A], rc); +} + +void JitCompiler::EmitMIN_RK() +{ + auto rc = newTempInt32(); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.mov(rc, asmjit::imm(konstd[C])); + cc.cmp(rc, regD[A]); + cc.cmovl(regD[A], rc); +} + +void JitCompiler::EmitMAX_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.cmp(rc, regD[A]); + cc.cmovg(regD[A], rc); +} + +void JitCompiler::EmitMAX_RK() +{ + auto rc = newTempInt32(); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.mov(rc, asmjit::imm(konstd[C])); + cc.cmp(rc, regD[A]); + cc.cmovg(regD[A], rc); +} + +void JitCompiler::EmitMINU_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.cmp(rc, regD[A]); + cc.cmovb(regD[A], rc); +} + +void JitCompiler::EmitMINU_RK() +{ + auto rc = newTempInt32(); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.mov(rc, asmjit::imm(konstd[C])); + cc.cmp(rc, regD[A]); + cc.cmovb(regD[A], rc); +} + +void JitCompiler::EmitMAXU_RR() +{ + auto rc = CheckRegD(C, A); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.cmp(rc, regD[A]); + cc.cmova(regD[A], rc); +} + +void JitCompiler::EmitMAXU_RK() +{ + auto rc = newTempInt32(); + if (A != B) + cc.mov(regD[A], regD[B]); + cc.mov(rc, asmjit::imm(konstd[C])); + cc.cmp(rc, regD[A]); + cc.cmova(regD[A], rc); +} + +void JitCompiler::EmitABS() +{ + auto srcB = CheckRegD(B, A); + auto tmp = newTempInt32(); + cc.mov(tmp, regD[B]); + cc.sar(tmp, 31); + cc.mov(regD[A], tmp); + cc.xor_(regD[A], srcB); + cc.sub(regD[A], tmp); +} + +void JitCompiler::EmitNEG() +{ + auto srcB = CheckRegD(B, A); + cc.xor_(regD[A], regD[A]); + cc.sub(regD[A], srcB); +} + +void JitCompiler::EmitNOT() +{ + if (A != B) + cc.mov(regD[A], regD[B]); + cc.not_(regD[A]); +} + +void JitCompiler::EmitEQ_R() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], regD[C]); + if (check) cc.je(fail); + else cc.jne(fail); + }); +} + +void JitCompiler::EmitEQ_K() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], konstd[C]); + if (check) cc.je(fail); + else cc.jne(fail); + }); +} + +void JitCompiler::EmitLT_RR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], regD[C]); + if (check) cc.jl(fail); + else cc.jnl(fail); + }); +} + +void JitCompiler::EmitLT_RK() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], konstd[C]); + if (check) cc.jl(fail); + else cc.jnl(fail); + }); +} + +void JitCompiler::EmitLT_KR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstd[B])); + cc.cmp(asmjit::x86::ptr(tmp), regD[C]); + if (check) cc.jl(fail); + else cc.jnl(fail); + }); +} + +void JitCompiler::EmitLE_RR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], regD[C]); + if (check) cc.jle(fail); + else cc.jnle(fail); + }); +} + +void JitCompiler::EmitLE_RK() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], konstd[C]); + if (check) cc.jle(fail); + else cc.jnle(fail); + }); +} + +void JitCompiler::EmitLE_KR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstd[B])); + cc.cmp(asmjit::x86::ptr(tmp), regD[C]); + if (check) cc.jle(fail); + else cc.jnle(fail); + }); +} + +void JitCompiler::EmitLTU_RR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], regD[C]); + if (check) cc.jb(fail); + else cc.jnb(fail); + }); +} + +void JitCompiler::EmitLTU_RK() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], konstd[C]); + if (check) cc.jb(fail); + else cc.jnb(fail); + }); +} + +void JitCompiler::EmitLTU_KR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstd[B])); + cc.cmp(asmjit::x86::ptr(tmp), regD[C]); + if (check) cc.jb(fail); + else cc.jnb(fail); + }); +} + +void JitCompiler::EmitLEU_RR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], regD[C]); + if (check) cc.jbe(fail); + else cc.jnbe(fail); + }); +} + +void JitCompiler::EmitLEU_RK() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regD[B], konstd[C]); + if (check) cc.jbe(fail); + else cc.jnbe(fail); + }); +} + +void JitCompiler::EmitLEU_KR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstd[B])); + cc.cmp(asmjit::x86::ptr(tmp), regD[C]); + if (check) cc.jbe(fail); + else cc.jnbe(fail); + }); +} + +///////////////////////////////////////////////////////////////////////////// +// Double-precision floating point math. + +void JitCompiler::EmitADDF_RR() +{ + auto rc = CheckRegF(C, A); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.addsd(regF[A], rc); +} + +void JitCompiler::EmitADDF_RK() +{ + auto tmp = newTempIntPtr(); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.addsd(regF[A], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitSUBF_RR() +{ + auto rc = CheckRegF(C, A); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.subsd(regF[A], rc); +} + +void JitCompiler::EmitSUBF_RK() +{ + auto tmp = newTempIntPtr(); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.subsd(regF[A], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitSUBF_KR() +{ + auto rc = CheckRegF(C, A); + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[B])); + cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.subsd(regF[A], rc); +} + +void JitCompiler::EmitMULF_RR() +{ + auto rc = CheckRegF(C, A); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.mulsd(regF[A], rc); +} + +void JitCompiler::EmitMULF_RK() +{ + auto tmp = newTempIntPtr(); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitDIVF_RR() +{ + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + auto zero = newTempXmmSd(); + cc.xorpd(zero, zero); + cc.ucomisd(regF[C], zero); + cc.je(label); + + auto rc = CheckRegF(C, A); + cc.movsd(regF[A], regF[B]); + cc.divsd(regF[A], rc); +} + +void JitCompiler::EmitDIVF_RK() +{ + if (konstf[C] == 0.) + { + EmitThrowException(X_DIVISION_BY_ZERO); + } + else + { + auto tmp = newTempIntPtr(); + cc.movsd(regF[A], regF[B]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.divsd(regF[A], asmjit::x86::qword_ptr(tmp)); + } +} + +void JitCompiler::EmitDIVF_KR() +{ + auto rc = CheckRegF(C, A); + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[B])); + cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.divsd(regF[A], rc); +} + +static double DoubleModF(double a, double b) +{ + return a - floor(a / b) * b; +} + +void JitCompiler::EmitMODF_RR() +{ + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + auto zero = newTempXmmSd(); + cc.xorpd(zero, zero); + cc.ucomisd(regF[C], zero); + cc.je(label); + + auto result = newResultXmmSd(); + auto call = CreateCall(DoubleModF); + call->setRet(0, result); + call->setArg(0, regF[B]); + call->setArg(1, regF[C]); + cc.movsd(regF[A], result); +} + +void JitCompiler::EmitMODF_RK() +{ + if (konstf[C] == 0.) + { + EmitThrowException(X_DIVISION_BY_ZERO); + } + else + { + auto tmpPtr = newTempIntPtr(); + cc.mov(tmpPtr, asmjit::imm_ptr(&konstf[C])); + + auto tmp = newTempXmmSd(); + cc.movsd(tmp, asmjit::x86::qword_ptr(tmpPtr)); + + auto result = newResultXmmSd(); + auto call = CreateCall(DoubleModF); + call->setRet(0, result); + call->setArg(0, regF[B]); + call->setArg(1, tmp); + cc.movsd(regF[A], result); + } +} + +void JitCompiler::EmitMODF_KR() +{ + using namespace asmjit; + + auto label = EmitThrowExceptionLabel(X_DIVISION_BY_ZERO); + auto zero = newTempXmmSd(); + cc.xorpd(zero, zero); + cc.ucomisd(regF[C], zero); + cc.je(label); + + auto tmp = newTempXmmSd(); + cc.movsd(tmp, x86::ptr(ToMemAddress(&konstf[B]))); + + auto result = newResultXmmSd(); + auto call = CreateCall(DoubleModF); + call->setRet(0, result); + call->setArg(0, tmp); + call->setArg(1, regF[C]); + cc.movsd(regF[A], result); +} + +void JitCompiler::EmitPOWF_RR() +{ + auto result = newResultXmmSd(); + auto call = CreateCall(g_pow); + call->setRet(0, result); + call->setArg(0, regF[B]); + call->setArg(1, regF[C]); + cc.movsd(regF[A], result); +} + +void JitCompiler::EmitPOWF_RK() +{ + auto tmp = newTempIntPtr(); + auto tmp2 = newTempXmmSd(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp)); + + auto result = newResultXmmSd(); + auto call = CreateCall(g_pow); + call->setRet(0, result); + call->setArg(0, regF[B]); + call->setArg(1, tmp2); + cc.movsd(regF[A], result); +} + +void JitCompiler::EmitPOWF_KR() +{ + auto tmp = newTempIntPtr(); + auto tmp2 = newTempXmmSd(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[B])); + cc.movsd(tmp2, asmjit::x86::qword_ptr(tmp)); + + auto result = newResultXmmSd(); + auto call = CreateCall(g_pow); + call->setRet(0, result); + call->setArg(0, tmp2); + call->setArg(1, regF[C]); + cc.movsd(regF[A], result); +} + +void JitCompiler::EmitMINF_RR() +{ + auto rc = CheckRegF(C, A); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.minpd(regF[A], rc); // minsd requires SSE 4.1 +} + +void JitCompiler::EmitMINF_RK() +{ + auto rb = CheckRegF(B, A); + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.minpd(regF[A], rb); // minsd requires SSE 4.1 +} + +void JitCompiler::EmitMAXF_RR() +{ + auto rc = CheckRegF(C, A); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.maxpd(regF[A], rc); // maxsd requires SSE 4.1 +} + +void JitCompiler::EmitMAXF_RK() +{ + auto rb = CheckRegF(B, A); + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.movsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.maxpd(regF[A], rb); // maxsd requires SSE 4.1 +} + +void JitCompiler::EmitATAN2() +{ + auto result = newResultXmmSd(); + auto call = CreateCall(g_atan2); + call->setRet(0, result); + call->setArg(0, regF[B]); + call->setArg(1, regF[C]); + cc.movsd(regF[A], result); + + static const double constant = 180 / M_PI; + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&constant)); + cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitFLOP() +{ + if (C == FLOP_NEG) + { + auto mask = cc.newDoubleConst(asmjit::kConstScopeLocal, -0.0); + auto maskXmm = newTempXmmSd(); + cc.movsd(maskXmm, mask); + if (A != B) + cc.movsd(regF[A], regF[B]); + cc.xorpd(regF[A], maskXmm); + } + else + { + auto v = newTempXmmSd(); + cc.movsd(v, regF[B]); + + if (C == FLOP_TAN_DEG) + { + static const double constant = M_PI / 180; + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&constant)); + cc.mulsd(v, asmjit::x86::qword_ptr(tmp)); + } + + typedef double(*FuncPtr)(double); + FuncPtr func = nullptr; + switch (C) + { + default: I_Error("Unknown OP_FLOP subfunction"); + case FLOP_ABS: func = fabs; break; + case FLOP_EXP: func = g_exp; break; + case FLOP_LOG: func = g_log; break; + case FLOP_LOG10: func = g_log10; break; + case FLOP_SQRT: func = g_sqrt; break; + case FLOP_CEIL: func = ceil; break; + case FLOP_FLOOR: func = floor; break; + case FLOP_ACOS: func = g_acos; break; + case FLOP_ASIN: func = g_asin; break; + case FLOP_ATAN: func = g_atan; break; + case FLOP_COS: func = g_cos; break; + case FLOP_SIN: func = g_sin; break; + case FLOP_TAN: func = g_tan; break; + case FLOP_ACOS_DEG: func = g_acos; break; + case FLOP_ASIN_DEG: func = g_asin; break; + case FLOP_ATAN_DEG: func = g_atan; break; + case FLOP_COS_DEG: func = g_cosdeg; break; + case FLOP_SIN_DEG: func = g_sindeg; break; + case FLOP_TAN_DEG: func = g_tan; break; + case FLOP_COSH: func = g_cosh; break; + case FLOP_SINH: func = g_sinh; break; + case FLOP_TANH: func = g_tanh; break; + case FLOP_ROUND: func = round; break; + } + + auto result = newResultXmmSd(); + auto call = CreateCall(func); + call->setRet(0, result); + call->setArg(0, v); + cc.movsd(regF[A], result); + + if (C == FLOP_ACOS_DEG || C == FLOP_ASIN_DEG || C == FLOP_ATAN_DEG) + { + static const double constant = 180 / M_PI; + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&constant)); + cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp)); + } + } +} + +void JitCompiler::EmitEQF_R() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + bool approx = static_cast(A & CMP_APPROX); + if (!approx) + { + cc.ucomisd(regF[B], regF[C]); + if (check) { + cc.jp(success); + cc.je(fail); + } + else { + cc.jp(fail); + cc.jne(fail); + } + } + else + { + auto tmp = newTempXmmSd(); + + const int64_t absMaskInt = 0x7FFFFFFFFFFFFFFF; + auto absMask = cc.newDoubleConst(asmjit::kConstScopeLocal, reinterpret_cast(absMaskInt)); + auto absMaskXmm = newTempXmmPd(); + + auto epsilon = cc.newDoubleConst(asmjit::kConstScopeLocal, VM_EPSILON); + auto epsilonXmm = newTempXmmSd(); + + cc.movsd(tmp, regF[B]); + cc.subsd(tmp, regF[C]); + cc.movsd(absMaskXmm, absMask); + cc.andpd(tmp, absMaskXmm); + cc.movsd(epsilonXmm, epsilon); + cc.ucomisd(epsilonXmm, tmp); + + if (check) cc.ja(fail); + else cc.jna(fail); + } + }); +} + +void JitCompiler::EmitEQF_K() +{ + using namespace asmjit; + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + bool approx = static_cast(A & CMP_APPROX); + if (!approx) { + auto konstTmp = newTempIntPtr(); + cc.mov(konstTmp, asmjit::imm_ptr(&konstf[C])); + cc.ucomisd(regF[B], x86::qword_ptr(konstTmp)); + if (check) { + cc.jp(success); + cc.je(fail); + } + else { + cc.jp(fail); + cc.jne(fail); + } + } + else { + auto konstTmp = newTempIntPtr(); + auto subTmp = newTempXmmSd(); + + const int64_t absMaskInt = 0x7FFFFFFFFFFFFFFF; + auto absMask = cc.newDoubleConst(kConstScopeLocal, reinterpret_cast(absMaskInt)); + auto absMaskXmm = newTempXmmPd(); + + auto epsilon = cc.newDoubleConst(kConstScopeLocal, VM_EPSILON); + auto epsilonXmm = newTempXmmSd(); + + cc.mov(konstTmp, asmjit::imm_ptr(&konstf[C])); + + cc.movsd(subTmp, regF[B]); + cc.subsd(subTmp, x86::qword_ptr(konstTmp)); + cc.movsd(absMaskXmm, absMask); + cc.andpd(subTmp, absMaskXmm); + cc.movsd(epsilonXmm, epsilon); + cc.ucomisd(epsilonXmm, subTmp); + + if (check) cc.ja(fail); + else cc.jna(fail); + } + }); +} + +void JitCompiler::EmitLTF_RR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + if (static_cast(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LTF_RR.\n"); + + cc.ucomisd(regF[C], regF[B]); + if (check) cc.ja(fail); + else cc.jna(fail); + }); +} + +void JitCompiler::EmitLTF_RK() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + if (static_cast(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LTF_RK.\n"); + + auto constTmp = newTempIntPtr(); + auto xmmTmp = newTempXmmSd(); + cc.mov(constTmp, asmjit::imm_ptr(&konstf[C])); + cc.movsd(xmmTmp, asmjit::x86::qword_ptr(constTmp)); + + cc.ucomisd(xmmTmp, regF[B]); + if (check) cc.ja(fail); + else cc.jna(fail); + }); +} + +void JitCompiler::EmitLTF_KR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + if (static_cast(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LTF_KR.\n"); + + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[B])); + + cc.ucomisd(regF[C], asmjit::x86::qword_ptr(tmp)); + if (check) cc.ja(fail); + else cc.jna(fail); + }); +} + +void JitCompiler::EmitLEF_RR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + if (static_cast(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LEF_RR.\n"); + + cc.ucomisd(regF[C], regF[B]); + if (check) cc.jae(fail); + else cc.jnae(fail); + }); +} + +void JitCompiler::EmitLEF_RK() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + if (static_cast(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LEF_RK.\n"); + + auto constTmp = newTempIntPtr(); + auto xmmTmp = newTempXmmSd(); + cc.mov(constTmp, asmjit::imm_ptr(&konstf[C])); + cc.movsd(xmmTmp, asmjit::x86::qword_ptr(constTmp)); + + cc.ucomisd(xmmTmp, regF[B]); + if (check) cc.jae(fail); + else cc.jnae(fail); + }); +} + +void JitCompiler::EmitLEF_KR() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + if (static_cast(A & CMP_APPROX)) I_Error("CMP_APPROX not implemented for LEF_KR.\n"); + + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(&konstf[B])); + + cc.ucomisd(regF[C], asmjit::x86::qword_ptr(tmp)); + if (check) cc.jae(fail); + else cc.jnae(fail); + }); +} + +///////////////////////////////////////////////////////////////////////////// +// Vector math. (2D) + +void JitCompiler::EmitNEGV2() +{ + auto mask = cc.newDoubleConst(asmjit::kConstScopeLocal, -0.0); + auto maskXmm = newTempXmmSd(); + cc.movsd(maskXmm, mask); + cc.movsd(regF[A], regF[B]); + cc.xorpd(regF[A], maskXmm); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.xorpd(regF[A + 1], maskXmm); +} + +void JitCompiler::EmitADDV2_RR() +{ + auto rc0 = CheckRegF(C, A); + auto rc1 = CheckRegF(C + 1, A + 1); + cc.movsd(regF[A], regF[B]); + cc.addsd(regF[A], rc0); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.addsd(regF[A + 1], rc1); +} + +void JitCompiler::EmitSUBV2_RR() +{ + auto rc0 = CheckRegF(C, A); + auto rc1 = CheckRegF(C + 1, A + 1); + cc.movsd(regF[A], regF[B]); + cc.subsd(regF[A], rc0); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.subsd(regF[A + 1], rc1); +} + +void JitCompiler::EmitDOTV2_RR() +{ + auto rc0 = CheckRegF(C, A); + auto rc1 = CheckRegF(C + 1, A); + auto tmp = newTempXmmSd(); + cc.movsd(regF[A], regF[B]); + cc.mulsd(regF[A], rc0); + cc.movsd(tmp, regF[B + 1]); + cc.mulsd(tmp, rc1); + cc.addsd(regF[A], tmp); +} + +void JitCompiler::EmitMULVF2_RR() +{ + auto rc = CheckRegF(C, A, A + 1); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.mulsd(regF[A], rc); + cc.mulsd(regF[A + 1], rc); +} + +void JitCompiler::EmitMULVF2_RK() +{ + auto tmp = newTempIntPtr(); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.mulsd(regF[A + 1], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitDIVVF2_RR() +{ + auto rc = CheckRegF(C, A, A + 1); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.divsd(regF[A], rc); + cc.divsd(regF[A + 1], rc); +} + +void JitCompiler::EmitDIVVF2_RK() +{ + auto tmp = newTempIntPtr(); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.divsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.divsd(regF[A + 1], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitLENV2() +{ + auto rb0 = CheckRegF(B, A); + auto rb1 = CheckRegF(B + 1, A); + auto tmp = newTempXmmSd(); + cc.movsd(regF[A], regF[B]); + cc.mulsd(regF[A], rb0); + cc.movsd(tmp, rb1); + cc.mulsd(tmp, rb1); + cc.addsd(regF[A], tmp); + CallSqrt(regF[A], regF[A]); +} + +void JitCompiler::EmitEQV2_R() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + EmitVectorComparison<2> (check, fail, success); + }); +} + +void JitCompiler::EmitEQV2_K() +{ + I_Error("EQV2_K is not used."); +} + +///////////////////////////////////////////////////////////////////////////// +// Vector math. (3D) + +void JitCompiler::EmitNEGV3() +{ + auto mask = cc.newDoubleConst(asmjit::kConstScopeLocal, -0.0); + auto maskXmm = newTempXmmSd(); + cc.movsd(maskXmm, mask); + cc.movsd(regF[A], regF[B]); + cc.xorpd(regF[A], maskXmm); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.xorpd(regF[A + 1], maskXmm); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.xorpd(regF[A + 2], maskXmm); +} + +void JitCompiler::EmitADDV3_RR() +{ + auto rc0 = CheckRegF(C, A); + auto rc1 = CheckRegF(C + 1, A + 1); + auto rc2 = CheckRegF(C + 2, A + 2); + cc.movsd(regF[A], regF[B]); + cc.addsd(regF[A], rc0); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.addsd(regF[A + 1], rc1); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.addsd(regF[A + 2], rc2); +} + +void JitCompiler::EmitSUBV3_RR() +{ + auto rc0 = CheckRegF(C, A); + auto rc1 = CheckRegF(C + 1, A + 1); + auto rc2 = CheckRegF(C + 2, A + 2); + cc.movsd(regF[A], regF[B]); + cc.subsd(regF[A], rc0); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.subsd(regF[A + 1], rc1); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.subsd(regF[A + 2], rc2); +} + +void JitCompiler::EmitDOTV3_RR() +{ + auto rb1 = CheckRegF(B + 1, A); + auto rb2 = CheckRegF(B + 2, A); + auto rc0 = CheckRegF(C, A); + auto rc1 = CheckRegF(C + 1, A); + auto rc2 = CheckRegF(C + 2, A); + auto tmp = newTempXmmSd(); + cc.movsd(regF[A], regF[B]); + cc.mulsd(regF[A], rc0); + cc.movsd(tmp, rb1); + cc.mulsd(tmp, rc1); + cc.addsd(regF[A], tmp); + cc.movsd(tmp, rb2); + cc.mulsd(tmp, rc2); + cc.addsd(regF[A], tmp); +} + +void JitCompiler::EmitCROSSV_RR() +{ + auto tmp = newTempXmmSd(); + + auto a0 = CheckRegF(B, A); + auto a1 = CheckRegF(B + 1, A + 1); + auto a2 = CheckRegF(B + 2, A + 2); + auto b0 = CheckRegF(C, A); + auto b1 = CheckRegF(C + 1, A + 1); + auto b2 = CheckRegF(C + 2, A + 2); + + // r0 = a1b2 - a2b1 + cc.movsd(regF[A], a1); + cc.mulsd(regF[A], b2); + cc.movsd(tmp, a2); + cc.mulsd(tmp, b1); + cc.subsd(regF[A], tmp); + + // r1 = a2b0 - a0b2 + cc.movsd(regF[A + 1], a2); + cc.mulsd(regF[A + 1], b0); + cc.movsd(tmp, a0); + cc.mulsd(tmp, b2); + cc.subsd(regF[A + 1], tmp); + + // r2 = a0b1 - a1b0 + cc.movsd(regF[A + 2], a0); + cc.mulsd(regF[A + 2], b1); + cc.movsd(tmp, a1); + cc.mulsd(tmp, b0); + cc.subsd(regF[A + 2], tmp); +} + +void JitCompiler::EmitMULVF3_RR() +{ + auto rc = CheckRegF(C, A, A + 1, A + 2); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.mulsd(regF[A], rc); + cc.mulsd(regF[A + 1], rc); + cc.mulsd(regF[A + 2], rc); +} + +void JitCompiler::EmitMULVF3_RK() +{ + auto tmp = newTempIntPtr(); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.mulsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.mulsd(regF[A + 1], asmjit::x86::qword_ptr(tmp)); + cc.mulsd(regF[A + 2], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitDIVVF3_RR() +{ + auto rc = CheckRegF(C, A, A + 1, A + 2); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.divsd(regF[A], rc); + cc.divsd(regF[A + 1], rc); + cc.divsd(regF[A + 2], rc); +} + +void JitCompiler::EmitDIVVF3_RK() +{ + auto tmp = newTempIntPtr(); + cc.movsd(regF[A], regF[B]); + cc.movsd(regF[A + 1], regF[B + 1]); + cc.movsd(regF[A + 2], regF[B + 2]); + cc.mov(tmp, asmjit::imm_ptr(&konstf[C])); + cc.divsd(regF[A], asmjit::x86::qword_ptr(tmp)); + cc.divsd(regF[A + 1], asmjit::x86::qword_ptr(tmp)); + cc.divsd(regF[A + 2], asmjit::x86::qword_ptr(tmp)); +} + +void JitCompiler::EmitLENV3() +{ + auto rb1 = CheckRegF(B + 1, A); + auto rb2 = CheckRegF(B + 2, A); + auto tmp = newTempXmmSd(); + cc.movsd(regF[A], regF[B]); + cc.mulsd(regF[A], regF[B]); + cc.movsd(tmp, rb1); + cc.mulsd(tmp, rb1); + cc.addsd(regF[A], tmp); + cc.movsd(tmp, rb2); + cc.mulsd(tmp, rb2); + cc.addsd(regF[A], tmp); + CallSqrt(regF[A], regF[A]); +} + +void JitCompiler::EmitEQV3_R() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + EmitVectorComparison<3> (check, fail, success); + }); +} + +void JitCompiler::EmitEQV3_K() +{ + I_Error("EQV3_K is not used."); +} + +///////////////////////////////////////////////////////////////////////////// +// Pointer math. + +void JitCompiler::EmitADDA_RR() +{ + auto tmp = newTempIntPtr(); + auto label = cc.newLabel(); + + cc.mov(tmp, regA[B]); + + // Check if zero, the first operand is zero, if it is, don't add. + cc.cmp(tmp, 0); + cc.je(label); + + auto tmpptr = newTempIntPtr(); + cc.mov(tmpptr, regD[C]); + cc.add(tmp, tmpptr); + + cc.bind(label); + cc.mov(regA[A], tmp); +} + +void JitCompiler::EmitADDA_RK() +{ + auto tmp = newTempIntPtr(); + auto label = cc.newLabel(); + + cc.mov(tmp, regA[B]); + + // Check if zero, the first operand is zero, if it is, don't add. + cc.cmp(tmp, 0); + cc.je(label); + + cc.add(tmp, konstd[C]); + + cc.bind(label); + cc.mov(regA[A], tmp); +} + +void JitCompiler::EmitSUBA() +{ + auto tmp = newTempIntPtr(); + cc.mov(tmp, regA[B]); + cc.sub(tmp, regD[C]); + cc.mov(regA[A], tmp); +} + +void JitCompiler::EmitEQA_R() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + cc.cmp(regA[B], regA[C]); + if (check) cc.je(fail); + else cc.jne(fail); + }); +} + +void JitCompiler::EmitEQA_K() +{ + EmitComparisonOpcode([&](bool check, asmjit::Label& fail, asmjit::Label& success) { + auto tmp = newTempIntPtr(); + cc.mov(tmp, asmjit::imm_ptr(konsta[C].v)); + cc.cmp(regA[B], tmp); + if (check) cc.je(fail); + else cc.jne(fail); + }); +} + +void JitCompiler::CallSqrt(const asmjit::X86Xmm &a, const asmjit::X86Xmm &b) +{ + auto result = newResultXmmSd(); + auto call = CreateCall(g_sqrt); + call->setRet(0, result); + call->setArg(0, b); + cc.movsd(a, result); +} diff --git a/source/common/scripting/jit/jit_move.cpp b/source/common/scripting/jit/jit_move.cpp new file mode 100644 index 000000000..d6b870646 --- /dev/null +++ b/source/common/scripting/jit/jit_move.cpp @@ -0,0 +1,272 @@ + +#include "jitintern.h" +#include "v_video.h" +#include "s_soundinternal.h" +#include "texturemanager.h" + +void JitCompiler::EmitMOVE() +{ + cc.mov(regD[A], regD[B]); +} +void JitCompiler::EmitMOVEF() +{ + cc.movsd(regF[A], regF[B]); +} + +void JitCompiler::EmitMOVES() +{ + auto call = CreateCall(&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(CastI2S); + call->setArg(0, regS[A]); + call->setArg(1, regD[B]); + break; + case CAST_U2S: + call = CreateCall(CastU2S); + call->setArg(0, regS[A]); + call->setArg(1, regD[B]); + break; + case CAST_F2S: + call = CreateCall(CastF2S); + call->setArg(0, regS[A]); + call->setArg(1, regF[B]); + break; + case CAST_V22S: + call = CreateCall(CastV22S); + call->setArg(0, regS[A]); + call->setArg(1, regF[B]); + call->setArg(2, regF[B + 1]); + break; + case CAST_V32S: + call = CreateCall(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(CastP2S); + call->setArg(0, regS[A]); + call->setArg(1, regA[B]); + break; + case CAST_S2I: + resultD = newResultInt32(); + call = CreateCall(CastS2I); + call->setRet(0, resultD); + call->setArg(0, regS[B]); + cc.mov(regD[A], resultD); + break; + case CAST_S2F: + resultF = newResultXmmSd(); + call = CreateCall(CastS2F); + call->setRet(0, resultF); + call->setArg(0, regS[B]); + cc.movsd(regF[A], resultF); + break; + case CAST_S2N: + resultD = newResultInt32(); + call = CreateCall(CastS2N); + call->setRet(0, resultD); + call->setArg(0, regS[B]); + cc.mov(regD[A], resultD); + break; + case CAST_N2S: + call = CreateCall(CastN2S); + call->setArg(0, regS[A]); + call->setArg(1, regD[B]); + break; + case CAST_S2Co: + resultD = newResultInt32(); + call = CreateCall(CastS2Co); + call->setRet(0, resultD); + call->setArg(0, regS[B]); + cc.mov(regD[A], resultD); + break; + case CAST_Co2S: + call = CreateCall(CastCo2S); + call->setArg(0, regS[A]); + call->setArg(1, regD[B]); + break; + case CAST_S2So: + resultD = newResultInt32(); + call = CreateCall(CastS2So); + call->setRet(0, resultD); + call->setArg(0, regS[B]); + cc.mov(regD[A], resultD); + break; + case CAST_So2S: + call = CreateCall(CastSo2S); + call->setArg(0, regS[A]); + call->setArg(1, regD[B]); + break; + case CAST_SID2S: + call = CreateCall(CastSID2S); + call->setArg(0, regS[A]); + call->setArg(1, regD[B]); + break; + case CAST_TID2S: + call = CreateCall(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(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(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(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(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(DynCastC); + call->setRet(0, result); + call->setArg(0, regA[B]); + call->setArg(1, c); + cc.mov(regA[A], result); +} diff --git a/source/common/scripting/jit/jit_runtime.cpp b/source/common/scripting/jit/jit_runtime.cpp new file mode 100644 index 000000000..b0a9dc488 --- /dev/null +++ b/source/common/scripting/jit/jit_runtime.cpp @@ -0,0 +1,970 @@ + +#include +#include "jit.h" +#include "jitintern.h" + +#ifdef WIN32 +#include +#else +#include +#include +#include +#include +#include +#endif + +struct JitFuncInfo +{ + FString name; + FString filename; + TArray LineInfo; + void *start; + void *end; +}; + +static TArray JitDebugInfo; +static TArray JitBlocks; +static TArray 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 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 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(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(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 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 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 &stream, unsigned int pos, unsigned int v) +{ + *(uint32_t*)(&stream[pos]) = v; +} + +static void WriteUInt64(TArray &stream, uint64_t v) +{ + for (int i = 0; i < 8; i++) + stream.Push((v >> (i * 8)) & 0xff); +} + +static void WriteUInt32(TArray &stream, uint32_t v) +{ + for (int i = 0; i < 4; i++) + stream.Push((v >> (i * 8)) & 0xff); +} + +static void WriteUInt16(TArray &stream, uint16_t v) +{ + for (int i = 0; i < 2; i++) + stream.Push((v >> (i * 8)) & 0xff); +} + +static void WriteUInt8(TArray &stream, uint8_t v) +{ + stream.Push(v); +} + +static void WriteULEB128(TArray &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 &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 &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 &stream, const TArray &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 &stream, const TArray &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 &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 &cieInstructions, int dwarfRegId, int stackOffset) +{ + WriteUInt8(cieInstructions, 0x0c); // DW_CFA_def_cfa + WriteULEB128(cieInstructions, dwarfRegId); + WriteULEB128(cieInstructions, stackOffset); +} + +static void WriteDefineStackOffset(TArray &fdeInstructions, int stackOffset) +{ + WriteUInt8(fdeInstructions, 0x0e); // DW_CFA_def_cfa_offset + WriteULEB128(fdeInstructions, stackOffset); +} + +static void WriteRegisterStackLocation(TArray &instructions, int dwarfRegId, int stackLocation) +{ + WriteUInt8(instructions, (2 << 6) | dwarfRegId); // DW_CFA_offset + WriteULEB128(instructions, stackLocation); +} + +static TArray 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::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 cieInstructions; + TArray 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(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(vecSize)); + + WriteAdvanceLoc(fdeInstructions, assembler.getOffset(), lastOffset); + WriteRegisterStackLocation(fdeInstructions, dwarfRegXmmId + regId, stackOffset - vecOffset); + vecOffset += static_cast(vecSize); + } + } + + TArray 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 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(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 nativeSymbols; + if (includeNativeFrames) + nativeSymbols.reset(new NativeSymbolResolver()); + + FString s; + for (int i = framesToSkip + 1; i < numframes; i++) + { + s += JitGetStackFrameName(nativeSymbols.get(), frames[i]); + } + return s; +} diff --git a/source/common/scripting/jit/jit_store.cpp b/source/common/scripting/jit/jit_store.cpp new file mode 100644 index 000000000..6dc1a45a9 --- /dev/null +++ b/source/common/scripting/jit/jit_store.cpp @@ -0,0 +1,176 @@ + +#include "jitintern.h" + +void JitCompiler::EmitSB() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::byte_ptr(regA[A], konstd[C]), regD[B].r8Lo()); +} + +void JitCompiler::EmitSB_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::byte_ptr(regA[A], regD[C]), regD[B].r8Lo()); +} + +void JitCompiler::EmitSH() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::word_ptr(regA[A], konstd[C]), regD[B].r16()); +} + +void JitCompiler::EmitSH_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::word_ptr(regA[A], regD[C]), regD[B].r16()); +} + +void JitCompiler::EmitSW() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::dword_ptr(regA[A], konstd[C]), regD[B]); +} + +void JitCompiler::EmitSW_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::dword_ptr(regA[A], regD[C]), regD[B]); +} + +void JitCompiler::EmitSSP() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp = newTempXmmSd(); + cc.xorpd(tmp, tmp); + cc.cvtsd2ss(tmp, regF[B]); + cc.movss(asmjit::x86::dword_ptr(regA[A], konstd[C]), tmp); +} + +void JitCompiler::EmitSSP_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp = newTempXmmSd(); + cc.xorpd(tmp, tmp); + cc.cvtsd2ss(tmp, regF[B]); + cc.movss(asmjit::x86::dword_ptr(regA[A], regD[C]), tmp); +} + +void JitCompiler::EmitSDP() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.movsd(asmjit::x86::qword_ptr(regA[A], konstd[C]), regF[B]); +} + +void JitCompiler::EmitSDP_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.movsd(asmjit::x86::qword_ptr(regA[A], regD[C]), regF[B]); +} + +void JitCompiler::EmitSS() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto ptr = newTempIntPtr(); + cc.lea(ptr, asmjit::x86::ptr(regA[A], konstd[C])); + auto call = CreateCall(&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(&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(static_cast(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(static_cast(GC::WriteBarrier)); + call->setArg(0, regA[B]); +} + +void JitCompiler::EmitSP() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::ptr(regA[A], konstd[C]), regA[B]); +} + +void JitCompiler::EmitSP_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + cc.mov(asmjit::x86::ptr(regA[A], regD[C]), regA[B]); +} + +void JitCompiler::EmitSV2() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp = newTempIntPtr(); + cc.mov(tmp, regA[A]); + cc.add(tmp, konstd[C]); + cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]); + cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]); +} + +void JitCompiler::EmitSV2_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp = newTempIntPtr(); + cc.mov(tmp, regA[A]); + cc.add(tmp, regD[C]); + cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]); + cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]); +} + +void JitCompiler::EmitSV3() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp = newTempIntPtr(); + cc.mov(tmp, regA[A]); + cc.add(tmp, konstd[C]); + cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]); + cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]); + cc.movsd(asmjit::x86::qword_ptr(tmp, 16), regF[B + 2]); +} + +void JitCompiler::EmitSV3_R() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp = newTempIntPtr(); + cc.mov(tmp, regA[A]); + cc.add(tmp, regD[C]); + cc.movsd(asmjit::x86::qword_ptr(tmp), regF[B]); + cc.movsd(asmjit::x86::qword_ptr(tmp, 8), regF[B + 1]); + cc.movsd(asmjit::x86::qword_ptr(tmp, 16), regF[B + 2]); +} + +void JitCompiler::EmitSBIT() +{ + EmitNullPointerThrow(A, X_WRITE_NIL); + auto tmp1 = newTempInt32(); + auto tmp2 = newTempInt32(); + cc.mov(tmp1, asmjit::x86::byte_ptr(regA[A])); + cc.mov(tmp2, tmp1); + cc.or_(tmp1, (int)C); + cc.and_(tmp2, ~(int)C); + cc.test(regD[B], regD[B]); + cc.cmove(tmp1, tmp2); + cc.mov(asmjit::x86::byte_ptr(regA[A]), tmp1); +} diff --git a/source/common/scripting/jit/jitintern.h b/source/common/scripting/jit/jitintern.h new file mode 100644 index 000000000..ac3d8acf5 --- /dev/null +++ b/source/common/scripting/jit/jitintern.h @@ -0,0 +1,337 @@ + +#include "jit.h" + +#include "types.h" +#include "stats.h" + +// To do: get cmake to define these.. +#define ASMJIT_BUILD_EMBED +#define ASMJIT_STATIC + +#include +#include +#include +#include + +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 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 + 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(A & CMP_CHECK), failLabel, successLabel); + + cc.bind(successLabel); + pc++; // This instruction uses two instruction slots - skip the next one + } + + template + void EmitVectorComparison(bool check, asmjit::Label& fail, asmjit::Label& success) + { + bool approx = static_cast(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(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 + asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1)) { return cc.call(asmjit::imm_ptr(reinterpret_cast(static_cast(func))), asmjit::FuncSignature1()); } + + template + asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2)) { return cc.call(asmjit::imm_ptr(reinterpret_cast(static_cast(func))), asmjit::FuncSignature2()); } + + template + asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3)) { return cc.call(asmjit::imm_ptr(reinterpret_cast(static_cast(func))), asmjit::FuncSignature3()); } + + template + asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4)) { return cc.call(asmjit::imm_ptr(reinterpret_cast(static_cast(func))), asmjit::FuncSignature4()); } + + template + asmjit::CCFuncCall *CreateCall(RetType(*func)(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)) { return cc.call(asmjit::imm_ptr(reinterpret_cast(static_cast(func))), asmjit::FuncSignature5()); } + + template + 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(static_cast(func))), asmjit::FuncSignature6()); } + + template + 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(static_cast(func))), asmjit::FuncSignature7()); } + + FString regname; + size_t tmpPosInt32, tmpPosInt64, tmpPosIntPtr, tmpPosXmmSd, tmpPosXmmSs, tmpPosXmmPd, resultPosInt32, resultPosIntPtr, resultPosXmmSd; + std::vector regTmpInt32, regTmpInt64, regTmpIntPtr, regResultInt32, regResultIntPtr; + std::vector 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 + T newTempRegister(std::vector &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 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 regD; + TArray regF; + TArray regA; + TArray 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 labels; + + const VMOP *pc; + VM_UBYTE op; +}; + +class AsmJitException : public std::exception +{ +public: + AsmJitException(asmjit::Error error, const char *message) noexcept : error(error), message(message) + { + } + + const char* what() const noexcept override + { + return message.GetChars(); + } + + asmjit::Error error; + FString message; +}; + +class ThrowingErrorHandler : public asmjit::ErrorHandler +{ +public: + bool handleError(asmjit::Error err, const char *message, asmjit::CodeEmitter *origin) override + { + throw AsmJitException(err, message); + } +}; + +void *AddJitFunction(asmjit::CodeHolder* code, JitCompiler *compiler); +asmjit::CodeInfo GetHostCodeInfo(); diff --git a/source/common/scripting/vm/vm.h b/source/common/scripting/vm/vm.h new file mode 100644 index 000000000..cb5c2dab5 --- /dev/null +++ b/source/common/scripting/vm/vm.h @@ -0,0 +1,790 @@ +/* +** vm.h +** VM <-> native interface +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef VM_H +#define VM_H + +#include "autosegs.h" +#include "zstring.h" +#include "vectors.h" +#include "cmdlib.h" +#include "engineerrors.h" +#include "memarena.h" +#include "name.h" +#include "scopebarrier.h" + +class DObject; +union VMOP; +class VMScriptFunction; + +extern FMemArena ClassDataAllocator; + +#define MAX_RETURNS 8 // Maximum number of results a function called by script code can return +#define MAX_TRY_DEPTH 8 // Maximum number of nested TRYs in a single function + +void JitRelease(); + + +typedef unsigned char VM_UBYTE; +typedef signed char VM_SBYTE; +typedef unsigned short VM_UHALF; +typedef signed short VM_SHALF; +typedef unsigned int VM_UWORD; +typedef signed int VM_SWORD; + +#define VM_EPSILON (1/65536.0) + +// Register types for VMParam +enum +{ + REGT_INT = 0, + REGT_FLOAT = 1, + REGT_STRING = 2, + REGT_POINTER = 3, + REGT_TYPE = 3, + + REGT_KONST = 4, + REGT_MULTIREG2 = 8, + REGT_MULTIREG3 = 16, // (e.g. a vector) + REGT_MULTIREG = 24, + REGT_ADDROF = 32, // used with PARAM: pass address of this register + + REGT_NIL = 128 // parameter was omitted +}; + +#define RET_FINAL (0x80) // Used with RET and RETI in the destination slot: this is the final return value + + +enum EVMAbortException +{ + X_OTHER, + X_READ_NIL, + X_WRITE_NIL, + X_TOO_MANY_TRIES, + X_ARRAY_OUT_OF_BOUNDS, + X_DIVISION_BY_ZERO, + X_BAD_SELF, + X_FORMAT_ERROR +}; + +class CVMAbortException : public CEngineError +{ +public: + static FString stacktrace; + CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap); + void MaybePrintMessage(); +}; + +// This must be a separate function because the VC compiler would otherwise allocate memory on the stack for every separate instance of the exception object that may get thrown. +void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...); +void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...); + +void ClearGlobalVMStack(); + +struct VMReturn +{ + void *Location; + VM_UBYTE RegType; // Same as VMParam RegType, except REGT_KONST is invalid; only used by asserts + + void SetInt(int val) + { + assert(RegType == REGT_INT); + *(int *)Location = val; + } + void SetFloat(double val) + { + assert(RegType == REGT_FLOAT); + *(double *)Location = val; + } + void SetVector(const double val[3]) + { + assert(RegType == (REGT_FLOAT|REGT_MULTIREG3)); + ((double *)Location)[0] = val[0]; + ((double *)Location)[1] = val[1]; + ((double *)Location)[2] = val[2]; + } + void SetVector(const DVector3 &val) + { + assert(RegType == (REGT_FLOAT | REGT_MULTIREG3)); + ((double *)Location)[0] = val[0]; + ((double *)Location)[1] = val[1]; + ((double *)Location)[2] = val[2]; + } + void SetVector2(const double val[2]) + { + assert(RegType == (REGT_FLOAT|REGT_MULTIREG2)); + ((double *)Location)[0] = val[0]; + ((double *)Location)[1] = val[1]; + } + void SetVector2(const DVector2 &val) + { + assert(RegType == (REGT_FLOAT | REGT_MULTIREG2)); + ((double *)Location)[0] = val[0]; + ((double *)Location)[1] = val[1]; + } + void SetString(const FString &val) + { + assert(RegType == REGT_STRING); + *(FString *)Location = val; + } + + void SetPointer(void *val) + { + assert(RegType == REGT_POINTER); + *(void **)Location = val; + } + + void SetObject(DObject *val) + { + assert(RegType == REGT_POINTER); + *(void **)Location = val; + } + + void IntAt(int *loc) + { + Location = loc; + RegType = REGT_INT; + } + void FloatAt(double *loc) + { + Location = loc; + RegType = REGT_FLOAT; + } + void Vec2At(DVector2 *loc) + { + Location = loc; + RegType = REGT_FLOAT | REGT_MULTIREG2; + } + void StringAt(FString *loc) + { + Location = loc; + RegType = REGT_STRING; + } + void PointerAt(void **loc) + { + Location = loc; + RegType = REGT_POINTER; + } + VMReturn() { } + VMReturn(int *loc) { IntAt(loc); } + VMReturn(double *loc) { FloatAt(loc); } + VMReturn(DVector2 *loc) { Vec2At(loc); } + VMReturn(FString *loc) { StringAt(loc); } + VMReturn(void **loc) { PointerAt(loc); } +}; + +struct VMRegisters; + +struct TypedVMValue +{ + union + { + int i; + void *a; + double f; + struct { int pad[3]; VM_UBYTE Type; }; + struct { int foo[4]; } biggest; + const FString *sp; + }; + + const FString &s() const { return *sp; } + + TypedVMValue() + { + a = NULL; + Type = REGT_NIL; + } + TypedVMValue(const TypedVMValue &o) + { + biggest = o.biggest; + } + TypedVMValue(int v) + { + i = v; + Type = REGT_INT; + } + TypedVMValue(double v) + { + f = v; + Type = REGT_FLOAT; + } + + TypedVMValue(const FString *s) + { + sp = s; + Type = REGT_STRING; + } + TypedVMValue(DObject *v) + { + a = v; + Type = REGT_POINTER; + } + TypedVMValue(void *v) + { + a = v; + Type = REGT_POINTER; + } + TypedVMValue &operator=(const TypedVMValue &o) + { + biggest = o.biggest; + return *this; + } + TypedVMValue &operator=(int v) + { + i = v; + Type = REGT_INT; + return *this; + } + TypedVMValue &operator=(double v) + { + f = v; + Type = REGT_FLOAT; + return *this; + } + TypedVMValue &operator=(const FString *v) + { + sp = v; + Type = REGT_STRING; + return *this; + } + TypedVMValue &operator=(DObject *v) + { + a = v; + Type = REGT_POINTER; + return *this; + } +}; + + +struct VMValue +{ + union + { + int i; + void *a; + double f; + struct { int foo[2]; } biggest; + const FString *sp; + }; + + const FString &s() const { return *sp; } + + VMValue() + { + a = NULL; + } + VMValue(const VMValue &o) + { + biggest = o.biggest; + } + VMValue(int v) + { + i = v; + } + VMValue(double v) + { + f = v; + } + VMValue(const char *s) = delete; + VMValue(const FString &s) = delete; + + VMValue(const FString *s) + { + sp = s; + } + VMValue(void *v) + { + a = v; + } + VMValue &operator=(const VMValue &o) + { + biggest = o.biggest; + return *this; + } + VMValue &operator=(const TypedVMValue &o) + { + memcpy(&biggest, &o.biggest, sizeof(biggest)); + return *this; + } + VMValue &operator=(int v) + { + i = v; + return *this; + } + VMValue &operator=(double v) + { + f = v; + return *this; + } + VMValue &operator=(const FString *v) + { + sp = v; + return *this; + } + VMValue &operator=(const FString &v) = delete; + VMValue &operator=(const char *v) = delete; + VMValue &operator=(DObject *v) + { + a = v; + return *this; + } + int ToInt(int Type) + { + if (Type == REGT_INT) + { + return i; + } + if (Type == REGT_FLOAT) + { + return int(f); + } + if (Type == REGT_STRING) + { + return (int)s().ToLong(); + } + // FIXME + return 0; + } + double ToDouble(int Type) + { + if (Type == REGT_FLOAT) + { + return f; + } + if (Type == REGT_INT) + { + return i; + } + if (Type == REGT_STRING) + { + return s().ToDouble(); + } + // FIXME + return 0; + } +}; + +class VMFunction +{ +public: + bool Unsafe = false; + uint8_t ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action + int VarFlags = 0; // [ZZ] this replaces 5+ bool fields + unsigned VirtualIndex = ~0u; + FName Name; + const uint8_t *RegTypes = nullptr; + TArray DefaultArgs; + FString PrintableName; // so that the VM can print meaningful info if something in this function goes wrong. + + class PPrototype *Proto; + TArray 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 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 ¶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 at position

+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 struct native_is_valid { static const bool value = false; static const bool retval = false; }; + template struct native_is_valid { static const bool value = true; static const bool retval = true; }; + template struct native_is_valid { static const bool value = true; static const bool retval = true; }; + template<> struct native_is_valid { static const bool value = true; static const bool retval = true; }; + template<> struct native_is_valid { static const bool value = true; static const bool retval = true; }; + template<> struct native_is_valid { static const bool value = true; static const bool retval = true; }; + template<> struct native_is_valid { static const bool value = true; static const bool retval = true; }; + template<> struct native_is_valid { 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() + template DirectNativeDesc(Ret(*func)()) : Ptr(reinterpret_cast(func)) { ValidateRet(); } + template DirectNativeDesc(Ret(*func)(P1)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); } + template DirectNativeDesc(Ret(*func)(P1,P2)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); } + template DirectNativeDesc(Ret(*func)(P1,P2,P3)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11)) : Ptr(reinterpret_cast(func)) { ValidateRet(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); } + template DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12)) : Ptr(reinterpret_cast(func)) { ValidateRet(); 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 DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13)) : Ptr(reinterpret_cast(func)) { ValidateRet(); 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 DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast(func)) { ValidateRet(); 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 void ValidateType() { static_assert(native_is_valid::value, "Argument type is not valid as a direct native parameter or return type"); } + template void ValidateRet() { static_assert(native_is_valid::retval, "Return type is not valid as a direct native parameter or return type"); } + + operator void *() const { return Ptr; } + + void *Ptr; +}; + +struct AFuncDesc +{ + const char *ClassName; + const char *FuncName; + actionf_p Function; + VMNativeFunction **VMPointer; + DirectNativeDesc DirectNative; +}; + +#if defined(_MSC_VER) +#pragma section(".areg$u",read) +#pragma section(".freg$u",read) + +#define MSVC_ASEG __declspec(allocate(".areg$u")) +#define MSVC_FSEG __declspec(allocate(".freg$u")) +#define GCC_ASEG +#define GCC_FSEG +#else +#define MSVC_ASEG +#define MSVC_FSEG +#define GCC_ASEG __attribute__((section(SECTION_AREG))) __attribute__((used)) +#define GCC_FSEG __attribute__((section(SECTION_FREG))) __attribute__((used)) +#endif + +// Macros to handle action functions. These are here so that I don't have to +// change every single use in case the parameters change. + +#define DEFINE_ACTION_FUNCTION_NATIVE(cls, name, native) \ + static int AF_##cls##_##name(VM_ARGS); \ + VMNativeFunction *cls##_##name##_VMPtr; \ + static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr, native }; \ + extern AFuncDesc const *const cls##_##name##_HookPtr; \ + MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \ + static int AF_##cls##_##name(VM_ARGS) + +#define DEFINE_ACTION_FUNCTION_NATIVE0(cls, name, native) \ + static int AF_##cls##_##name(VM_ARGS); \ + VMNativeFunction *cls##_##name##_VMPtr; \ + static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \ + extern AFuncDesc const *const cls##_##name##_HookPtr; \ + MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \ + static int AF_##cls##_##name(VM_ARGS) + +#define DEFINE_ACTION_FUNCTION(cls, name) \ + static int AF_##cls##_##name(VM_ARGS); \ + VMNativeFunction *cls##_##name##_VMPtr; \ + static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \ + extern AFuncDesc const *const cls##_##name##_HookPtr; \ + MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \ + static int AF_##cls##_##name(VM_ARGS) + +// cls is the scripted class name, icls the internal one (e.g. player_t vs. Player) +#define DEFINE_FIELD_X(cls, icls, name) \ + static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \ + extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name; + +// This is for cases where the internal size does not match the part that gets exported. +#define DEFINE_FIELD_UNSIZED(cls, icls, name) \ + static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), ~0u, 0 }; \ + extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name; + +#define DEFINE_FIELD_NAMED_X(cls, icls, name, scriptname) \ + static const FieldDesc VMField_##cls##_##scriptname = { "A" #cls, #scriptname, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \ + extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname; + +#define DEFINE_FIELD_X_BIT(cls, icls, name, bitval) \ + static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), bitval }; \ + extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name; + +#define DEFINE_FIELD(cls, name) \ + static const FieldDesc VMField_##cls##_##name = { #cls, #name, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \ + extern FieldDesc const *const VMField_##cls##_##name##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##cls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name; + +#define DEFINE_FIELD_NAMED(cls, name, scriptname) \ + static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \ + extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname; + +#define DEFINE_FIELD_BIT(cls, name, scriptname, bitval) \ + static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), bitval }; \ + extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname; + +#define DEFINE_GLOBAL(name) \ + static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&name, (unsigned)sizeof(name), 0 }; \ + extern FieldDesc const *const VMGlobal_##name##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name; + +#define DEFINE_GLOBAL_NAMED(iname, name) \ + static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&iname, (unsigned)sizeof(iname), 0 }; \ + extern FieldDesc const *const VMGlobal_##name##_HookPtr; \ + MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name; + + +class AActor; + +#define ACTION_RETURN_STATE(v) do { FState *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0) +#define ACTION_RETURN_POINTER(v) do { void *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0) +#define ACTION_RETURN_OBJECT(v) do { auto state = v; if (numret > 0) { assert(ret != NULL); ret->SetObject(state); return 1; } return 0; } while(0) +#define ACTION_RETURN_FLOAT(v) do { double u = v; if (numret > 0) { assert(ret != nullptr); ret->SetFloat(u); return 1; } return 0; } while(0) +#define ACTION_RETURN_VEC2(v) do { DVector2 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector2(u); return 1; } return 0; } while(0) +#define ACTION_RETURN_VEC3(v) do { DVector3 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector(u); return 1; } return 0; } while(0) +#define ACTION_RETURN_INT(v) do { int u = v; if (numret > 0) { assert(ret != NULL); ret->SetInt(u); return 1; } return 0; } while(0) +#define ACTION_RETURN_BOOL(v) ACTION_RETURN_INT(v) +#define ACTION_RETURN_STRING(v) do { FString u = v; if (numret > 0) { assert(ret != NULL); ret->SetString(u); return 1; } return 0; } while(0) + +// Checks to see what called the current action function +#define ACTION_CALL_FROM_ACTOR() (stateinfo == nullptr || stateinfo->mStateType == STATE_Actor) +#define ACTION_CALL_FROM_PSPRITE() (self->player && stateinfo != nullptr && stateinfo->mStateType == STATE_Psprite) +#define ACTION_CALL_FROM_INVENTORY() (stateinfo != nullptr && stateinfo->mStateType == STATE_StateChain) + +// Standard parameters for all action functions +// self - Actor this action is to operate on (player if a weapon) +// stateowner - Actor this action really belongs to (may be an item) +// callingstate - State this action was called from +#define PARAM_ACTION_PROLOGUE(type) \ + PARAM_PROLOGUE; \ + PARAM_OBJECT_NOT_NULL (self, AActor); \ + PARAM_OBJECT (stateowner, type) \ + PARAM_POINTER (stateinfo, FStateParamInfo) \ + +// Number of action paramaters +#define NAP 3 + +#define PARAM_SELF_PROLOGUE(type) \ + PARAM_PROLOGUE; \ + PARAM_OBJECT_NOT_NULL(self, type); + +// for structs we cannot do a class validation +#define PARAM_SELF_STRUCT_PROLOGUE(type) \ + PARAM_PROLOGUE; \ + PARAM_POINTER_NOT_NULL(self, type); + +class PFunction; + +VMFunction *FindVMFunction(PClass *cls, const char *name); +#define DECLARE_VMFUNC(cls, name) static VMFunction *name; if (name == nullptr) name = FindVMFunction(RUNTIME_CLASS(cls), #name); + +FString FStringFormat(VM_ARGS, int offset = 0); + +#define IFVM(cls, funcname) \ + static VMFunction * func = nullptr; \ + if (func == nullptr) { \ + PClass::FindFunction(&func, #cls, #funcname); \ + assert(func); \ + } \ + if (func != nullptr) + + + +unsigned GetVirtualIndex(PClass *cls, const char *funcname); + +#define IFVIRTUALPTR(self, cls, funcname) \ + static unsigned VIndex = ~0u; \ + if (VIndex == ~0u) { \ + VIndex = GetVirtualIndex(RUNTIME_CLASS(cls), #funcname); \ + assert(VIndex != ~0u); \ + } \ + auto clss = self->GetClass(); \ + VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \ + if (func != nullptr) + +#define IFVIRTUAL(cls, funcname) IFVIRTUALPTR(this, cls, funcname) + +#define IFVIRTUALPTRNAME(self, cls, funcname) \ + static unsigned VIndex = ~0u; \ + if (VIndex == ~0u) { \ + VIndex = GetVirtualIndex(PClass::FindClass(cls), #funcname); \ + assert(VIndex != ~0u); \ + } \ + auto clss = self->GetClass(); \ + VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \ + if (func != nullptr) + +#endif diff --git a/source/common/scripting/vm/vmexec.cpp b/source/common/scripting/vm/vmexec.cpp new file mode 100644 index 000000000..088988bd7 --- /dev/null +++ b/source/common/scripting/vm/vmexec.cpp @@ -0,0 +1,253 @@ +/* +** vmexec.cpp +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include +#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 +struct VMExec_Checked +{ +#include "vmexec.h" +}; +#if WAS_NDEBUG +#define NDEBUG +#endif + +#if !WAS_NDEBUG +#define NDEBUG +#endif +#undef assert +#include +struct VMExec_Unchecked +{ +#include "vmexec.h" +}; +#if !WAS_NDEBUG +#undef NDEBUG +#endif +#undef assert +#include + +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(callee->Func); + const VMRegisters calleereg(callee); + + assert(calleefunc != NULL && !(calleefunc->VarFlags & VARF_Native)); + assert(numparam == calleefunc->NumArgs); + assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3); + + regd = regf = regs = rega = 0; + const uint8_t *reginfo = calleefunc->RegTypes; + assert(reginfo != nullptr); + for (int i = 0; i < calleefunc->NumArgs; ++i, reginfo++) + { + // copy all parameters to the local registers. + VMValue &p = params[i]; + if (*reginfo < REGT_STRING) + { + if (*reginfo == REGT_INT) + { + calleereg.d[regd++] = p.i; + } + else // p.Type == REGT_FLOAT + { + calleereg.f[regf++] = p.f; + } + } + else if (*reginfo == REGT_STRING) + { + calleereg.s[regs++] = p.s(); + } + else + { + assert(*reginfo == REGT_POINTER); + calleereg.a[rega++] = p.a; + } + } +} + + +#ifndef NDEBUG +bool AssertObject(void * ob) +{ + auto obj = (DObject*)ob; + if (obj == nullptr) return true; +#ifdef _MSC_VER + __try + { + return obj->MagicID == DObject::MAGIC_ID; + } + __except (1) + { + return false; + } +#else + // No SEH on non-Microsoft compilers. :( + return obj->MagicID == DObject::MAGIC_ID; +#endif +} +#endif diff --git a/source/common/scripting/vm/vmexec.h b/source/common/scripting/vm/vmexec.h new file mode 100644 index 000000000..0e28bee2a --- /dev/null +++ b/source/common/scripting/vm/vmexec.h @@ -0,0 +1,2017 @@ +/* +** vmexec.h +** VM bytecode interpreter +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** Copyright 2016-2017 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef IMPLEMENT_VMEXEC +#error vmexec.h must not be #included outside vmexec.cpp. Use vm.h instead. +#endif + +static int ExecScriptFunc(VMFrameStack *stack, VMReturn *ret, int numret) +{ +#if COMPGOTO + static const void * const ops[256] = + { +#define xx(op,sym,mode,alt,kreg,ktype) &&op, +#include "vmops.h" + }; +#endif + //const VMOP *exception_frames[MAX_TRY_DEPTH]; + //int try_depth = 0; + VMFrame *f = stack->TopFrame(); + VMScriptFunction *sfunc = static_cast(f->Func); + const int *konstd = sfunc->KonstD; + const double *konstf = sfunc->KonstF; + const FString *konsts = sfunc->KonstS; + const FVoidObj *konsta = sfunc->KonstA; + const VMOP *pc = sfunc->Code; + + assert(!(f->Func->VarFlags & VARF_Native) && "Only script functions should ever reach VMExec"); + + const VMRegisters reg(f); + + void *ptr; + double fb, fc; + const double *fbp, *fcp; + int a, b, c; + +//begin: + try + { +#if !COMPGOTO + VM_UBYTE op; + for(;;) switch(op = pc->op, a = pc->a, op) +#else + pc--; + NEXTOP; +#endif + { +#if !COMPGOTO + default: + assert(0 && "Undefined opcode hit"); + NEXTOP; +#endif + OP(LI): + ASSERTD(a); + reg.d[a] = BCs; + NEXTOP; + OP(LK): + ASSERTD(a); ASSERTKD(BC); + reg.d[a] = konstd[BC]; + NEXTOP; + OP(LKF): + ASSERTF(a); ASSERTKF(BC); + reg.f[a] = konstf[BC]; + NEXTOP; + OP(LKS): + ASSERTS(a); ASSERTKS(BC); + reg.s[a] = konsts[BC]; + NEXTOP; + OP(LKP): + ASSERTA(a); ASSERTKA(BC); + reg.a[a] = konsta[BC].v; + NEXTOP; + + OP(LK_R) : + ASSERTD(a); ASSERTD(B); + reg.d[a] = konstd[reg.d[B] + C]; + NEXTOP; + OP(LKF_R) : + ASSERTF(a); ASSERTD(B); + reg.f[a] = konstf[reg.d[B] + C]; + NEXTOP; + OP(LKS_R) : + ASSERTS(a); ASSERTD(B); + reg.s[a] = konsts[reg.d[B] + C]; + NEXTOP; + OP(LKP_R) : + ASSERTA(a); ASSERTD(B); + b = reg.d[B] + C; + reg.a[a] = konsta[b].v; + NEXTOP; + + OP(LFP): + ASSERTA(a); assert(sfunc != NULL); assert(sfunc->ExtraSpace > 0); + reg.a[a] = f->GetExtra(); + NEXTOP; + + OP(CLSS): + { + ASSERTA(a); ASSERTA(B); + DObject *o = (DObject*)reg.a[B]; + if (o == nullptr) + { + ThrowAbortException(X_READ_NIL, nullptr); + return 0; + } + reg.a[a] = o->GetClass(); + NEXTOP; + } + + OP(META): + { + ASSERTA(a); ASSERTA(B); + DObject *o = (DObject*)reg.a[B]; + if (o == nullptr) + { + ThrowAbortException(X_READ_NIL, nullptr); + return 0; + } + reg.a[a] = o->GetClass()->Meta; + NEXTOP; + } + + OP(LB): + ASSERTD(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.d[a] = *(VM_SBYTE *)ptr; + NEXTOP; + OP(LB_R): + ASSERTD(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.d[a] = *(VM_SBYTE *)ptr; + NEXTOP; + OP(LH): + ASSERTD(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.d[a] = *(VM_SHALF *)ptr; + NEXTOP; + OP(LH_R): + ASSERTD(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.d[a] = *(VM_SHALF *)ptr; + NEXTOP; + OP(LW): + ASSERTD(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.d[a] = *(VM_SWORD *)ptr; + NEXTOP; + OP(LW_R): + ASSERTD(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.d[a] = *(VM_SWORD *)ptr; + NEXTOP; + OP(LBU): + ASSERTD(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.d[a] = *(VM_UBYTE *)ptr; + NEXTOP; + OP(LBU_R): + ASSERTD(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.d[a] = *(VM_UBYTE *)ptr; + NEXTOP; + OP(LHU): + ASSERTD(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.d[a] = *(VM_UHALF *)ptr; + NEXTOP; + OP(LHU_R): + ASSERTD(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.d[a] = *(VM_UHALF *)ptr; + NEXTOP; + + OP(LSP): + ASSERTF(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.f[a] = *(float *)ptr; + NEXTOP; + OP(LSP_R): + ASSERTF(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.f[a] = *(float *)ptr; + NEXTOP; + OP(LDP): + ASSERTF(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.f[a] = *(double *)ptr; + NEXTOP; + OP(LDP_R): + ASSERTF(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.f[a] = *(double *)ptr; + NEXTOP; + + OP(LS): + ASSERTS(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.s[a] = *(FString *)ptr; + NEXTOP; + OP(LS_R): + ASSERTS(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.s[a] = *(FString *)ptr; + NEXTOP; + OP(LCS): + ASSERTS(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.s[a] = *(const char **)ptr; + NEXTOP; + OP(LCS_R): + ASSERTS(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.s[a] = *(const char **)ptr; + NEXTOP; + OP(LO): + ASSERTA(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.a[a] = GC::ReadBarrier(*(DObject **)ptr); + NEXTOP; + OP(LO_R): + ASSERTA(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.a[a] = GC::ReadBarrier(*(DObject **)ptr); + NEXTOP; + OP(LP): + ASSERTA(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + reg.a[a] = *(void **)ptr; + NEXTOP; + OP(LP_R): + ASSERTA(a); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + reg.a[a] = *(void **)ptr; + NEXTOP; + OP(LV2): + ASSERTF(a+1); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + { + auto v = (double *)ptr; + reg.f[a] = v[0]; + reg.f[a+1] = v[1]; + } + NEXTOP; + OP(LV2_R): + ASSERTF(a+1); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + { + auto v = (double *)ptr; + reg.f[a] = v[0]; + reg.f[a+1] = v[1]; + } + NEXTOP; + OP(LV3): + ASSERTF(a+2); ASSERTA(B); ASSERTKD(C); + GETADDR(PB,KC,X_READ_NIL); + { + auto v = (double *)ptr; + reg.f[a] = v[0]; + reg.f[a+1] = v[1]; + reg.f[a+2] = v[2]; + } + NEXTOP; + OP(LV3_R): + ASSERTF(a+2); ASSERTA(B); ASSERTD(C); + GETADDR(PB,RC,X_READ_NIL); + { + auto v = (double *)ptr; + reg.f[a] = v[0]; + reg.f[a+1] = v[1]; + reg.f[a+2] = v[2]; + } + NEXTOP; + OP(LBIT): + ASSERTD(a); ASSERTA(B); + GETADDR(PB,0,X_READ_NIL); + reg.d[a] = !!(*(VM_UBYTE *)ptr & C); + NEXTOP; + + OP(SB): + ASSERTA(a); ASSERTD(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(VM_SBYTE *)ptr = reg.d[B]; + NEXTOP; + OP(SB_R): + ASSERTA(a); ASSERTD(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(VM_SBYTE *)ptr = reg.d[B]; + NEXTOP; + OP(SH): + ASSERTA(a); ASSERTD(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(VM_SHALF *)ptr = reg.d[B]; + NEXTOP; + OP(SH_R): + ASSERTA(a); ASSERTD(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(VM_SHALF *)ptr = reg.d[B]; + NEXTOP; + OP(SW): + ASSERTA(a); ASSERTD(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(VM_SWORD *)ptr = reg.d[B]; + NEXTOP; + OP(SW_R): + ASSERTA(a); ASSERTD(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(VM_SWORD *)ptr = reg.d[B]; + NEXTOP; + OP(SSP): + ASSERTA(a); ASSERTF(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(float *)ptr = (float)reg.f[B]; + NEXTOP; + OP(SSP_R): + ASSERTA(a); ASSERTF(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(float *)ptr = (float)reg.f[B]; + NEXTOP; + OP(SDP): + ASSERTA(a); ASSERTF(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(double *)ptr = reg.f[B]; + NEXTOP; + OP(SDP_R): + ASSERTA(a); ASSERTF(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(double *)ptr = reg.f[B]; + NEXTOP; + OP(SS): + ASSERTA(a); ASSERTS(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(FString *)ptr = reg.s[B]; + NEXTOP; + OP(SS_R): + ASSERTA(a); ASSERTS(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(FString *)ptr = reg.s[B]; + NEXTOP; + OP(SP): + ASSERTA(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(void **)ptr = reg.a[B]; + NEXTOP; + OP(SP_R): + ASSERTA(a); ASSERTA(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + *(void **)ptr = reg.a[B]; + NEXTOP; + OP(SO): + ASSERTA(a); ASSERTA(B); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + *(void **)ptr = reg.a[B]; + GC::WriteBarrier((DObject*)*(void **)ptr); + NEXTOP; + OP(SO_R): + ASSERTA(a); ASSERTA(B); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + GC::WriteBarrier((DObject*)*(void **)ptr); + NEXTOP; + OP(SV2): + ASSERTA(a); ASSERTF(B+1); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + { + auto v = (double *)ptr; + v[0] = reg.f[B]; + v[1] = reg.f[B+1]; + } + NEXTOP; + OP(SV2_R): + ASSERTA(a); ASSERTF(B+1); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + { + auto v = (double *)ptr; + v[0] = reg.f[B]; + v[1] = reg.f[B+1]; + } + NEXTOP; + OP(SV3): + ASSERTA(a); ASSERTF(B+2); ASSERTKD(C); + GETADDR(PA,KC,X_WRITE_NIL); + { + auto v = (double *)ptr; + v[0] = reg.f[B]; + v[1] = reg.f[B+1]; + v[2] = reg.f[B+2]; + } + NEXTOP; + OP(SV3_R): + ASSERTA(a); ASSERTF(B+2); ASSERTD(C); + GETADDR(PA,RC,X_WRITE_NIL); + { + auto v = (double *)ptr; + v[0] = reg.f[B]; + v[1] = reg.f[B+1]; + v[2] = reg.f[B+2]; + } + NEXTOP; + OP(SBIT): + ASSERTA(a); ASSERTD(B); + GETADDR(PA,0,X_WRITE_NIL); + if (reg.d[B]) + { + *(VM_UBYTE *)ptr |= C; + } + else + { + *(VM_UBYTE *)ptr &= ~C; + } + NEXTOP; + + OP(MOVE): + ASSERTD(a); ASSERTD(B); + reg.d[a] = reg.d[B]; + NEXTOP; + OP(MOVEF): + ASSERTF(a); ASSERTF(B); + reg.f[a] = reg.f[B]; + NEXTOP; + OP(MOVES): + ASSERTS(a); ASSERTS(B); + reg.s[a] = reg.s[B]; + NEXTOP; + OP(MOVEA): + { + ASSERTA(a); ASSERTA(B); + b = B; + reg.a[a] = reg.a[b]; + NEXTOP; + } + OP(MOVEV2): + { + ASSERTF(a); ASSERTF(B); + b = B; + reg.f[a] = reg.f[b]; + reg.f[a + 1] = reg.f[b + 1]; + NEXTOP; + } + OP(MOVEV3): + { + ASSERTF(a); ASSERTF(B); + b = B; + reg.f[a] = reg.f[b]; + reg.f[a + 1] = reg.f[b + 1]; + reg.f[a + 2] = reg.f[b + 2]; + NEXTOP; + } + OP(DYNCAST_R) : + ASSERTA(a); ASSERTA(B); ASSERTA(C); + b = B; + reg.a[a] = (reg.a[b] && ((DObject*)(reg.a[b]))->IsKindOf((PClass*)(reg.a[C]))) ? reg.a[b] : nullptr; + NEXTOP; + OP(DYNCAST_K) : + ASSERTA(a); ASSERTA(B); ASSERTKA(C); + b = B; + reg.a[a] = (reg.a[b] && ((DObject*)(reg.a[b]))->IsKindOf((PClass*)(konsta[C].o))) ? reg.a[b] : nullptr; + NEXTOP; + OP(DYNCASTC_R) : + ASSERTA(a); ASSERTA(B); ASSERTA(C); + b = B; + reg.a[a] = (reg.a[b] && ((PClass*)(reg.a[b]))->IsDescendantOf((PClass*)(reg.a[C]))) ? reg.a[b] : nullptr; + NEXTOP; + OP(DYNCASTC_K) : + ASSERTA(a); ASSERTA(B); ASSERTKA(C); + b = B; + reg.a[a] = (reg.a[b] && ((PClass*)(reg.a[b]))->IsDescendantOf((PClass*)(konsta[C].o))) ? reg.a[b] : nullptr; + NEXTOP; + OP(CAST): + if (C == CAST_I2F) + { + ASSERTF(a); ASSERTD(B); + reg.f[a] = reg.d[B]; + } + else if (C == CAST_F2I) + { + ASSERTD(a); ASSERTF(B); + reg.d[a] = (int)reg.f[B]; + } + else + { + DoCast(reg, f, a, B, C); + } + NEXTOP; + + OP(CASTB): + if (C == CASTB_I) + { + ASSERTD(a); ASSERTD(B); + reg.d[a] = !!reg.d[B]; + } + else if (C == CASTB_F) + { + ASSERTD(a); ASSERTF(B); + reg.d[a] = reg.f[B] != 0; + } + else if (C == CASTB_A) + { + ASSERTD(a); ASSERTA(B); + reg.d[a] = reg.a[B] != nullptr; + } + else + { + ASSERTD(a); ASSERTS(B); + reg.d[a] = reg.s[B].Len() > 0; + } + NEXTOP; + + OP(TEST): + ASSERTD(a); + if (reg.d[a] != BC) + { + pc++; + } + NEXTOP; + OP(TESTN): + ASSERTD(a); + if (-reg.d[a] != BC) + { + pc++; + } + NEXTOP; + OP(JMP): + pc += JMPOFS(pc); + NEXTOP; + OP(IJMP): + ASSERTD(a); + pc += (reg.d[a]); + assert(pc[1].op == OP_JMP); + pc += 1 + JMPOFS(pc+1); + NEXTOP; + OP(PARAMI): + assert(f->NumParam < sfunc->MaxParam); + { + VMValue *param = ®.param[f->NumParam++]; + ::new(param) VMValue(ABCs); + } + NEXTOP; + OP(PARAM): + assert(f->NumParam < sfunc->MaxParam); + { + VMValue *param = ®.param[f->NumParam++]; + b = BC; + if (a == REGT_NIL) + { + ::new(param) VMValue(); + } + else + { + switch(a) + { + case REGT_INT: + assert(b < f->NumRegD); + ::new(param) VMValue(reg.d[b]); + break; + case REGT_INT | REGT_ADDROF: + assert(b < f->NumRegD); + ::new(param) VMValue(®.d[b]); + break; + case REGT_INT | REGT_KONST: + assert(b < sfunc->NumKonstD); + ::new(param) VMValue(konstd[b]); + break; + case REGT_STRING: + assert(b < f->NumRegS); + ::new(param) VMValue(®.s[b]); + break; + case REGT_STRING | REGT_ADDROF: + assert(b < f->NumRegS); + ::new(param) VMValue((void*)®.s[b]); // Note that this may not use the FString* version of the constructor! + break; + case REGT_STRING | REGT_KONST: + assert(b < sfunc->NumKonstS); + ::new(param) VMValue(&konsts[b]); + break; + case REGT_POINTER: + assert(b < f->NumRegA); + ::new(param) VMValue(reg.a[b]); + break; + case REGT_POINTER | REGT_ADDROF: + assert(b < f->NumRegA); + ::new(param) VMValue(®.a[b]); + break; + case REGT_POINTER | REGT_KONST: + assert(b < sfunc->NumKonstA); + ::new(param) VMValue(konsta[b].v); + break; + case REGT_FLOAT: + assert(b < f->NumRegF); + ::new(param) VMValue(reg.f[b]); + break; + case REGT_FLOAT | REGT_MULTIREG2: + assert(b < f->NumRegF - 1); + assert(f->NumParam < sfunc->MaxParam); + ::new(param) VMValue(reg.f[b]); + ::new(param + 1) VMValue(reg.f[b + 1]); + f->NumParam++; + break; + case REGT_FLOAT | REGT_MULTIREG3: + assert(b < f->NumRegF - 2); + assert(f->NumParam < sfunc->MaxParam - 1); + ::new(param) VMValue(reg.f[b]); + ::new(param + 1) VMValue(reg.f[b + 1]); + ::new(param + 2) VMValue(reg.f[b + 2]); + f->NumParam += 2; + break; + case REGT_FLOAT | REGT_ADDROF: + assert(b < f->NumRegF); + ::new(param) VMValue(®.f[b]); + break; + case REGT_FLOAT | REGT_KONST: + assert(b < sfunc->NumKonstF); + ::new(param) VMValue(konstf[b]); + break; + default: + assert(0); + break; + } + } + } + NEXTOP; + OP(VTBL): + ASSERTA(a); ASSERTA(B); + { + auto o = (DObject*)reg.a[B]; + if (o == nullptr) + { + ThrowAbortException(X_READ_NIL, nullptr); + return 0; + } + auto p = o->GetClass(); + assert(C < p->Virtuals.Size()); + reg.a[a] = p->Virtuals[C]; + } + NEXTOP; + OP(SCOPE): + { + ASSERTA(a); ASSERTKA(C); + auto o = (DObject*)reg.a[a]; + if (o == nullptr) + { + ThrowAbortException(X_READ_NIL, nullptr); + return 0; + } + FScopeBarrier::ValidateCall(o->GetClass(), (VMFunction*)konsta[C].v, B - 1); + } + NEXTOP; + + OP(CALL_K): + ASSERTKA(a); + ptr = konsta[a].o; + goto Do_CALL; + OP(CALL): + ASSERTA(a); + ptr = reg.a[a]; + Do_CALL: + assert(B <= f->NumParam); + assert(C <= MAX_RETURNS); + { + VMFunction *call = (VMFunction *)ptr; + VMReturn returns[MAX_RETURNS]; + int numret; + + b = B; + FillReturns(reg, f, returns, pc+1, C); + if (call->VarFlags & VARF_Native) + { + try + { + VMCycles[0].Unclock(); + numret = static_cast(call)->NativeCall(VM_INVOKE(reg.param + f->NumParam - b, b, returns, C, call->RegTypes)); + VMCycles[0].Clock(); + } + catch (CVMAbortException &err) + { + err.MaybePrintMessage(); + err.stacktrace.AppendFormat("Called from %s\n", call->PrintableName.GetChars()); + // PrintParameters(reg.param + f->NumParam - B, B); + throw; + } + } + else + { + auto sfunc = static_cast(call); + numret = sfunc->ScriptCall(sfunc, reg.param + f->NumParam - b, b, returns, C); + } + assert(numret == C && "Number of parameters returned differs from what was expected by the caller"); + f->NumParam -= B; + pc += C; // Skip RESULTs + } + NEXTOP; + OP(RET): + if (B == REGT_NIL) + { // No return values + return 0; + } + assert(ret != NULL || numret == 0); + { + int retnum = a & ~RET_FINAL; + if (retnum < numret) + { + SetReturn(reg, f, &ret[retnum], B, C); + } + if (a & RET_FINAL) + { + return retnum < numret ? retnum + 1 : numret; + } + } + NEXTOP; + OP(RETI): + assert(ret != NULL || numret == 0); + { + int retnum = a & ~RET_FINAL; + if (retnum < numret) + { + ret[retnum].SetInt(BCs); + } + if (a & RET_FINAL) + { + return retnum < numret ? retnum + 1 : numret; + } + } + NEXTOP; + OP(RESULT): + // This instruction is just a placeholder to indicate where a return + // value should be stored. It does nothing on its own and should not + // be executed. + assert(0); + NEXTOP; + +#if 0 + OP(TRY): + assert(try_depth < MAX_TRY_DEPTH); + if (try_depth >= MAX_TRY_DEPTH) + { + ThrowAbortException(X_TOO_MANY_TRIES, nullptr); + } + assert((pc + JMPOFS(pc) + 1)->op == OP_CATCH); + exception_frames[try_depth++] = pc + JMPOFS(pc) + 1; + NEXTOP; + OP(UNTRY): + assert(a <= try_depth); + try_depth -= a; + NEXTOP; +#endif + OP(THROW): +#if 0 + if (a == 0) + { + ASSERTA(B); + ThrowVMException((VMException *)reg.a[B]); + } + else if (a == 1) + { + ASSERTKA(B); + assert(AssertObject(konsta[B].o)); + ThrowVMException((VMException *)konsta[B].o); + } + else +#endif + { + ThrowAbortException(EVMAbortException(BC), nullptr); + } + NEXTOP; +#if 0 + OP(CATCH): + // This instruction is handled by our own catch handler and should + // not be executed by the normal VM code. + assert(0); + NEXTOP; +#endif + + OP(BOUND): + if (reg.d[a] >= BC) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", BC, reg.d[a]); + return 0; + } + else if (reg.d[a] < 0) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", reg.d[a]); + return 0; + } + NEXTOP; + + OP(BOUND_K): + ASSERTKD(BC); + if (reg.d[a] >= konstd[BC]) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", konstd[BC], reg.d[a]); + return 0; + } + else if (reg.d[a] < 0) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", reg.d[a]); + return 0; + } + NEXTOP; + + OP(BOUND_R): + ASSERTD(B); + if (reg.d[a] >= reg.d[B]) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", reg.d[B], reg.d[a]); + return 0; + } + else if (reg.d[a] < 0) + { + ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Negative current index = %i\n", reg.d[a]); + return 0; + } + NEXTOP; + + OP(CONCAT): + ASSERTS(a); ASSERTS(B); ASSERTS(C); + reg.s[a] = reg.s[B] + reg.s[C]; + NEXTOP; + OP(LENS): + ASSERTD(a); ASSERTS(B); + reg.d[a] = (int)reg.s[B].Len(); + NEXTOP; + + OP(CMPS): + // String comparison is a fairly expensive operation, so I've + // chosen to conserve a few opcodes by condensing all the + // string comparisons into a single one. + { + const FString *b, *c; + int test, method; + bool cmp; + + if (a & CMP_BK) + { + ASSERTKS(B); + b = &konsts[B]; + } + else + { + ASSERTS(B); + b = ®.s[B]; + } + if (a & CMP_CK) + { + ASSERTKS(C); + c = &konsts[C]; + } + else + { + ASSERTS(C); + c = ®.s[C]; + } + test = (a & CMP_APPROX) ? b->CompareNoCase(*c) : b->Compare(*c); + method = a & CMP_METHOD_MASK; + if (method == CMP_EQ) + { + cmp = !test; + } + else if (method == CMP_LT) + { + cmp = (test < 0); + } + else + { + assert(method == CMP_LE); + cmp = (test <= 0); + } + if (cmp == (a & CMP_CHECK)) + { + assert(pc[1].op == OP_JMP); + pc += 1 + JMPOFS(pc+1); + } + else + { + pc += 1; + } + } + NEXTOP; + + OP(SLL_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] << reg.d[C]; + NEXTOP; + OP(SLL_RI): + ASSERTD(a); ASSERTD(B); assert(C <= 31); + reg.d[a] = reg.d[B] << C; + NEXTOP; + OP(SLL_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + reg.d[a] = konstd[B] << reg.d[C]; + NEXTOP; + + OP(SRL_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = (unsigned)reg.d[B] >> reg.d[C]; + NEXTOP; + OP(SRL_RI): + ASSERTD(a); ASSERTD(B); assert(C <= 31); + reg.d[a] = (unsigned)reg.d[B] >> C; + NEXTOP; + OP(SRL_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + reg.d[a] = (unsigned)konstd[B] >> reg.d[C]; + NEXTOP; + + OP(SRA_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] >> reg.d[C]; + NEXTOP; + OP(SRA_RI): + ASSERTD(a); ASSERTD(B); assert(C <= 31); + reg.d[a] = reg.d[B] >> C; + NEXTOP; + OP(SRA_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + reg.d[a] = konstd[B] >> reg.d[C]; + NEXTOP; + + OP(ADD_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] + reg.d[C]; + NEXTOP; + OP(ADD_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] + konstd[C]; + NEXTOP; + OP(ADDI): + ASSERTD(a); ASSERTD(B); + reg.d[a] = reg.d[B] + Cs; + NEXTOP; + + OP(SUB_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] - reg.d[C]; + NEXTOP; + OP(SUB_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] - konstd[C]; + NEXTOP; + OP(SUB_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + reg.d[a] = konstd[B] - reg.d[C]; + NEXTOP; + + OP(MUL_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] * reg.d[C]; + NEXTOP; + OP(MUL_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] * konstd[C]; + NEXTOP; + + OP(DIV_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = reg.d[B] / reg.d[C]; + NEXTOP; + OP(DIV_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + if (konstd[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = reg.d[B] / konstd[C]; + NEXTOP; + OP(DIV_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = konstd[B] / reg.d[C]; + NEXTOP; + + OP(DIVU_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = int((unsigned)reg.d[B] / (unsigned)reg.d[C]); + NEXTOP; + OP(DIVU_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + if (konstd[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = int((unsigned)reg.d[B] / (unsigned)konstd[C]); + NEXTOP; + OP(DIVU_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = int((unsigned)konstd[B] / (unsigned)reg.d[C]); + NEXTOP; + + OP(MOD_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = reg.d[B] % reg.d[C]; + NEXTOP; + OP(MOD_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + if (konstd[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = reg.d[B] % konstd[C]; + NEXTOP; + OP(MOD_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = konstd[B] % reg.d[C]; + NEXTOP; + + OP(MODU_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = int((unsigned)reg.d[B] % (unsigned)reg.d[C]); + NEXTOP; + OP(MODU_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + if (konstd[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = int((unsigned)reg.d[B] % (unsigned)konstd[C]); + NEXTOP; + OP(MODU_KR): + ASSERTD(a); ASSERTKD(B); ASSERTD(C); + if (reg.d[C] == 0) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.d[a] = int((unsigned)konstd[B] % (unsigned)reg.d[C]); + NEXTOP; + + OP(AND_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] & reg.d[C]; + NEXTOP; + OP(AND_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] & konstd[C]; + NEXTOP; + + OP(OR_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] | reg.d[C]; + NEXTOP; + OP(OR_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] | konstd[C]; + NEXTOP; + + OP(XOR_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] ^ reg.d[C]; + NEXTOP; + OP(XOR_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] ^ konstd[C]; + NEXTOP; + + OP(MIN_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] < reg.d[C] ? reg.d[B] : reg.d[C]; + NEXTOP; + OP(MIN_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] < konstd[C] ? reg.d[B] : konstd[C]; + NEXTOP; + OP(MAX_RR): + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = reg.d[B] > reg.d[C] ? reg.d[B] : reg.d[C]; + NEXTOP; + OP(MAX_RK): + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = reg.d[B] > konstd[C] ? reg.d[B] : konstd[C]; + NEXTOP; + + OP(MINU_RR) : + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = (unsigned)reg.d[B] < (unsigned)reg.d[C] ? reg.d[B] : reg.d[C]; + NEXTOP; + OP(MINU_RK) : + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = (unsigned)reg.d[B] < (unsigned)konstd[C] ? reg.d[B] : konstd[C]; + NEXTOP; + OP(MAXU_RR) : + ASSERTD(a); ASSERTD(B); ASSERTD(C); + reg.d[a] = (unsigned)reg.d[B] > (unsigned)reg.d[C] ? reg.d[B] : reg.d[C]; + NEXTOP; + OP(MAXU_RK) : + ASSERTD(a); ASSERTD(B); ASSERTKD(C); + reg.d[a] = (unsigned)reg.d[B] > (unsigned)konstd[C] ? reg.d[B] : konstd[C]; + NEXTOP; + + OP(ABS): + ASSERTD(a); ASSERTD(B); + reg.d[a] = abs(reg.d[B]); + NEXTOP; + + OP(NEG): + ASSERTD(a); ASSERTD(B); + reg.d[a] = -reg.d[B]; + NEXTOP; + + OP(NOT): + ASSERTD(a); ASSERTD(B); + reg.d[a] = ~reg.d[B]; + NEXTOP; + + OP(EQ_R): + ASSERTD(B); ASSERTD(C); + CMPJMP(reg.d[B] == reg.d[C]); + NEXTOP; + OP(EQ_K): + ASSERTD(B); ASSERTKD(C); + CMPJMP(reg.d[B] == konstd[C]); + NEXTOP; + OP(LT_RR): + ASSERTD(B); ASSERTD(C); + CMPJMP(reg.d[B] < reg.d[C]); + NEXTOP; + OP(LT_RK): + ASSERTD(B); ASSERTKD(C); + CMPJMP(reg.d[B] < konstd[C]); + NEXTOP; + OP(LT_KR): + ASSERTKD(B); ASSERTD(C); + CMPJMP(konstd[B] < reg.d[C]); + NEXTOP; + OP(LE_RR): + ASSERTD(B); ASSERTD(C); + CMPJMP(reg.d[B] <= reg.d[C]); + NEXTOP; + OP(LE_RK): + ASSERTD(B); ASSERTKD(C); + CMPJMP(reg.d[B] <= konstd[C]); + NEXTOP; + OP(LE_KR): + ASSERTKD(B); ASSERTD(C); + CMPJMP(konstd[B] <= reg.d[C]); + NEXTOP; + OP(LTU_RR): + ASSERTD(B); ASSERTD(C); + CMPJMP((VM_UWORD)reg.d[B] < (VM_UWORD)reg.d[C]); + NEXTOP; + OP(LTU_RK): + ASSERTD(B); ASSERTKD(C); + CMPJMP((VM_UWORD)reg.d[B] < (VM_UWORD)konstd[C]); + NEXTOP; + OP(LTU_KR): + ASSERTKD(B); ASSERTD(C); + CMPJMP((VM_UWORD)konstd[B] < (VM_UWORD)reg.d[C]); + NEXTOP; + OP(LEU_RR): + ASSERTD(B); ASSERTD(C); + CMPJMP((VM_UWORD)reg.d[B] <= (VM_UWORD)reg.d[C]); + NEXTOP; + OP(LEU_RK): + ASSERTD(B); ASSERTKD(C); + CMPJMP((VM_UWORD)reg.d[B] <= (VM_UWORD)konstd[C]); + NEXTOP; + OP(LEU_KR): + ASSERTKD(B); ASSERTD(C); + CMPJMP((VM_UWORD)konstd[B] <= (VM_UWORD)reg.d[C]); + NEXTOP; + + OP(ADDF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = reg.f[B] + reg.f[C]; + NEXTOP; + OP(ADDF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + reg.f[a] = reg.f[B] + konstf[C]; + NEXTOP; + + OP(SUBF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = reg.f[B] - reg.f[C]; + NEXTOP; + OP(SUBF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + reg.f[a] = reg.f[B] - konstf[C]; + NEXTOP; + OP(SUBF_KR): + ASSERTF(a); ASSERTKF(B); ASSERTF(C); + reg.f[a] = konstf[B] - reg.f[C]; + NEXTOP; + + OP(MULF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = reg.f[B] * reg.f[C]; + NEXTOP; + OP(MULF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + reg.f[a] = reg.f[B] * konstf[C]; + NEXTOP; + + OP(DIVF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + if (reg.f[C] == 0.) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.f[a] = reg.f[B] / reg.f[C]; + NEXTOP; + OP(DIVF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + if (konstf[C] == 0.) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.f[a] = reg.f[B] / konstf[C]; + NEXTOP; + OP(DIVF_KR): + ASSERTF(a); ASSERTKF(B); ASSERTF(C); + if (reg.f[C] == 0.) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.f[a] = konstf[B] / reg.f[C]; + NEXTOP; + + OP(MODF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + fb = reg.f[B]; fc = reg.f[C]; + Do_MODF: + if (fc == 0.) + { + ThrowAbortException(X_DIVISION_BY_ZERO, nullptr); + return 0; + } + reg.f[a] = luai_nummod(fb, fc); + NEXTOP; + OP(MODF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + fb = reg.f[B]; fc = konstf[C]; + goto Do_MODF; + NEXTOP; + OP(MODF_KR): + ASSERTF(a); ASSERTKF(B); ASSERTF(C); + fb = konstf[B]; fc = reg.f[C]; + goto Do_MODF; + NEXTOP; + + OP(POWF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = g_pow(reg.f[B], reg.f[C]); + NEXTOP; + OP(POWF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + reg.f[a] = g_pow(reg.f[B], konstf[C]); + NEXTOP; + OP(POWF_KR): + ASSERTF(a); ASSERTKF(B); ASSERTF(C); + reg.f[a] = g_pow(konstf[B], reg.f[C]); + NEXTOP; + + OP(MINF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = reg.f[B] < reg.f[C] ? reg.f[B] : reg.f[C]; + NEXTOP; + OP(MINF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + reg.f[a] = reg.f[B] < konstf[C] ? reg.f[B] : konstf[C]; + NEXTOP; + OP(MAXF_RR): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = reg.f[B] > reg.f[C] ? reg.f[B] : reg.f[C]; + NEXTOP; + OP(MAXF_RK): + ASSERTF(a); ASSERTF(B); ASSERTKF(C); + reg.f[a] = reg.f[B] > konstf[C] ? reg.f[B] : konstf[C]; + NEXTOP; + + OP(ATAN2): + ASSERTF(a); ASSERTF(B); ASSERTF(C); + reg.f[a] = g_atan2(reg.f[B], reg.f[C]) * (180 / M_PI); + NEXTOP; + + OP(FLOP): + ASSERTF(a); ASSERTF(B); + fb = reg.f[B]; + reg.f[a] = (C == FLOP_ABS) ? fabs(fb) : (C == FLOP_NEG) ? -fb : DoFLOP(C, fb); + NEXTOP; + + OP(EQF_R): + ASSERTF(B); ASSERTF(C); + if (a & CMP_APPROX) + { + CMPJMP(fabs(reg.f[C] - reg.f[B]) < VM_EPSILON); + } + else + { + CMPJMP(reg.f[C] == reg.f[B]); + } + NEXTOP; + OP(EQF_K): + ASSERTF(B); ASSERTKF(C); + if (a & CMP_APPROX) + { + CMPJMP(fabs(konstf[C] - reg.f[B]) < VM_EPSILON); + } + else + { + CMPJMP(konstf[C] == reg.f[B]); + } + NEXTOP; + OP(LTF_RR): + ASSERTF(B); ASSERTF(C); + if (a & CMP_APPROX) + { + CMPJMP((reg.f[B] - reg.f[C]) < -VM_EPSILON); + } + else + { + CMPJMP(reg.f[B] < reg.f[C]); + } + NEXTOP; + OP(LTF_RK): + ASSERTF(B); ASSERTKF(C); + if (a & CMP_APPROX) + { + CMPJMP((reg.f[B] - konstf[C]) < -VM_EPSILON); + } + else + { + CMPJMP(reg.f[B] < konstf[C]); + } + NEXTOP; + OP(LTF_KR): + ASSERTKF(B); ASSERTF(C); + if (a & CMP_APPROX) + { + CMPJMP((konstf[B] - reg.f[C]) < -VM_EPSILON); + } + else + { + CMPJMP(konstf[B] < reg.f[C]); + } + NEXTOP; + OP(LEF_RR): + ASSERTF(B); ASSERTF(C); + if (a & CMP_APPROX) + { + CMPJMP((reg.f[B] - reg.f[C]) <= -VM_EPSILON); + } + else + { + CMPJMP(reg.f[B] <= reg.f[C]); + } + NEXTOP; + OP(LEF_RK): + ASSERTF(B); ASSERTKF(C); + if (a & CMP_APPROX) + { + CMPJMP((reg.f[B] - konstf[C]) <= -VM_EPSILON); + } + else + { + CMPJMP(reg.f[B] <= konstf[C]); + } + NEXTOP; + OP(LEF_KR): + ASSERTKF(B); ASSERTF(C); + if (a & CMP_APPROX) + { + CMPJMP((konstf[B] - reg.f[C]) <= -VM_EPSILON); + } + else + { + CMPJMP(konstf[B] <= reg.f[C]); + } + NEXTOP; + + OP(NEGV2): + ASSERTF(a+1); ASSERTF(B+1); + reg.f[a] = -reg.f[B]; + reg.f[a+1] = -reg.f[B+1]; + NEXTOP; + + OP(ADDV2_RR): + ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C+1); + fcp = ®.f[C]; + fbp = ®.f[B]; + reg.f[a] = fbp[0] + fcp[0]; + reg.f[a+1] = fbp[1] + fcp[1]; + NEXTOP; + + OP(SUBV2_RR): + ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C+1); + fbp = ®.f[B]; + fcp = ®.f[C]; + reg.f[a] = fbp[0] - fcp[0]; + reg.f[a+1] = fbp[1] - fcp[1]; + NEXTOP; + + OP(DOTV2_RR): + ASSERTF(a); ASSERTF(B+1); ASSERTF(C+1); + reg.f[a] = reg.f[B] * reg.f[C] + reg.f[B+1] * reg.f[C+1]; + NEXTOP; + + OP(MULVF2_RR): + ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C); + fc = reg.f[C]; + fbp = ®.f[B]; + Do_MULV2: + reg.f[a] = fbp[0] * fc; + reg.f[a+1] = fbp[1] * fc; + NEXTOP; + OP(MULVF2_RK): + ASSERTF(a+1); ASSERTF(B+1); ASSERTKF(C); + fc = konstf[C]; + fbp = ®.f[B]; + goto Do_MULV2; + + OP(DIVVF2_RR): + ASSERTF(a+1); ASSERTF(B+1); ASSERTF(C); + fc = reg.f[C]; + fbp = ®.f[B]; + Do_DIVV2: + reg.f[a] = fbp[0] / fc; + reg.f[a+1] = fbp[1] / fc; + NEXTOP; + OP(DIVVF2_RK): + ASSERTF(a+1); ASSERTF(B+1); ASSERTKF(C); + fc = konstf[C]; + fbp = ®.f[B]; + goto Do_DIVV2; + + OP(LENV2): + ASSERTF(a); ASSERTF(B+1); + reg.f[a] = g_sqrt(reg.f[B] * reg.f[B] + reg.f[B+1] * reg.f[B+1]); + NEXTOP; + + OP(EQV2_R): + ASSERTF(B+1); ASSERTF(C+1); + fcp = ®.f[C]; + Do_EQV2: + if (a & CMP_APPROX) + { + CMPJMP(fabs(reg.f[B ] - fcp[0]) < VM_EPSILON && + fabs(reg.f[B+1] - fcp[1]) < VM_EPSILON); + } + else + { + CMPJMP(reg.f[B] == fcp[0] && reg.f[B+1] == fcp[1]); + } + NEXTOP; + OP(EQV2_K): + ASSERTF(B+1); ASSERTKF(C+1); + fcp = &konstf[C]; + goto Do_EQV2; + + OP(NEGV3): + ASSERTF(a+2); ASSERTF(B+2); + reg.f[a] = -reg.f[B]; + reg.f[a+1] = -reg.f[B+1]; + reg.f[a+2] = -reg.f[B+2]; + NEXTOP; + + OP(ADDV3_RR): + ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2); + fcp = ®.f[C]; + fbp = ®.f[B]; + reg.f[a] = fbp[0] + fcp[0]; + reg.f[a+1] = fbp[1] + fcp[1]; + reg.f[a+2] = fbp[2] + fcp[2]; + NEXTOP; + + OP(SUBV3_RR): + ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2); + fbp = ®.f[B]; + fcp = ®.f[C]; + reg.f[a] = fbp[0] - fcp[0]; + reg.f[a+1] = fbp[1] - fcp[1]; + reg.f[a+2] = fbp[2] - fcp[2]; + NEXTOP; + + OP(DOTV3_RR): + ASSERTF(a); ASSERTF(B+2); ASSERTF(C+2); + reg.f[a] = reg.f[B] * reg.f[C] + reg.f[B+1] * reg.f[C+1] + reg.f[B+2] * reg.f[C+2]; + NEXTOP; + + OP(CROSSV_RR): + ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C+2); + fbp = ®.f[B]; + fcp = ®.f[C]; + { + double t[3]; + t[2] = fbp[0] * fcp[1] - fbp[1] * fcp[0]; + t[1] = fbp[2] * fcp[0] - fbp[0] * fcp[2]; + t[0] = fbp[1] * fcp[2] - fbp[2] * fcp[1]; + reg.f[a] = t[0]; reg.f[a+1] = t[1]; reg.f[a+2] = t[2]; + } + NEXTOP; + + OP(MULVF3_RR): + ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C); + fc = reg.f[C]; + fbp = ®.f[B]; + Do_MULV3: + reg.f[a] = fbp[0] * fc; + reg.f[a+1] = fbp[1] * fc; + reg.f[a+2] = fbp[2] * fc; + NEXTOP; + OP(MULVF3_RK): + ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C); + fc = konstf[C]; + fbp = ®.f[B]; + goto Do_MULV3; + + OP(DIVVF3_RR): + ASSERTF(a+2); ASSERTF(B+2); ASSERTF(C); + fc = reg.f[C]; + fbp = ®.f[B]; + Do_DIVV3: + reg.f[a] = fbp[0] / fc; + reg.f[a+1] = fbp[1] / fc; + reg.f[a+2] = fbp[2] / fc; + NEXTOP; + OP(DIVVF3_RK): + ASSERTF(a+2); ASSERTF(B+2); ASSERTKF(C); + fc = konstf[C]; + fbp = ®.f[B]; + goto Do_DIVV3; + + OP(LENV3): + ASSERTF(a); ASSERTF(B+2); + reg.f[a] = g_sqrt(reg.f[B] * reg.f[B] + reg.f[B+1] * reg.f[B+1] + reg.f[B+2] * reg.f[B+2]); + NEXTOP; + + OP(EQV3_R): + ASSERTF(B+2); ASSERTF(C+2); + fcp = ®.f[C]; + Do_EQV3: + if (a & CMP_APPROX) + { + CMPJMP(fabs(reg.f[B ] - fcp[0]) < VM_EPSILON && + fabs(reg.f[B+1] - fcp[1]) < VM_EPSILON && + fabs(reg.f[B+2] - fcp[2]) < VM_EPSILON); + } + else + { + CMPJMP(reg.f[B] == fcp[0] && reg.f[B+1] == fcp[1] && reg.f[B+2] == fcp[2]); + } + NEXTOP; + OP(EQV3_K): + ASSERTF(B+2); ASSERTKF(C+2); + fcp = &konstf[C]; + goto Do_EQV3; + + OP(ADDA_RR): + ASSERTA(a); ASSERTA(B); ASSERTD(C); + c = reg.d[C]; + Do_ADDA: + if (reg.a[B] == NULL) // Leave NULL pointers as NULL pointers + { + c = 0; + } + reg.a[a] = (VM_UBYTE *)reg.a[B] + c; + NEXTOP; + OP(ADDA_RK): + ASSERTA(a); ASSERTA(B); ASSERTKD(C); + c = konstd[C]; + goto Do_ADDA; + + OP(SUBA): + ASSERTD(a); ASSERTA(B); ASSERTA(C); + reg.d[a] = (VM_UWORD)((VM_UBYTE *)reg.a[B] - (VM_UBYTE *)reg.a[C]); + NEXTOP; + + OP(EQA_R): + ASSERTA(B); ASSERTA(C); + CMPJMP(reg.a[B] == reg.a[C]); + NEXTOP; + OP(EQA_K): + ASSERTA(B); ASSERTKA(C); + CMPJMP(reg.a[B] == konsta[C].v); + NEXTOP; + + OP(NOP): + NEXTOP; + } + } +#if 0 + catch(VMException *exception) + { + // Try to find a handler for the exception. + PClass *extype = exception->GetClass(); + + while(--try_depth >= 0) + { + pc = exception_frames[try_depth]; + assert(pc->op == OP_CATCH); + while (pc->a > 1) + { + // CATCH must be followed by JMP if it doesn't terminate a catch chain. + assert(pc[1].op == OP_JMP); + + PClass *type; + int b = pc->b; + + if (pc->a == 2) + { + ASSERTA(b); + type = (PClass *)reg.a[b]; + } + else + { + assert(pc->a == 3); + ASSERTKA(b); + type = (PClass *)konsta[b].o; + } + ASSERTA(pc->c); + if (type == extype) + { + // Found a handler. Store the exception in pC, skip the JMP, + // and begin executing its code. + reg.a[pc->c] = exception; + pc += 2; + goto begin; + } + // This catch didn't handle it. Try the next one. + pc += 1 + JMPOFS(pc + 1); + assert(pc->op == OP_CATCH); + } + if (pc->a == 1) + { + // Catch any type of VMException. This terminates the chain. + ASSERTA(pc->c); + reg.a[pc->c] = exception; + pc += 1; + goto begin; + } + // This frame failed. Try the next one out. + } + // Nothing caught it. Rethrow and let somebody else deal with it. + throw; + } +#endif + catch (CVMAbortException &err) + { + err.MaybePrintMessage(); + err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(pc)); + // PrintParameters(reg.param + f->NumParam - B, B); + throw; + } + return 0; +} + + +static double DoFLOP(int flop, double v) +{ + switch(flop) + { + case FLOP_ABS: return fabs(v); + case FLOP_NEG: return -v; + case FLOP_EXP: return g_exp(v); + case FLOP_LOG: return g_log(v); + case FLOP_LOG10: return g_log10(v); + case FLOP_SQRT: return g_sqrt(v); + case FLOP_CEIL: return ceil(v); + case FLOP_FLOOR: return floor(v); + + case FLOP_ACOS: return g_acos(v); + case FLOP_ASIN: return g_asin(v); + case FLOP_ATAN: return g_atan(v); + case FLOP_COS: return g_cos(v); + case FLOP_SIN: return g_sin(v); + case FLOP_TAN: return g_tan(v); + + case FLOP_ACOS_DEG: return g_acos(v) * (180 / M_PI); + case FLOP_ASIN_DEG: return g_asin(v) * (180 / M_PI); + case FLOP_ATAN_DEG: return g_atan(v) * (180 / M_PI); + case FLOP_COS_DEG: return g_cosdeg(v); + case FLOP_SIN_DEG: return g_sindeg(v); + case FLOP_TAN_DEG: return g_tan(v * (M_PI / 180)); + + case FLOP_COSH: return g_cosh(v); + case FLOP_SINH: return g_sinh(v); + case FLOP_TANH: return g_tanh(v); + + case FLOP_ROUND: return round(v); + } + assert(0); + return 0; +} + +static void DoCast(const VMRegisters ®, const VMFrame *f, int a, int b, int cast) +{ + switch (cast) + { + case CAST_I2F: + ASSERTF(a); ASSERTD(b); + reg.f[a] = reg.d[b]; + break; + case CAST_U2F: + ASSERTF(a); ASSERTD(b); + reg.f[a] = unsigned(reg.d[b]); + break; + case CAST_I2S: + ASSERTS(a); ASSERTD(b); + reg.s[a].Format("%d", reg.d[b]); + break; + case CAST_U2S: + ASSERTS(a); ASSERTD(b); + reg.s[a].Format("%u", reg.d[b]); + break; + + case CAST_F2I: + ASSERTD(a); ASSERTF(b); + reg.d[a] = (int)reg.f[b]; + break; + case CAST_F2U: + ASSERTD(a); ASSERTF(b); + reg.d[a] = (int)(unsigned)reg.f[b]; + break; + case CAST_F2S: + ASSERTS(a); ASSERTF(b); + reg.s[a].Format("%.5f", reg.f[b]); // keep this small. For more precise conversion there should be a conversion function. + break; + case CAST_V22S: + ASSERTS(a); ASSERTF(b+1); + reg.s[a].Format("(%.5f, %.5f)", reg.f[b], reg.f[b + 1]); + break; + case CAST_V32S: + ASSERTS(a); ASSERTF(b + 2); + reg.s[a].Format("(%.5f, %.5f, %.5f)", reg.f[b], reg.f[b + 1], reg.f[b + 2]); + break; + + case CAST_P2S: + { + ASSERTS(a); ASSERTA(b); + if (reg.a[b] == nullptr) reg.s[a] = "null"; + else reg.s[a].Format("%p", reg.a[b]); + break; + } + + case CAST_S2I: + ASSERTD(a); ASSERTS(b); + reg.d[a] = (VM_SWORD)reg.s[b].ToLong(); + break; + case CAST_S2F: + ASSERTF(a); ASSERTS(b); + reg.f[a] = reg.s[b].ToDouble(); + break; + + case CAST_S2N: + ASSERTD(a); ASSERTS(b); + reg.d[a] = reg.s[b].Len() == 0? NAME_None : FName(reg.s[b]).GetIndex(); + break; + + case CAST_N2S: + { + ASSERTS(a); ASSERTD(b); + FName name = FName(ENamedName(reg.d[b])); + reg.s[a] = name.IsValidName() ? name.GetChars() : ""; + break; + } + + case CAST_S2Co: + ASSERTD(a); ASSERTS(b); + reg.d[a] = V_GetColor(NULL, reg.s[b]); + break; + + case CAST_Co2S: + ASSERTS(a); ASSERTD(b); + reg.s[a].Format("%02x %02x %02x", PalEntry(reg.d[b]).r, PalEntry(reg.d[b]).g, PalEntry(reg.d[b]).b); + break; + + case CAST_S2So: + ASSERTD(a); ASSERTS(b); + reg.d[a] = FSoundID(reg.s[b]); + break; + + case CAST_So2S: + ASSERTS(a); ASSERTD(b); + reg.s[a] = soundEngine->GetSoundName(reg.d[b]); + break; + + case CAST_SID2S: + ASSERTS(a); ASSERTD(b); + reg.s[a] = "";// unsigned(reg.d[b]) >= sprites.Size() ? "TNT1" : sprites[reg.d[b]].name; + break; + + case CAST_TID2S: + { + ASSERTS(a); ASSERTD(b); + auto tex = TexMan.GetTexture(*(FTextureID*)&(reg.d[b])); + reg.s[a] = tex == nullptr ? "(null)" : tex->GetName().GetChars(); + break; + } + + default: + assert(0); + } +} + +//=========================================================================== +// +// FillReturns +// +// Fills in an array of pointers to locations to store return values in. +// +//=========================================================================== + +static void FillReturns(const VMRegisters ®, VMFrame *frame, VMReturn *returns, const VMOP *retval, int numret) +{ + int i, type, regnum; + VMReturn *ret; + + assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3); + + for (i = 0, ret = returns; i < numret; ++i, ++ret, ++retval) + { + assert(retval->op == OP_RESULT); // opcode + ret->RegType = type = retval->b; + regnum = retval->c; + assert(!(type & REGT_KONST)); + type &= REGT_TYPE; + if (type < REGT_STRING) + { + if (type == REGT_INT) + { + assert(regnum < frame->NumRegD); + ret->Location = ®.d[regnum]; + } + else // type == REGT_FLOAT + { + assert(regnum < frame->NumRegF); + ret->Location = ®.f[regnum]; + } + } + else if (type == REGT_STRING) + { + assert(regnum < frame->NumRegS); + ret->Location = ®.s[regnum]; + } + else + { + assert(type == REGT_POINTER); + assert(regnum < frame->NumRegA); + ret->Location = ®.a[regnum]; + } + } +} + +//=========================================================================== +// +// SetReturn +// +// Used by script code to set a return value. +// +//=========================================================================== + +static void SetReturn(const VMRegisters ®, VMFrame *frame, VMReturn *ret, VM_UBYTE regtype, int regnum) +{ + const void *src; + VMScriptFunction *func = static_cast(frame->Func); + + assert(func != NULL && !(func->VarFlags & VARF_Native)); + assert((regtype & ~REGT_KONST) == ret->RegType); + + switch (regtype & REGT_TYPE) + { + case REGT_INT: + assert(!(regtype & REGT_MULTIREG)); + if (regtype & REGT_KONST) + { + assert(regnum < func->NumKonstD); + src = &func->KonstD[regnum]; + } + else + { + assert(regnum < frame->NumRegD); + src = ®.d[regnum]; + } + ret->SetInt(*(int *)src); + break; + + case REGT_FLOAT: + if (regtype & REGT_KONST) + { + assert(regnum < func->NumKonstF); + src = &func->KonstF[regnum]; + } + else + { + assert(regnum < frame->NumRegF); + src = ®.f[regnum]; + } + if (regtype & REGT_MULTIREG3) + { + ret->SetVector((double *)src); + } + else if (regtype & REGT_MULTIREG2) + { + ret->SetVector2((double *)src); + } + else + { + ret->SetFloat(*(double *)src); + } + break; + + case REGT_STRING: + assert(!(regtype & REGT_MULTIREG)); + if (regtype & REGT_KONST) + { + assert(regnum < func->NumKonstS); + src = &func->KonstS[regnum]; + } + else + { + assert(regnum < frame->NumRegS); + src = ®.s[regnum]; + } + ret->SetString(*(const FString *)src); + break; + + case REGT_POINTER: + assert(!(regtype & REGT_MULTIREG)); + if (regtype & REGT_KONST) + { + assert(regnum < func->NumKonstA); + ret->SetPointer(func->KonstA[regnum].v); + } + else + { + assert(regnum < frame->NumRegA); + ret->SetPointer(reg.a[regnum]); + } + break; + } +} + +static int Exec(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret) +{ + VMCalls[0]++; + VMFrameStack *stack = &GlobalVMStack; + VMFrame *newf = stack->AllocFrame(static_cast(func)); + VMFillParams(params, newf, numparams); + try + { + numret = ExecScriptFunc(stack, ret, numret); + } + catch (...) + { + stack->PopFrame(); + throw; + } + stack->PopFrame(); + return numret; +} diff --git a/source/common/scripting/vm/vmframe.cpp b/source/common/scripting/vm/vmframe.cpp new file mode 100644 index 000000000..7c2a63b1f --- /dev/null +++ b/source/common/scripting/vm/vmframe.cpp @@ -0,0 +1,771 @@ +/* +** vmframe.cpp +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#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::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(func))) + { + func->ScriptCall = JitCompile(static_cast(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(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(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(func)->NativeCall(VM_INVOKE(params, numparams, results, numresults, func->RegTypes)); + } + else + { + auto code = static_cast(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(func)->KonstD[0]); + return 1; + } + else + { + VMCycles[0].Clock(); + + auto sfunc = static_cast(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 ¶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(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 \n"); +} + diff --git a/source/common/scripting/vm/vmintern.h b/source/common/scripting/vm/vmintern.h new file mode 100644 index 000000000..b93f3e6ef --- /dev/null +++ b/source/common/scripting/vm/vmintern.h @@ -0,0 +1,483 @@ +#pragma once + +#include "vm.h" +#include + +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 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 SpecialInits; // list of all contents on the extra stack which require construction and destruction + + void InitExtra(void *addr); + void DestroyExtra(void *addr); + int AllocExtraStack(PType *type); + int PCToLine(const VMOP *pc); + +private: + static int FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret); +}; diff --git a/source/common/scripting/vm/vmops.h b/source/common/scripting/vm/vmops.h new file mode 100644 index 000000000..82b3cabc4 --- /dev/null +++ b/source/common/scripting/vm/vmops.h @@ -0,0 +1,258 @@ +#ifndef xx +#define xx(op, name, mode, alt, kreg, ktype) OP_##op, +#endif + +// first row is the opcode +// second row is the disassembly name +// third row is the disassembly flags +// fourth row is the alternative opcode if all 256 constant registers are exhausted. +// fifth row is the constant register index in the opcode +// sixth row is the constant register type. +// OP_PARAM and OP_CMPS need special treatment because they encode this information in the instruction. + +xx(NOP, nop, NOP, NOP, 0, 0) // no operation + +// Load constants. +xx(LI, li, LI, NOP, 0, 0) // load immediate signed 16-bit constant +xx(LK, lk, LKI, NOP, 0, 0) // load integer constant +xx(LKF, lk, LKF, NOP, 0, 0) // load float constant +xx(LKS, lk, LKS, NOP, 0, 0) // load string constant +xx(LKP, lk, LKP, NOP, 0, 0) // load pointer constant +xx(LK_R, lk, RIRII8, NOP, 0, 0) // load integer constant indexed +xx(LKF_R, lk, RFRII8, NOP, 0, 0) // load float constant indexed +xx(LKS_R, lk, RSRII8, NOP, 0, 0) // load string constant indexed +xx(LKP_R, lk, RPRII8, NOP, 0, 0) // load pointer constant indexed +xx(LFP, lf, LFP, NOP, 0, 0) // load frame pointer +xx(META, meta, RPRP, NOP, 0, 0) // load a class's meta data address +xx(CLSS, clss, RPRP, NOP, 0, 0) // load a class's descriptor address + +// Load from memory. rA = *(rB + rkC) +xx(LB, lb, RIRPKI, LB_R, 4, REGT_INT) // load byte +xx(LB_R, lb, RIRPRI, NOP, 0, 0) +xx(LH, lh, RIRPKI, LH_R, 4, REGT_INT) // load halfword +xx(LH_R, lh, RIRPRI, NOP, 0, 0) +xx(LW, lw, RIRPKI, LW_R, 4, REGT_INT) // load word +xx(LW_R, lw, RIRPRI, NOP, 0, 0) +xx(LBU, lbu, RIRPKI, LBU_R, 4, REGT_INT) // load byte unsigned +xx(LBU_R, lbu, RIRPRI, NOP, 0, 0) +xx(LHU, lhu, RIRPKI, LHU_R, 4, REGT_INT) // load halfword unsigned +xx(LHU_R, lhu, RIRPRI, NOP, 0, 0) +xx(LSP, lsp, RFRPKI, LSP_R, 4, REGT_INT) // load single-precision fp +xx(LSP_R, lsp, RFRPRI, NOP, 0, 0) +xx(LDP, ldp, RFRPKI, LDP_R, 4, REGT_INT) // load double-precision fp +xx(LDP_R, ldp, RFRPRI, NOP, 0, 0) +xx(LS, ls, RSRPKI, LS_R, 4, REGT_INT) // load string +xx(LS_R, ls, RSRPRI, NOP, 0, 0) +xx(LO, lo, RPRPKI, LO_R, 4, REGT_INT) // load object +xx(LO_R, lo, RPRPRI, NOP, 0, 0) +xx(LP, lp, RPRPKI, LP_R, 4, REGT_INT) // load pointer +xx(LP_R, lp, RPRPRI, NOP, 0, 0) +xx(LV2, lv2, RVRPKI, LV2_R, 4, REGT_INT) // load vector2 +xx(LV2_R, lv2, RVRPRI, NOP, 0, 0) +xx(LV3, lv3, RVRPKI, LV3_R, 4, REGT_INT) // load vector3 +xx(LV3_R, lv3, RVRPRI, NOP, 0, 0) +xx(LCS, lcs, RSRPKI, LCS_R, 4, REGT_INT) // load string from char ptr. +xx(LCS_R, lcs, RSRPRI, NOP, 0, 0) + +xx(LBIT, lbit, RIRPI8, NOP, 0, 0) // rA = !!(*rB & C) -- *rB is a byte + +// Store instructions. *(rA + rkC) = rB +xx(SB, sb, RPRIKI, SB_R, 4, REGT_INT) // store byte +xx(SB_R, sb, RPRIRI, NOP, 0, 0) +xx(SH, sh, RPRIKI, SH_R, 4, REGT_INT) // store halfword +xx(SH_R, sh, RPRIRI, NOP, 0, 0) +xx(SW, sw, RPRIKI, SW_R, 4, REGT_INT) // store word +xx(SW_R, sw, RPRIRI, NOP, 0, 0) +xx(SSP, ssp, RPRFKI, SSP_R, 4, REGT_INT) // store single-precision fp +xx(SSP_R, ssp, RPRFRI, NOP, 0, 0) +xx(SDP, sdp, RPRFKI, SDP_R, 4, REGT_INT) // store double-precision fp +xx(SDP_R, sdp, RPRFRI, NOP, 0, 0) +xx(SS, ss, RPRSKI, SS_R, 4, REGT_INT) // store string +xx(SS_R, ss, RPRSRI, NOP, 0, 0) +xx(SP, sp, RPRPKI, SP_R, 4, REGT_INT) // store pointer +xx(SP_R, sp, RPRPRI, NOP, 0, 0) +xx(SO, so, RPRPKI, SO_R, 4, REGT_INT) // store object pointer with write barrier (only needed for non thinkers and non types) +xx(SO_R, so, RPRPRI, NOP, 0, 0) +xx(SV2, sv2, RPRVKI, SV2_R, 4, REGT_INT) // store vector2 +xx(SV2_R, sv2, RPRVRI, NOP, 0, 0) +xx(SV3, sv3, RPRVKI, SV3_R, 4, REGT_INT) // store vector3 +xx(SV3_R, sv3, RPRVRI, NOP, 0, 0) + +xx(SBIT, sbit, RPRII8, NOP, 0, 0) // *rA |= C if rB is true, *rA &= ~C otherwise + +// Move instructions. +xx(MOVE, mov, RIRI, NOP, 0, 0) // dA = dB +xx(MOVEF, mov, RFRF, NOP, 0, 0) // fA = fB +xx(MOVES, mov, RSRS, NOP, 0, 0) // sA = sB +xx(MOVEA, mov, RPRP, NOP, 0, 0) // aA = aB +xx(MOVEV2, mov2, RFRF, NOP, 0, 0) // fA = fB (2 elements) +xx(MOVEV3, mov3, RFRF, NOP, 0, 0) // fA = fB (3 elements) +xx(CAST, cast, CAST, NOP, 0, 0) // xA = xB, conversion specified by C +xx(CASTB, castb, CAST, NOP, 0, 0) // xA = !!xB, type specified by C +xx(DYNCAST_R, dyncast, RPRPRP, NOP, 0, 0) // aA = dyn_cast(aB); +xx(DYNCAST_K, dyncast, RPRPKP, NOP, 0, 0) // aA = dyn_cast(aB); +xx(DYNCASTC_R, dyncastc, RPRPRP, NOP, 0, 0) // aA = dyn_cast(aB); for class types +xx(DYNCASTC_K, dyncastc, RPRPKP, NOP, 0, 0) // aA = dyn_cast(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 == ) then pc++ ; next instruction must JMP to another CATCH + // A == 3: (pkB == ) then pc++ ; next instruction must JMP to another CATCH + // for A > 0, exception is stored in pC +xx(BOUND, bound, RII16, NOP, 0, 0) // if rA < 0 or rA >= BC, throw exception +xx(BOUND_K, bound, LKI, NOP, 0, 0) // if rA < 0 or rA >= const[BC], throw exception +xx(BOUND_R, bound, RIRI, NOP, 0, 0) // if rA < 0 or rA >= rB, throw exception + +// String instructions. +xx(CONCAT, concat, RSRSRS, NOP, 0, 0) // sA = sB..sC +xx(LENS, lens, RIRS, NOP, 0, 0) // dA = sB.Length +xx(CMPS, cmps, I8RXRX, NOP, 0, 0) // if ((skB op skC) != (A & 1)) then pc++ + +// Integer math. +xx(SLL_RR, sll, RIRIRI, NOP, 0, 0) // dA = dkB << diC +xx(SLL_RI, sll, RIRII8, NOP, 0, 0) +xx(SLL_KR, sll, RIKIRI, SLL_RR, 2, REGT_INT) +xx(SRL_RR, srl, RIRIRI, NOP, 0, 0) // dA = dkB >> diC -- unsigned +xx(SRL_RI, srl, RIRII8, NOP, 0, 0) +xx(SRL_KR, srl, RIKIRI, SRL_RR, 2, REGT_INT) +xx(SRA_RR, sra, RIRIRI, NOP, 0, 0) // dA = dkB >> diC -- signed +xx(SRA_RI, sra, RIRII8, NOP, 0, 0) +xx(SRA_KR, sra, RIKIRI, SRA_RR, 2, REGT_INT) +xx(ADD_RR, add, RIRIRI, NOP, 0, 0) // dA = dB + dkC +xx(ADD_RK, add, RIRIKI, ADD_RR, 4, REGT_INT) +xx(ADDI, addi, RIRIIs, NOP, 0, 0) // dA = dB + C -- C is a signed 8-bit constant +xx(SUB_RR, sub, RIRIRI, NOP, 0, 0) // dA = dkB - dkC +xx(SUB_RK, sub, RIRIKI, SUB_RR, 4, REGT_INT) +xx(SUB_KR, sub, RIKIRI, SUB_RR, 2, REGT_INT) +xx(MUL_RR, mul, RIRIRI, NOP, 0, 0) // dA = dB * dkC +xx(MUL_RK, mul, RIRIKI, MUL_RR, 4, REGT_INT) +xx(DIV_RR, div, RIRIRI, NOP, 0, 0) // dA = dkB / dkC (signed) +xx(DIV_RK, div, RIRIKI, DIV_RR, 4, REGT_INT) +xx(DIV_KR, div, RIKIRI, DIV_RR, 2, REGT_INT) +xx(DIVU_RR, divu, RIRIRI, NOP, 0, 0) // dA = dkB / dkC (unsigned) +xx(DIVU_RK, divu, RIRIKI, DIVU_RR,4, REGT_INT) +xx(DIVU_KR, divu, RIKIRI, DIVU_RR,2, REGT_INT) +xx(MOD_RR, mod, RIRIRI, NOP, 0, 0) // dA = dkB % dkC (signed) +xx(MOD_RK, mod, RIRIKI, MOD_RR, 4, REGT_INT) +xx(MOD_KR, mod, RIKIRI, MOD_RR, 2, REGT_INT) +xx(MODU_RR, modu, RIRIRI, NOP, 0, 0) // dA = dkB % dkC (unsigned) +xx(MODU_RK, modu, RIRIKI, MODU_RR,4, REGT_INT) +xx(MODU_KR, modu, RIKIRI, MODU_RR,2, REGT_INT) +xx(AND_RR, and, RIRIRI, NOP, 0, 0) // dA = dB & dkC +xx(AND_RK, and, RIRIKI, AND_RR, 4, REGT_INT) +xx(OR_RR, or, RIRIRI, NOP, 0, 0) // dA = dB | dkC +xx(OR_RK, or, RIRIKI, OR_RR, 4, REGT_INT) +xx(XOR_RR, xor, RIRIRI, NOP, 0, 0) // dA = dB ^ dkC +xx(XOR_RK, xor, RIRIKI, XOR_RR, 4, REGT_INT) +xx(MIN_RR, min, RIRIRI, NOP, 0, 0) // dA = min(dB,dkC) +xx(MIN_RK, min, RIRIKI, MIN_RR, 4, REGT_INT) +xx(MAX_RR, max, RIRIRI, NOP, 0, 0) // dA = max(dB,dkC) +xx(MAX_RK, max, RIRIKI, MAX_RR, 4, REGT_INT) +xx(MINU_RR, minu, RIRIRI, NOP, 0, 0) // dA = min(dB,dkC) unsigned +xx(MINU_RK, minu, RIRIKI, MIN_RR, 4, REGT_INT) +xx(MAXU_RR, maxu, RIRIRI, NOP, 0, 0) // dA = max(dB,dkC) unsigned +xx(MAXU_RK, maxu, RIRIKI, MAX_RR, 4, REGT_INT) +xx(ABS, abs, RIRI, NOP, 0, 0) // dA = abs(dB) +xx(NEG, neg, RIRI, NOP, 0, 0) // dA = -dB +xx(NOT, not, RIRI, NOP, 0, 0) // dA = ~dB +xx(EQ_R, beq, CIRR, NOP, 0, 0) // if ((dB == dkC) != A) then pc++ +xx(EQ_K, beq, CIRK, EQ_R, 4, REGT_INT) +xx(LT_RR, blt, CIRR, NOP, 0, 0) // if ((dkB < dkC) != A) then pc++ +xx(LT_RK, blt, CIRK, LT_RR, 4, REGT_INT) +xx(LT_KR, blt, CIKR, LT_RR, 2, REGT_INT) +xx(LE_RR, ble, CIRR, NOP, 0, 0) // if ((dkB <= dkC) != A) then pc++ +xx(LE_RK, ble, CIRK, LE_RR, 4, REGT_INT) +xx(LE_KR, ble, CIKR, LE_RR, 2, REGT_INT) +xx(LTU_RR, bltu, CIRR, NOP, 0, 0) // if ((dkB < dkC) != A) then pc++ -- unsigned +xx(LTU_RK, bltu, CIRK, LTU_RR, 4, REGT_INT) +xx(LTU_KR, bltu, CIKR, LTU_RR, 2, REGT_INT) +xx(LEU_RR, bleu, CIRR, NOP, 0, 0) // if ((dkB <= dkC) != A) then pc++ -- unsigned +xx(LEU_RK, bleu, CIRK, LEU_RR, 4, REGT_INT) +xx(LEU_KR, bleu, CIKR, LEU_RR, 2, REGT_INT) + +// Double-precision floating point math. +xx(ADDF_RR, add, RFRFRF, NOP, 0, 0) // fA = fB + fkC +xx(ADDF_RK, add, RFRFKF, ADDF_RR,4, REGT_FLOAT) +xx(SUBF_RR, sub, RFRFRF, NOP, 0, 0) // fA = fkB - fkC +xx(SUBF_RK, sub, RFRFKF, SUBF_RR,4, REGT_FLOAT) +xx(SUBF_KR, sub, RFKFRF, SUBF_RR,2, REGT_FLOAT) +xx(MULF_RR, mul, RFRFRF, NOP, 0, 0) // fA = fB * fkC +xx(MULF_RK, mul, RFRFKF, MULF_RR,4, REGT_FLOAT) +xx(DIVF_RR, div, RFRFRF, NOP, 0, 0) // fA = fkB / fkC +xx(DIVF_RK, div, RFRFKF, DIVF_RR,4, REGT_FLOAT) +xx(DIVF_KR, div, RFKFRF, DIVF_RR,2, REGT_FLOAT) +xx(MODF_RR, mod, RFRFRF, NOP, 0, 0) // fA = fkB % fkC +xx(MODF_RK, mod, RFRFKF, MODF_RR,4, REGT_FLOAT) +xx(MODF_KR, mod, RFKFRF, MODF_RR,4, REGT_FLOAT) +xx(POWF_RR, pow, RFRFRF, NOP, 0, 0) // fA = fkB ** fkC +xx(POWF_RK, pow, RFRFKF, POWF_RR,4, REGT_FLOAT) +xx(POWF_KR, pow, RFKFRF, POWF_RR,2, REGT_FLOAT) +xx(MINF_RR, min, RFRFRF, NOP, 0, 0) // fA = min(fB)fkC) +xx(MINF_RK, min, RFRFKF, MINF_RR,4, REGT_FLOAT) +xx(MAXF_RR, max, RFRFRF, NOP, 0, 0) // fA = max(fB)fkC) +xx(MAXF_RK, max, RFRFKF, MAXF_RR,4, REGT_FLOAT) +xx(ATAN2, atan2, RFRFRF, NOP, 0, 0) // fA = atan2(fB,fC) result is in degrees +xx(FLOP, flop, RFRFI8, NOP, 0, 0) // fA = f(fB) where function is selected by C +xx(EQF_R, beq, CFRR, NOP, 0, 0) // if ((fB == fkC) != (A & 1)) then pc++ +xx(EQF_K, beq, CFRK, EQF_R, 4, REGT_FLOAT) +xx(LTF_RR, blt, CFRR, NOP, 0, 0) // if ((fkB < fkC) != (A & 1)) then pc++ +xx(LTF_RK, blt, CFRK, LTF_RR, 4, REGT_FLOAT) +xx(LTF_KR, blt, CFKR, LTF_RR, 2, REGT_FLOAT) +xx(LEF_RR, ble, CFRR, NOP, 0, 0) // if ((fkb <= fkC) != (A & 1)) then pc++ +xx(LEF_RK, ble, CFRK, LEF_RR, 4, REGT_FLOAT) +xx(LEF_KR, ble, CFKR, LEF_RR, 2, REGT_FLOAT) + +// Vector math. (2D) +xx(NEGV2, negv2, RVRV, NOP, 0, 0) // vA = -vB +xx(ADDV2_RR, addv2, RVRVRV, NOP, 0, 0) // vA = vB + vkC +xx(SUBV2_RR, subv2, RVRVRV, NOP, 0, 0) // vA = vkB - vkC +xx(DOTV2_RR, dotv2, RVRVRV, NOP, 0, 0) // va = vB dot vkC +xx(MULVF2_RR, mulv2, RVRVRF, NOP, 0, 0) // vA = vkB * fkC +xx(MULVF2_RK, mulv2, RVRVKF, MULVF2_RR,4, REGT_FLOAT) +xx(DIVVF2_RR, divv2, RVRVRF, NOP, 0, 0) // vA = vkB / fkC +xx(DIVVF2_RK, divv2, RVRVKF, DIVVF2_RR,4, REGT_FLOAT) +xx(LENV2, lenv2, RFRV, NOP, 0, 0) // fA = vB.Length +xx(EQV2_R, beqv2, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 32) +xx(EQV2_K, beqv2, CVRK, NOP, 0, 0) // this will never be used. + +// Vector math (3D) +xx(NEGV3, negv3, RVRV, NOP, 0, 0) // vA = -vB +xx(ADDV3_RR, addv3, RVRVRV, NOP, 0, 0) // vA = vB + vkC +xx(SUBV3_RR, subv3, RVRVRV, NOP, 0, 0) // vA = vkB - vkC +xx(DOTV3_RR, dotv3, RVRVRV, NOP, 0, 0) // va = vB dot vkC +xx(CROSSV_RR, crossv, RVRVRV, NOP, 0, 0) // vA = vkB cross vkC +xx(MULVF3_RR, mulv3, RVRVRF, NOP, 0, 0) // vA = vkB * fkC +xx(MULVF3_RK, mulv3, RVRVKF, MULVF3_RR,4, REGT_FLOAT) +xx(DIVVF3_RR, divv3, RVRVRF, NOP, 0, 0) // vA = vkB / fkC +xx(DIVVF3_RK, divv3, RVRVKF, DIVVF3_RR,4, REGT_FLOAT) +xx(LENV3, lenv3, RFRV, NOP, 0, 0) // fA = vB.Length +xx(EQV3_R, beqv3, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 33) +xx(EQV3_K, beqv3, CVRK, NOP, 0, 0) // this will never be used. + +// Pointer math. +xx(ADDA_RR, add, RPRPRI, NOP, 0, 0) // pA = pB + dkC +xx(ADDA_RK, add, RPRPKI, ADDA_RR,4, REGT_INT) +xx(SUBA, sub, RIRPRP, NOP, 0, 0) // dA = pB - pC +xx(EQA_R, beq, CPRR, NOP, 0, 0) // if ((pB == pkC) != A) then pc++ +xx(EQA_K, beq, CPRK, EQA_R, 4, REGT_POINTER) + +#undef xx diff --git a/source/common/utility/basics.h b/source/common/utility/basics.h index 1ae1f1764..f5ecdd1a2 100644 --- a/source/common/utility/basics.h +++ b/source/common/utility/basics.h @@ -1,5 +1,4 @@ -#ifndef __BASICS_H -#define __BASICS_H +#pragma once #include #include @@ -56,4 +55,52 @@ typedef uint32_t angle_t; using INTBOOL = int; using BITFIELD = uint32_t; + + +#if defined(_MSC_VER) +#define NOVTABLE __declspec(novtable) +#else +#define NOVTABLE #endif + +// always use our own definition for consistency. +#ifdef M_PI +#undef M_PI +#endif + +const double M_PI = 3.14159265358979323846; // matches value in gcc v2 math.h + +inline float DEG2RAD(float deg) +{ + return deg * float(M_PI / 180.0); +} + +inline double DEG2RAD(double deg) +{ + return deg * (M_PI / 180.0); +} + +inline float RAD2DEG(float deg) +{ + return deg * float(180. / M_PI); +} + + +// Auto-registration sections for GCC. +// Apparently, you cannot do string concatenation inside section attributes. +#ifdef __MACH__ +#define SECTION_AREG "__DATA,areg" +#define SECTION_CREG "__DATA,creg" +#define SECTION_FREG "__DATA,freg" +#define SECTION_GREG "__DATA,greg" +#define SECTION_MREG "__DATA,mreg" +#define SECTION_YREG "__DATA,yreg" +#else +#define SECTION_AREG "areg" +#define SECTION_CREG "creg" +#define SECTION_FREG "freg" +#define SECTION_GREG "greg" +#define SECTION_MREG "mreg" +#define SECTION_YREG "yreg" +#endif + diff --git a/source/core/raze_sound.h b/source/core/raze_sound.h index 4ee5ec22c..dc7c809cf 100644 --- a/source/core/raze_sound.h +++ b/source/core/raze_sound.h @@ -22,16 +22,6 @@ inline void FX_SetReverbDelay(int delay) { } -inline int S_FindSoundByResID(int ndx) -{ - return soundEngine->FindSoundByResID(ndx); -} - -inline int S_FindSound(const char* name) -{ - return soundEngine->FindSound(name); -} - int S_LookupSound(const char* fn); class FSerializer; void S_SerializeSounds(FSerializer& arc); diff --git a/source/core/serializer.cpp b/source/core/serializer.cpp index 2c0f11811..715103acb 100644 --- a/source/core/serializer.cpp +++ b/source/core/serializer.cpp @@ -43,6 +43,7 @@ #include "rapidjson/prettywriter.h" #include "rapidjson/document.h" #include "serializer.h" +#include "dobject.h" #include "filesystem.h" #include "v_font.h" #include "v_text.h" @@ -50,6 +51,9 @@ #include "utf8.h" #include "printf.h" #include "raze_sound.h" +#include "engineerrors.h" +#include "textures.h" +#include "texturemanager.h" bool save_full = false; @@ -145,10 +149,8 @@ struct FWriter PrettyWriter *mWriter2; TArray mInObject; rapidjson::StringBuffer mOutString; -#if 0 TArray mDObjects; TMap mObjectMap; -#endif FWriter(bool pretty) { @@ -287,9 +289,7 @@ struct FReader { TArray mObjects; rapidjson::Document mDoc; -#if 0 TArray 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(pic->UseType)); + int ut = static_cast(pic->GetUseType()); + arc.w->Int(ut); arc.w->EndArray(); } } @@ -1334,9 +1328,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTe } return arc; } -#endif -#if 0 //========================================================================== // // This never uses defval and instead uses 'null' as default @@ -1352,11 +1344,13 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje if (value != nullptr && !(value->ObjectFlags & (OF_EuthanizeMe | OF_Transient))) { int ndx; + /* if (value == WP_NOCHANGE) { ndx = -1; } else + */ { int *pndx = arc.w->mObjectMap.CheckKey(value); if (pndx != nullptr) @@ -1397,7 +1391,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje int index = val->GetInt(); if (index == -1) { - value = WP_NOCHANGE; + value = nullptr;// WP_NOCHANGE; } else { @@ -1429,7 +1423,6 @@ FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObje } return arc; } -#endif //========================================================================== // @@ -1519,6 +1512,54 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundI } +//========================================================================== +// +// almost, but not quite the same as the above. +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&clst, PClass **def) +{ + if (arc.isWriting()) + { + if (!arc.w->inObject() || def == nullptr || clst != *def) + { + arc.WriteKey(key); + if (clst == nullptr) + { + arc.w->Null(); + } + else + { + arc.w->String(clst->TypeName.GetChars()); + } + } + } + else + { + auto val = arc.r->FindKey(key); + if (val != nullptr) + { + if (val->IsString()) + { + clst = PClass::FindClass(UnicodeToString(val->GetString())); + } + else if (val->IsNull()) + { + clst = nullptr; + } + else + { + Printf(TEXTCOLOR_RED "string type expected for '%s'\n", key); + clst = nullptr; + arc.mErrors++; + } + } + } + return arc; + +} + //========================================================================== // // @@ -1561,6 +1602,29 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FString &pstr, FString } +//========================================================================== +// +// +// +//========================================================================== + +template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&font, FFont **def) +{ + if (arc.isWriting()) + { + FName n = font? font->GetName() : NAME_None; + return arc(key, n); + } + else + { + FName n = NAME_None; + arc(key, n); + font = n == NAME_None? nullptr : V_GetFont(n.GetChars()); + return arc; + } + +} + //========================================================================== // // Handler to retrieve a numeric value of any kind. @@ -1618,4 +1682,10 @@ FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &value, N return arc; } +#include "renderstyle.h" +FSerializer& Serialize(FSerializer& arc, const char* key, FRenderStyle& style, FRenderStyle* def) +{ + return arc.Array(key, &style.BlendOp, def ? &def->BlendOp : nullptr, 4); +} + SaveRecords saveRecords; diff --git a/source/core/serializer.h b/source/core/serializer.h index 24bb18c38..4a4199b33 100644 --- a/source/core/serializer.h +++ b/source/core/serializer.h @@ -14,9 +14,12 @@ extern bool save_full; struct FWriter; struct FReader; +class PClass; class FFont; class FSoundID; struct FRenderStyle; +class DObject; +class FTextureID; inline bool nullcmp(const void *buffer, size_t length) { @@ -181,13 +184,14 @@ FSerializer &Serialize(FSerializer &arc, const char *key, int16_t &value, int16_ FSerializer &Serialize(FSerializer &arc, const char *key, uint16_t &value, uint16_t *defval); FSerializer &Serialize(FSerializer &arc, const char *key, double &value, double *defval); FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTextureID *defval); +FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObject ** /*defval*/, bool *retcode = nullptr); FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *defval); FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def); FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def); FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def); -#if 0 template 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 -FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, TObjPtr *) -{ - Serialize(arc, key, value.o, nullptr); - return arc; -} - -template -FSerializer &Serialize(FSerializer &arc, const char *key, TObjPtr &value, T *) -{ - Serialize(arc, key, value.o, nullptr); - return arc; -} -#endif template FSerializer &Serialize(FSerializer &arc, const char *key, TArray &value, TArray *def) @@ -237,6 +227,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, TArray &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) {