mirror of
https://github.com/ZDoom/Raze.git
synced 2025-06-02 02:01:31 +00:00
- added a first bunch of ZScript code.
# Conflicts: # source/CMakeLists.txt # source/common/utility/basics.h # source/core/serializer.h
This commit is contained in:
parent
c1f7cf1c3a
commit
c9b2399cd0
34 changed files with 15258 additions and 109 deletions
771
source/common/scripting/vm/vmframe.cpp
Normal file
771
source/common/scripting/vm/vmframe.cpp
Normal file
|
@ -0,0 +1,771 @@
|
|||
/*
|
||||
** vmframe.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright -2016 Randy Heit
|
||||
** Copyright 2016 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <new>
|
||||
#include "dobject.h"
|
||||
#include "v_text.h"
|
||||
#include "stats.h"
|
||||
#include "c_dispatch.h"
|
||||
#include "templates.h"
|
||||
#include "vmintern.h"
|
||||
#include "types.h"
|
||||
#include "jit.h"
|
||||
#include "c_cvars.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef HAVE_VM_JIT
|
||||
CUSTOM_CVAR(Bool, vm_jit, true, CVAR_NOINITCALL)
|
||||
{
|
||||
Printf("You must restart " GAMENAME " for this change to take effect.\n");
|
||||
Printf("This cvar is currently not saved. You must specify it on the command line.");
|
||||
}
|
||||
#else
|
||||
CVAR(Bool, vm_jit, false, CVAR_NOINITCALL|CVAR_NOSET)
|
||||
FString JitCaptureStackTrace(int framesToSkip, bool includeNativeFrames) { return FString(); }
|
||||
void JitRelease() {}
|
||||
#endif
|
||||
|
||||
cycle_t VMCycles[10];
|
||||
int VMCalls[10];
|
||||
|
||||
#if 0
|
||||
IMPLEMENT_CLASS(VMException, false, false)
|
||||
#endif
|
||||
|
||||
TArray<VMFunction *> VMFunction::AllFunctions;
|
||||
|
||||
// Creates the register type list for a function.
|
||||
// Native functions only need this to assert their parameters in debug mode, script functions use this to load their registers from the VMValues.
|
||||
void VMFunction::CreateRegUse()
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
if (VarFlags & VARF_Native) return; // we do not need this for native functions in release builds.
|
||||
#endif
|
||||
int count = 0;
|
||||
if (!Proto)
|
||||
{
|
||||
if (RegTypes) return;
|
||||
Printf(TEXTCOLOR_ORANGE "Function without prototype needs register info manually set: %s\n", PrintableName.GetChars());
|
||||
return;
|
||||
}
|
||||
assert(Proto->isPrototype());
|
||||
|
||||
for (auto arg : Proto->ArgumentTypes)
|
||||
{
|
||||
count += arg? arg->GetRegCount() : 1;
|
||||
}
|
||||
uint8_t *regp;
|
||||
RegTypes = regp = (uint8_t*)ClassDataAllocator.Alloc(count);
|
||||
count = 0;
|
||||
for (unsigned i = 0; i < Proto->ArgumentTypes.Size(); i++)
|
||||
{
|
||||
auto arg = Proto->ArgumentTypes[i];
|
||||
auto flg = ArgFlags.Size() > i ? ArgFlags[i] : 0;
|
||||
if (arg == nullptr)
|
||||
{
|
||||
// Marker for start of varargs.
|
||||
*regp++ = REGT_NIL;
|
||||
}
|
||||
else if ((flg & VARF_Out) && !arg->isPointer())
|
||||
{
|
||||
*regp++ = REGT_POINTER;
|
||||
}
|
||||
else for (int j = 0; j < arg->GetRegCount(); j++)
|
||||
{
|
||||
*regp++ = arg->GetRegType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VMScriptFunction::VMScriptFunction(FName name)
|
||||
{
|
||||
Name = name;
|
||||
LineInfo = nullptr;
|
||||
Code = NULL;
|
||||
KonstD = NULL;
|
||||
KonstF = NULL;
|
||||
KonstS = NULL;
|
||||
KonstA = NULL;
|
||||
LineInfoCount = 0;
|
||||
ExtraSpace = 0;
|
||||
CodeSize = 0;
|
||||
NumRegD = 0;
|
||||
NumRegF = 0;
|
||||
NumRegS = 0;
|
||||
NumRegA = 0;
|
||||
NumKonstD = 0;
|
||||
NumKonstF = 0;
|
||||
NumKonstS = 0;
|
||||
NumKonstA = 0;
|
||||
MaxParam = 0;
|
||||
NumArgs = 0;
|
||||
ScriptCall = &VMScriptFunction::FirstScriptCall;
|
||||
}
|
||||
|
||||
VMScriptFunction::~VMScriptFunction()
|
||||
{
|
||||
if (Code != NULL)
|
||||
{
|
||||
if (KonstS != NULL)
|
||||
{
|
||||
for (int i = 0; i < NumKonstS; ++i)
|
||||
{
|
||||
KonstS[i].~FString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers)
|
||||
{
|
||||
assert(Code == NULL);
|
||||
assert(numops > 0);
|
||||
assert(numkonstd >= 0 && numkonstd <= 65535);
|
||||
assert(numkonstf >= 0 && numkonstf <= 65535);
|
||||
assert(numkonsts >= 0 && numkonsts <= 65535);
|
||||
assert(numkonsta >= 0 && numkonsta <= 65535);
|
||||
assert(numlinenumbers >= 0 && numlinenumbers <= 65535);
|
||||
void *mem = ClassDataAllocator.Alloc(numops * sizeof(VMOP) +
|
||||
numkonstd * sizeof(int) +
|
||||
numkonstf * sizeof(double) +
|
||||
numkonsts * sizeof(FString) +
|
||||
numkonsta * sizeof(FVoidObj) +
|
||||
numlinenumbers * sizeof(FStatementInfo));
|
||||
Code = (VMOP *)mem;
|
||||
mem = (void *)((VMOP *)mem + numops);
|
||||
|
||||
if (numlinenumbers > 0)
|
||||
{
|
||||
LineInfo = (FStatementInfo*)mem;
|
||||
LineInfoCount = numlinenumbers;
|
||||
mem = LineInfo + numlinenumbers;
|
||||
}
|
||||
else
|
||||
{
|
||||
LineInfo = nullptr;
|
||||
LineInfoCount = 0;
|
||||
}
|
||||
if (numkonstd > 0)
|
||||
{
|
||||
KonstD = (int *)mem;
|
||||
mem = (void *)((int *)mem + numkonstd);
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstD = NULL;
|
||||
}
|
||||
if (numkonstf > 0)
|
||||
{
|
||||
KonstF = (double *)mem;
|
||||
mem = (void *)((double *)mem + numkonstf);
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstF = NULL;
|
||||
}
|
||||
if (numkonsts > 0)
|
||||
{
|
||||
KonstS = (FString *)mem;
|
||||
for (int i = 0; i < numkonsts; ++i)
|
||||
{
|
||||
::new(&KonstS[i]) FString;
|
||||
}
|
||||
mem = (void *)((FString *)mem + numkonsts);
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstS = NULL;
|
||||
}
|
||||
if (numkonsta > 0)
|
||||
{
|
||||
KonstA = (FVoidObj *)mem;
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstA = NULL;
|
||||
}
|
||||
CodeSize = numops;
|
||||
NumKonstD = numkonstd;
|
||||
NumKonstF = numkonstf;
|
||||
NumKonstS = numkonsts;
|
||||
NumKonstA = numkonsta;
|
||||
}
|
||||
|
||||
void VMScriptFunction::InitExtra(void *addr)
|
||||
{
|
||||
char *caddr = (char*)addr;
|
||||
|
||||
for (auto tao : SpecialInits)
|
||||
{
|
||||
tao.first->InitializeValue(caddr + tao.second, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void VMScriptFunction::DestroyExtra(void *addr)
|
||||
{
|
||||
char *caddr = (char*)addr;
|
||||
|
||||
for (auto tao : SpecialInits)
|
||||
{
|
||||
tao.first->DestroyValue(caddr + tao.second);
|
||||
}
|
||||
}
|
||||
|
||||
int VMScriptFunction::AllocExtraStack(PType *type)
|
||||
{
|
||||
int address = ((ExtraSpace + type->Align - 1) / type->Align) * type->Align;
|
||||
ExtraSpace = address + type->Size;
|
||||
type->SetDefaultValue(nullptr, address, &SpecialInits);
|
||||
return address;
|
||||
}
|
||||
|
||||
int VMScriptFunction::PCToLine(const VMOP *pc)
|
||||
{
|
||||
int PCIndex = int(pc - Code);
|
||||
if (LineInfoCount == 1) return LineInfo[0].LineNumber;
|
||||
for (unsigned i = 1; i < LineInfoCount; i++)
|
||||
{
|
||||
if (LineInfo[i].InstructionIndex > PCIndex)
|
||||
{
|
||||
return LineInfo[i - 1].LineNumber;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool CanJit(VMScriptFunction *func)
|
||||
{
|
||||
// Asmjit has a 256 register limit. Stay safely away from it as the jit compiler uses a few for temporaries as well.
|
||||
// Any function exceeding the limit will use the VM - a fair punishment to someone for writing a function so bloated ;)
|
||||
|
||||
int maxregs = 200;
|
||||
if (func->NumRegA + func->NumRegD + func->NumRegF + func->NumRegS < maxregs)
|
||||
return true;
|
||||
|
||||
Printf(TEXTCOLOR_ORANGE "%s is using too many registers (%d of max %d)! Function will not use native code.\n", func->PrintableName.GetChars(), func->NumRegA + func->NumRegD + func->NumRegF + func->NumRegS, maxregs);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int VMScriptFunction::FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret)
|
||||
{
|
||||
#ifdef HAVE_VM_JIT
|
||||
if (vm_jit && CanJit(static_cast<VMScriptFunction*>(func)))
|
||||
{
|
||||
func->ScriptCall = JitCompile(static_cast<VMScriptFunction*>(func));
|
||||
if (!func->ScriptCall)
|
||||
func->ScriptCall = VMExec;
|
||||
}
|
||||
else
|
||||
#endif // HAVE_VM_JIT
|
||||
{
|
||||
func->ScriptCall = VMExec;
|
||||
}
|
||||
|
||||
return func->ScriptCall(func, params, numparams, ret, numret);
|
||||
}
|
||||
|
||||
int VMNativeFunction::NativeScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *returns, int numret)
|
||||
{
|
||||
try
|
||||
{
|
||||
VMCycles[0].Unclock();
|
||||
numret = static_cast<VMNativeFunction *>(func)->NativeCall(VM_INVOKE(params, numparams, returns, numret, func->RegTypes));
|
||||
VMCycles[0].Clock();
|
||||
|
||||
return numret;
|
||||
}
|
||||
catch (CVMAbortException &err)
|
||||
{
|
||||
err.MaybePrintMessage();
|
||||
err.stacktrace.AppendFormat("Called from %s\n", func->PrintableName.GetChars());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrame :: InitRegS
|
||||
//
|
||||
// Initialize the string registers of a newly-allocated VMFrame.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void VMFrame::InitRegS()
|
||||
{
|
||||
FString *regs = GetRegS();
|
||||
for (int i = 0; i < NumRegS; ++i)
|
||||
{
|
||||
::new(®s[i]) FString;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack - Constructor
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrameStack::VMFrameStack()
|
||||
{
|
||||
Blocks = NULL;
|
||||
UnusedBlocks = NULL;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack - Destructor
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrameStack::~VMFrameStack()
|
||||
{
|
||||
while (PopFrame() != NULL)
|
||||
{ }
|
||||
if (Blocks != NULL)
|
||||
{
|
||||
BlockHeader *block, *next;
|
||||
for (block = Blocks; block != NULL; block = next)
|
||||
{
|
||||
next = block->NextBlock;
|
||||
delete[] (VM_UBYTE *)block;
|
||||
}
|
||||
Blocks = NULL;
|
||||
}
|
||||
if (UnusedBlocks != NULL)
|
||||
{
|
||||
BlockHeader *block, *next;
|
||||
for (block = UnusedBlocks; block != NULL; block = next)
|
||||
{
|
||||
next = block->NextBlock;
|
||||
delete[] (VM_UBYTE *)block;
|
||||
}
|
||||
UnusedBlocks = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: AllocFrame
|
||||
//
|
||||
// Allocates a frame from the stack suitable for calling a particular
|
||||
// function.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::AllocFrame(VMScriptFunction *func)
|
||||
{
|
||||
VMFrame *frame = Alloc(func->StackSize);
|
||||
frame->Func = func;
|
||||
frame->NumRegD = func->NumRegD;
|
||||
frame->NumRegF = func->NumRegF;
|
||||
frame->NumRegS = func->NumRegS;
|
||||
frame->NumRegA = func->NumRegA;
|
||||
frame->MaxParam = func->MaxParam;
|
||||
frame->Func = func;
|
||||
frame->InitRegS();
|
||||
if (func->SpecialInits.Size())
|
||||
{
|
||||
func->InitExtra(frame->GetExtra());
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: Alloc
|
||||
//
|
||||
// Allocates space for a frame. Its size will be rounded up to a multiple
|
||||
// of 16 bytes.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::Alloc(int size)
|
||||
{
|
||||
BlockHeader *block;
|
||||
VMFrame *frame, *parent;
|
||||
|
||||
size = (size + 15) & ~15;
|
||||
block = Blocks;
|
||||
if (block != NULL)
|
||||
{
|
||||
parent = block->LastFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent = NULL;
|
||||
}
|
||||
if (block == NULL || ((VM_UBYTE *)block + block->BlockSize) < (block->FreeSpace + size))
|
||||
{ // Not enough space. Allocate a new block.
|
||||
int blocksize = ((sizeof(BlockHeader) + 15) & ~15) + size;
|
||||
BlockHeader **blockp;
|
||||
if (blocksize < BLOCK_SIZE)
|
||||
{
|
||||
blocksize = BLOCK_SIZE;
|
||||
}
|
||||
for (blockp = &UnusedBlocks, block = *blockp; block != NULL; block = block->NextBlock)
|
||||
{
|
||||
if (block->BlockSize >= blocksize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block != NULL)
|
||||
{
|
||||
*blockp = block->NextBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
block = (BlockHeader *)new VM_UBYTE[blocksize];
|
||||
block->BlockSize = blocksize;
|
||||
}
|
||||
block->InitFreeSpace();
|
||||
block->LastFrame = NULL;
|
||||
block->NextBlock = Blocks;
|
||||
Blocks = block;
|
||||
}
|
||||
frame = (VMFrame *)block->FreeSpace;
|
||||
memset(frame, 0, size);
|
||||
frame->ParentFrame = parent;
|
||||
block->FreeSpace += size;
|
||||
block->LastFrame = frame;
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: PopFrame
|
||||
//
|
||||
// Pops the top frame off the stack, returning a pointer to the new top
|
||||
// frame.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::PopFrame()
|
||||
{
|
||||
if (Blocks == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
VMFrame *frame = Blocks->LastFrame;
|
||||
if (frame == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
auto Func = static_cast<VMScriptFunction *>(frame->Func);
|
||||
if (Func->SpecialInits.Size())
|
||||
{
|
||||
Func->DestroyExtra(frame->GetExtra());
|
||||
}
|
||||
// Free any string registers this frame had.
|
||||
FString *regs = frame->GetRegS();
|
||||
for (int i = frame->NumRegS; i != 0; --i)
|
||||
{
|
||||
(regs++)->~FString();
|
||||
}
|
||||
VMFrame *parent = frame->ParentFrame;
|
||||
if (parent == NULL)
|
||||
{
|
||||
// Popping the last frame off the stack.
|
||||
if (Blocks != NULL)
|
||||
{
|
||||
assert(Blocks->NextBlock == NULL);
|
||||
Blocks->LastFrame = NULL;
|
||||
Blocks->InitFreeSpace();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if ((VM_UBYTE *)parent < (VM_UBYTE *)Blocks || (VM_UBYTE *)parent >= (VM_UBYTE *)Blocks + Blocks->BlockSize)
|
||||
{ // Parent frame is in a different block, so move this one to the unused list.
|
||||
BlockHeader *next = Blocks->NextBlock;
|
||||
assert(next != NULL);
|
||||
assert((VM_UBYTE *)parent >= (VM_UBYTE *)next && (VM_UBYTE *)parent < (VM_UBYTE *)next + next->BlockSize);
|
||||
Blocks->NextBlock = UnusedBlocks;
|
||||
UnusedBlocks = Blocks;
|
||||
Blocks = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
Blocks->LastFrame = parent;
|
||||
Blocks->FreeSpace = (VM_UBYTE *)frame;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: Call
|
||||
//
|
||||
// Calls a function, either native or scripted. If an exception occurs while
|
||||
// executing, the stack is cleaned up. If trap is non-NULL, it is set to the
|
||||
// VMException that was caught and the return value is negative. Otherwise,
|
||||
// any caught exceptions will be rethrown. Under normal termination, the
|
||||
// return value is the number of results from the function.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
int VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap*/)
|
||||
{
|
||||
#if 0
|
||||
try
|
||||
#endif
|
||||
{
|
||||
if (func->VarFlags & VARF_Native)
|
||||
{
|
||||
return static_cast<VMNativeFunction *>(func)->NativeCall(VM_INVOKE(params, numparams, results, numresults, func->RegTypes));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto code = static_cast<VMScriptFunction *>(func)->Code;
|
||||
// handle empty functions consisting of a single return explicitly so that empty virtual callbacks do not need to set up an entire VM frame.
|
||||
// code cann be null here in case of some non-fatal DECORATE errors.
|
||||
if (code == nullptr || code->word == (0x00808000|OP_RET))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (code->word == (0x00048000|OP_RET))
|
||||
{
|
||||
if (numresults == 0) return 0;
|
||||
results[0].SetInt(static_cast<VMScriptFunction *>(func)->KonstD[0]);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
VMCycles[0].Clock();
|
||||
|
||||
auto sfunc = static_cast<VMScriptFunction *>(func);
|
||||
int numret = sfunc->ScriptCall(sfunc, params, numparams, results, numresults);
|
||||
VMCycles[0].Unclock();
|
||||
return numret;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
catch (VMException *exception)
|
||||
{
|
||||
if (trap != NULL)
|
||||
{
|
||||
*trap = exception;
|
||||
return -1;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int VMCallWithDefaults(VMFunction *func, TArray<VMValue> ¶ms, VMReturn *results, int numresults/*, VMException **trap = NULL*/)
|
||||
{
|
||||
if (func->DefaultArgs.Size() > params.Size())
|
||||
{
|
||||
auto oldp = params.Size();
|
||||
params.Resize(func->DefaultArgs.Size());
|
||||
for (unsigned i = oldp; i < params.Size(); i++)
|
||||
{
|
||||
params[i] = func->DefaultArgs[i];
|
||||
}
|
||||
}
|
||||
return VMCall(func, params.Data(), params.Size(), results, numresults);
|
||||
}
|
||||
|
||||
|
||||
// Exception stuff for the VM is intentionally placed there, because having this in vmexec.cpp would subject it to inlining
|
||||
// which we do not want because it increases the local stack requirements of Exec which are already too high.
|
||||
FString CVMAbortException::stacktrace;
|
||||
|
||||
CVMAbortException::CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap)
|
||||
{
|
||||
SetMessage("VM execution aborted: ");
|
||||
switch (reason)
|
||||
{
|
||||
case X_READ_NIL:
|
||||
AppendMessage("tried to read from address zero.");
|
||||
break;
|
||||
|
||||
case X_WRITE_NIL:
|
||||
AppendMessage("tried to write to address zero.");
|
||||
break;
|
||||
|
||||
case X_TOO_MANY_TRIES:
|
||||
AppendMessage("too many try-catch blocks.");
|
||||
break;
|
||||
|
||||
case X_ARRAY_OUT_OF_BOUNDS:
|
||||
AppendMessage("array access out of bounds.");
|
||||
break;
|
||||
|
||||
case X_DIVISION_BY_ZERO:
|
||||
AppendMessage("division by zero.");
|
||||
break;
|
||||
|
||||
case X_BAD_SELF:
|
||||
AppendMessage("invalid self pointer.");
|
||||
break;
|
||||
|
||||
case X_FORMAT_ERROR:
|
||||
AppendMessage("string format failed.");
|
||||
break;
|
||||
|
||||
case X_OTHER:
|
||||
// no prepended message.
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
size_t len = strlen(m_Message);
|
||||
mysnprintf(m_Message + len, MAX_ERRORTEXT - len, "Unknown reason %d", reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (moreinfo != nullptr)
|
||||
{
|
||||
AppendMessage(" ");
|
||||
size_t len = strlen(m_Message);
|
||||
myvsnprintf(m_Message + len, MAX_ERRORTEXT - len, moreinfo, ap);
|
||||
}
|
||||
|
||||
if (vm_jit)
|
||||
stacktrace = JitCaptureStackTrace(1, false);
|
||||
else
|
||||
stacktrace = "";
|
||||
}
|
||||
|
||||
// Print this only once on the first catch block.
|
||||
void CVMAbortException::MaybePrintMessage()
|
||||
{
|
||||
auto m = GetMessage();
|
||||
if (m != nullptr)
|
||||
{
|
||||
Printf(TEXTCOLOR_RED);
|
||||
Printf("%s\n", m);
|
||||
SetMessage("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, moreinfo);
|
||||
throw CVMAbortException(reason, moreinfo, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, moreinfo);
|
||||
|
||||
CVMAbortException err(reason, moreinfo, ap);
|
||||
|
||||
err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(line));
|
||||
throw err;
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(DObject, ThrowAbortException)
|
||||
{
|
||||
PARAM_PROLOGUE;
|
||||
FString s = FStringFormat(VM_ARGS_NAMES);
|
||||
ThrowAbortException(X_OTHER, s.GetChars());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NullParam(const char *varname)
|
||||
{
|
||||
ThrowAbortException(X_READ_NIL, "In function parameter %s", varname);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void ThrowVMException(VMException *x)
|
||||
{
|
||||
throw x;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void ClearGlobalVMStack()
|
||||
{
|
||||
while (GlobalVMStack.PopFrame() != nullptr)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ADD_STAT(VM)
|
||||
{
|
||||
double added = 0;
|
||||
int addedc = 0;
|
||||
double peak = 0;
|
||||
for (auto d : VMCycles)
|
||||
{
|
||||
added += d.TimeMS();
|
||||
peak = MAX<double>(peak, d.TimeMS());
|
||||
}
|
||||
for (auto d : VMCalls) addedc += d;
|
||||
memmove(&VMCycles[1], &VMCycles[0], 9 * sizeof(cycle_t));
|
||||
memmove(&VMCalls[1], &VMCalls[0], 9 * sizeof(int));
|
||||
VMCycles[0].Reset();
|
||||
VMCalls[0] = 0;
|
||||
return FStringf("VM time in last 10 tics: %f ms, %d calls, peak = %f ms", added, addedc, peak);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
CCMD(vmengine)
|
||||
{
|
||||
if (argv.argc() == 2)
|
||||
{
|
||||
if (stricmp(argv[1], "default") == 0)
|
||||
{
|
||||
VMSelectEngine(VMEngine_Default);
|
||||
return;
|
||||
}
|
||||
else if (stricmp(argv[1], "checked") == 0)
|
||||
{
|
||||
VMSelectEngine(VMEngine_Checked);
|
||||
return;
|
||||
}
|
||||
else if (stricmp(argv[1], "unchecked") == 0)
|
||||
{
|
||||
VMSelectEngine(VMEngine_Unchecked);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Printf("Usage: vmengine <default|checked|unchecked>\n");
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue