mirror of
https://github.com/ZDoom/Raze.git
synced 2025-05-29 16:31:43 +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
790
source/common/scripting/vm/vm.h
Normal file
790
source/common/scripting/vm/vm.h
Normal file
|
@ -0,0 +1,790 @@
|
|||
/*
|
||||
** vm.h
|
||||
** VM <-> native interface
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright -2016 Randy Heit
|
||||
** Copyright 2016 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef VM_H
|
||||
#define VM_H
|
||||
|
||||
#include "autosegs.h"
|
||||
#include "zstring.h"
|
||||
#include "vectors.h"
|
||||
#include "cmdlib.h"
|
||||
#include "engineerrors.h"
|
||||
#include "memarena.h"
|
||||
#include "name.h"
|
||||
#include "scopebarrier.h"
|
||||
|
||||
class DObject;
|
||||
union VMOP;
|
||||
class VMScriptFunction;
|
||||
|
||||
extern FMemArena ClassDataAllocator;
|
||||
|
||||
#define MAX_RETURNS 8 // Maximum number of results a function called by script code can return
|
||||
#define MAX_TRY_DEPTH 8 // Maximum number of nested TRYs in a single function
|
||||
|
||||
void JitRelease();
|
||||
|
||||
|
||||
typedef unsigned char VM_UBYTE;
|
||||
typedef signed char VM_SBYTE;
|
||||
typedef unsigned short VM_UHALF;
|
||||
typedef signed short VM_SHALF;
|
||||
typedef unsigned int VM_UWORD;
|
||||
typedef signed int VM_SWORD;
|
||||
|
||||
#define VM_EPSILON (1/65536.0)
|
||||
|
||||
// Register types for VMParam
|
||||
enum
|
||||
{
|
||||
REGT_INT = 0,
|
||||
REGT_FLOAT = 1,
|
||||
REGT_STRING = 2,
|
||||
REGT_POINTER = 3,
|
||||
REGT_TYPE = 3,
|
||||
|
||||
REGT_KONST = 4,
|
||||
REGT_MULTIREG2 = 8,
|
||||
REGT_MULTIREG3 = 16, // (e.g. a vector)
|
||||
REGT_MULTIREG = 24,
|
||||
REGT_ADDROF = 32, // used with PARAM: pass address of this register
|
||||
|
||||
REGT_NIL = 128 // parameter was omitted
|
||||
};
|
||||
|
||||
#define RET_FINAL (0x80) // Used with RET and RETI in the destination slot: this is the final return value
|
||||
|
||||
|
||||
enum EVMAbortException
|
||||
{
|
||||
X_OTHER,
|
||||
X_READ_NIL,
|
||||
X_WRITE_NIL,
|
||||
X_TOO_MANY_TRIES,
|
||||
X_ARRAY_OUT_OF_BOUNDS,
|
||||
X_DIVISION_BY_ZERO,
|
||||
X_BAD_SELF,
|
||||
X_FORMAT_ERROR
|
||||
};
|
||||
|
||||
class CVMAbortException : public CEngineError
|
||||
{
|
||||
public:
|
||||
static FString stacktrace;
|
||||
CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap);
|
||||
void MaybePrintMessage();
|
||||
};
|
||||
|
||||
// This must be a separate function because the VC compiler would otherwise allocate memory on the stack for every separate instance of the exception object that may get thrown.
|
||||
void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...);
|
||||
void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...);
|
||||
|
||||
void ClearGlobalVMStack();
|
||||
|
||||
struct VMReturn
|
||||
{
|
||||
void *Location;
|
||||
VM_UBYTE RegType; // Same as VMParam RegType, except REGT_KONST is invalid; only used by asserts
|
||||
|
||||
void SetInt(int val)
|
||||
{
|
||||
assert(RegType == REGT_INT);
|
||||
*(int *)Location = val;
|
||||
}
|
||||
void SetFloat(double val)
|
||||
{
|
||||
assert(RegType == REGT_FLOAT);
|
||||
*(double *)Location = val;
|
||||
}
|
||||
void SetVector(const double val[3])
|
||||
{
|
||||
assert(RegType == (REGT_FLOAT|REGT_MULTIREG3));
|
||||
((double *)Location)[0] = val[0];
|
||||
((double *)Location)[1] = val[1];
|
||||
((double *)Location)[2] = val[2];
|
||||
}
|
||||
void SetVector(const DVector3 &val)
|
||||
{
|
||||
assert(RegType == (REGT_FLOAT | REGT_MULTIREG3));
|
||||
((double *)Location)[0] = val[0];
|
||||
((double *)Location)[1] = val[1];
|
||||
((double *)Location)[2] = val[2];
|
||||
}
|
||||
void SetVector2(const double val[2])
|
||||
{
|
||||
assert(RegType == (REGT_FLOAT|REGT_MULTIREG2));
|
||||
((double *)Location)[0] = val[0];
|
||||
((double *)Location)[1] = val[1];
|
||||
}
|
||||
void SetVector2(const DVector2 &val)
|
||||
{
|
||||
assert(RegType == (REGT_FLOAT | REGT_MULTIREG2));
|
||||
((double *)Location)[0] = val[0];
|
||||
((double *)Location)[1] = val[1];
|
||||
}
|
||||
void SetString(const FString &val)
|
||||
{
|
||||
assert(RegType == REGT_STRING);
|
||||
*(FString *)Location = val;
|
||||
}
|
||||
|
||||
void SetPointer(void *val)
|
||||
{
|
||||
assert(RegType == REGT_POINTER);
|
||||
*(void **)Location = val;
|
||||
}
|
||||
|
||||
void SetObject(DObject *val)
|
||||
{
|
||||
assert(RegType == REGT_POINTER);
|
||||
*(void **)Location = val;
|
||||
}
|
||||
|
||||
void IntAt(int *loc)
|
||||
{
|
||||
Location = loc;
|
||||
RegType = REGT_INT;
|
||||
}
|
||||
void FloatAt(double *loc)
|
||||
{
|
||||
Location = loc;
|
||||
RegType = REGT_FLOAT;
|
||||
}
|
||||
void Vec2At(DVector2 *loc)
|
||||
{
|
||||
Location = loc;
|
||||
RegType = REGT_FLOAT | REGT_MULTIREG2;
|
||||
}
|
||||
void StringAt(FString *loc)
|
||||
{
|
||||
Location = loc;
|
||||
RegType = REGT_STRING;
|
||||
}
|
||||
void PointerAt(void **loc)
|
||||
{
|
||||
Location = loc;
|
||||
RegType = REGT_POINTER;
|
||||
}
|
||||
VMReturn() { }
|
||||
VMReturn(int *loc) { IntAt(loc); }
|
||||
VMReturn(double *loc) { FloatAt(loc); }
|
||||
VMReturn(DVector2 *loc) { Vec2At(loc); }
|
||||
VMReturn(FString *loc) { StringAt(loc); }
|
||||
VMReturn(void **loc) { PointerAt(loc); }
|
||||
};
|
||||
|
||||
struct VMRegisters;
|
||||
|
||||
struct TypedVMValue
|
||||
{
|
||||
union
|
||||
{
|
||||
int i;
|
||||
void *a;
|
||||
double f;
|
||||
struct { int pad[3]; VM_UBYTE Type; };
|
||||
struct { int foo[4]; } biggest;
|
||||
const FString *sp;
|
||||
};
|
||||
|
||||
const FString &s() const { return *sp; }
|
||||
|
||||
TypedVMValue()
|
||||
{
|
||||
a = NULL;
|
||||
Type = REGT_NIL;
|
||||
}
|
||||
TypedVMValue(const TypedVMValue &o)
|
||||
{
|
||||
biggest = o.biggest;
|
||||
}
|
||||
TypedVMValue(int v)
|
||||
{
|
||||
i = v;
|
||||
Type = REGT_INT;
|
||||
}
|
||||
TypedVMValue(double v)
|
||||
{
|
||||
f = v;
|
||||
Type = REGT_FLOAT;
|
||||
}
|
||||
|
||||
TypedVMValue(const FString *s)
|
||||
{
|
||||
sp = s;
|
||||
Type = REGT_STRING;
|
||||
}
|
||||
TypedVMValue(DObject *v)
|
||||
{
|
||||
a = v;
|
||||
Type = REGT_POINTER;
|
||||
}
|
||||
TypedVMValue(void *v)
|
||||
{
|
||||
a = v;
|
||||
Type = REGT_POINTER;
|
||||
}
|
||||
TypedVMValue &operator=(const TypedVMValue &o)
|
||||
{
|
||||
biggest = o.biggest;
|
||||
return *this;
|
||||
}
|
||||
TypedVMValue &operator=(int v)
|
||||
{
|
||||
i = v;
|
||||
Type = REGT_INT;
|
||||
return *this;
|
||||
}
|
||||
TypedVMValue &operator=(double v)
|
||||
{
|
||||
f = v;
|
||||
Type = REGT_FLOAT;
|
||||
return *this;
|
||||
}
|
||||
TypedVMValue &operator=(const FString *v)
|
||||
{
|
||||
sp = v;
|
||||
Type = REGT_STRING;
|
||||
return *this;
|
||||
}
|
||||
TypedVMValue &operator=(DObject *v)
|
||||
{
|
||||
a = v;
|
||||
Type = REGT_POINTER;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct VMValue
|
||||
{
|
||||
union
|
||||
{
|
||||
int i;
|
||||
void *a;
|
||||
double f;
|
||||
struct { int foo[2]; } biggest;
|
||||
const FString *sp;
|
||||
};
|
||||
|
||||
const FString &s() const { return *sp; }
|
||||
|
||||
VMValue()
|
||||
{
|
||||
a = NULL;
|
||||
}
|
||||
VMValue(const VMValue &o)
|
||||
{
|
||||
biggest = o.biggest;
|
||||
}
|
||||
VMValue(int v)
|
||||
{
|
||||
i = v;
|
||||
}
|
||||
VMValue(double v)
|
||||
{
|
||||
f = v;
|
||||
}
|
||||
VMValue(const char *s) = delete;
|
||||
VMValue(const FString &s) = delete;
|
||||
|
||||
VMValue(const FString *s)
|
||||
{
|
||||
sp = s;
|
||||
}
|
||||
VMValue(void *v)
|
||||
{
|
||||
a = v;
|
||||
}
|
||||
VMValue &operator=(const VMValue &o)
|
||||
{
|
||||
biggest = o.biggest;
|
||||
return *this;
|
||||
}
|
||||
VMValue &operator=(const TypedVMValue &o)
|
||||
{
|
||||
memcpy(&biggest, &o.biggest, sizeof(biggest));
|
||||
return *this;
|
||||
}
|
||||
VMValue &operator=(int v)
|
||||
{
|
||||
i = v;
|
||||
return *this;
|
||||
}
|
||||
VMValue &operator=(double v)
|
||||
{
|
||||
f = v;
|
||||
return *this;
|
||||
}
|
||||
VMValue &operator=(const FString *v)
|
||||
{
|
||||
sp = v;
|
||||
return *this;
|
||||
}
|
||||
VMValue &operator=(const FString &v) = delete;
|
||||
VMValue &operator=(const char *v) = delete;
|
||||
VMValue &operator=(DObject *v)
|
||||
{
|
||||
a = v;
|
||||
return *this;
|
||||
}
|
||||
int ToInt(int Type)
|
||||
{
|
||||
if (Type == REGT_INT)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
if (Type == REGT_FLOAT)
|
||||
{
|
||||
return int(f);
|
||||
}
|
||||
if (Type == REGT_STRING)
|
||||
{
|
||||
return (int)s().ToLong();
|
||||
}
|
||||
// FIXME
|
||||
return 0;
|
||||
}
|
||||
double ToDouble(int Type)
|
||||
{
|
||||
if (Type == REGT_FLOAT)
|
||||
{
|
||||
return f;
|
||||
}
|
||||
if (Type == REGT_INT)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
if (Type == REGT_STRING)
|
||||
{
|
||||
return s().ToDouble();
|
||||
}
|
||||
// FIXME
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class VMFunction
|
||||
{
|
||||
public:
|
||||
bool Unsafe = false;
|
||||
uint8_t ImplicitArgs = 0; // either 0 for static, 1 for method or 3 for action
|
||||
int VarFlags = 0; // [ZZ] this replaces 5+ bool fields
|
||||
unsigned VirtualIndex = ~0u;
|
||||
FName Name;
|
||||
const uint8_t *RegTypes = nullptr;
|
||||
TArray<TypedVMValue> DefaultArgs;
|
||||
FString PrintableName; // so that the VM can print meaningful info if something in this function goes wrong.
|
||||
|
||||
class PPrototype *Proto;
|
||||
TArray<uint32_t> ArgFlags; // Should be the same length as Proto->ArgumentTypes
|
||||
|
||||
int(*ScriptCall)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret) = nullptr;
|
||||
|
||||
VMFunction(FName name = NAME_None) : ImplicitArgs(0), Name(name), Proto(NULL)
|
||||
{
|
||||
AllFunctions.Push(this);
|
||||
}
|
||||
virtual ~VMFunction() {}
|
||||
|
||||
void *operator new(size_t size)
|
||||
{
|
||||
return ClassDataAllocator.Alloc(size);
|
||||
}
|
||||
|
||||
void operator delete(void *block) {}
|
||||
void operator delete[](void *block) {}
|
||||
static void DeleteAll()
|
||||
{
|
||||
for (auto f : AllFunctions)
|
||||
{
|
||||
f->~VMFunction();
|
||||
}
|
||||
AllFunctions.Clear();
|
||||
// also release any JIT data
|
||||
JitRelease();
|
||||
}
|
||||
static void CreateRegUseInfo()
|
||||
{
|
||||
for (auto f : AllFunctions)
|
||||
{
|
||||
f->CreateRegUse();
|
||||
}
|
||||
}
|
||||
static TArray<VMFunction *> AllFunctions;
|
||||
protected:
|
||||
void CreateRegUse();
|
||||
};
|
||||
|
||||
// Use this in the prototype for a native function.
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define VM_ARGS VMValue *param, int numparam, VMReturn *ret, int numret
|
||||
#define VM_ARGS_NAMES param, numparam, ret, numret
|
||||
#define VM_INVOKE(param, numparam, ret, numret, reginfo) (param), (numparam), (ret), (numret)
|
||||
#else
|
||||
#define VM_ARGS VMValue *param, int numparam, VMReturn *ret, int numret, const uint8_t *reginfo
|
||||
#define VM_ARGS_NAMES param, numparam, ret, numret, reginfo
|
||||
#define VM_INVOKE(param, numparam, ret, numret, reginfo) (param), (numparam), (ret), (numret), (reginfo)
|
||||
#endif
|
||||
|
||||
class VMNativeFunction : public VMFunction
|
||||
{
|
||||
public:
|
||||
typedef int (*NativeCallType)(VM_ARGS);
|
||||
|
||||
// 8 is VARF_Native. I can't write VARF_Native because of circular references between this and dobject/dobjtype.
|
||||
VMNativeFunction() : NativeCall(NULL) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
|
||||
VMNativeFunction(NativeCallType call) : NativeCall(call) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
|
||||
VMNativeFunction(NativeCallType call, FName name) : VMFunction(name), NativeCall(call) { VarFlags = 8; ScriptCall = &VMNativeFunction::NativeScriptCall; }
|
||||
|
||||
// Return value is the number of results.
|
||||
NativeCallType NativeCall;
|
||||
|
||||
// Function pointer to a native function to be called directly by the JIT using the platform calling convention
|
||||
void *DirectNativeCall = nullptr;
|
||||
|
||||
private:
|
||||
static int NativeScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
|
||||
};
|
||||
|
||||
int VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap = NULL*/);
|
||||
int VMCallWithDefaults(VMFunction *func, TArray<VMValue> ¶ms, VMReturn *results, int numresults/*, VMException **trap = NULL*/);
|
||||
|
||||
inline int VMCallAction(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap = NULL*/)
|
||||
{
|
||||
return VMCall(func, params, numparams, results, numresults);
|
||||
}
|
||||
|
||||
// Use these to collect the parameters in a native function.
|
||||
// variable name <x> at position <p>
|
||||
void NullParam(const char *varname);
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool AssertObject(void * ob);
|
||||
#endif
|
||||
|
||||
#define PARAM_NULLCHECK(ptr, var) (ptr == nullptr? NullParam(#var), ptr : ptr)
|
||||
|
||||
// This cannot assert because there is no info for varargs
|
||||
#define PARAM_VA_POINTER(x) const uint8_t *x = (const uint8_t *)param[numparam-1].a;
|
||||
|
||||
// For required parameters.
|
||||
#define PARAM_INT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); int x = param[p].i;
|
||||
#define PARAM_UINT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); unsigned x = param[p].i;
|
||||
#define PARAM_BOOL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); bool x = !!param[p].i;
|
||||
#define PARAM_NAME_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FName x = ENamedName(param[p].i);
|
||||
#define PARAM_SOUND_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FSoundID x = param[p].i;
|
||||
#define PARAM_COLOR_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); PalEntry x; x.d = param[p].i;
|
||||
#define PARAM_FLOAT_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); double x = param[p].f;
|
||||
#define PARAM_ANGLE_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_FLOAT); DAngle x = param[p].f;
|
||||
#define PARAM_STRING_VAL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_STRING); FString x = param[p].s();
|
||||
#define PARAM_STRING_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_STRING); const FString &x = param[p].s();
|
||||
#define PARAM_STATELABEL_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); int x = param[p].i;
|
||||
#define PARAM_STATE_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FState *x = (FState *)StateLabels.GetState(param[p].i, self->GetClass());
|
||||
#define PARAM_STATE_ACTION_AT(p,x) assert((p) < numparam); assert(reginfo[p] == REGT_INT); FState *x = (FState *)StateLabels.GetState(param[p].i, stateowner->GetClass());
|
||||
#define PARAM_POINTER_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type *x = (type *)param[p].a;
|
||||
#define PARAM_OUTPOINTER_AT(p,x,type) assert((p) < numparam); type *x = (type *)param[p].a;
|
||||
#define PARAM_POINTERTYPE_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type x = (type )param[p].a;
|
||||
#define PARAM_OBJECT_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER && AssertObject(param[p].a)); type *x = (type *)param[p].a; assert(x == NULL || x->IsKindOf(RUNTIME_CLASS(type)));
|
||||
#define PARAM_CLASS_AT(p,x,base) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); base::MetaClass *x = (base::MetaClass *)param[p].a; assert(x == NULL || x->IsDescendantOf(RUNTIME_CLASS(base)));
|
||||
#define PARAM_POINTER_NOT_NULL_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); type *x = (type *)PARAM_NULLCHECK(param[p].a, #x);
|
||||
#define PARAM_OBJECT_NOT_NULL_AT(p,x,type) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER && (AssertObject(param[p].a))); type *x = (type *)PARAM_NULLCHECK(param[p].a, #x); assert(x == NULL || x->IsKindOf(RUNTIME_CLASS(type)));
|
||||
#define PARAM_CLASS_NOT_NULL_AT(p,x,base) assert((p) < numparam); assert(reginfo[p] == REGT_POINTER); base::MetaClass *x = (base::MetaClass *)PARAM_NULLCHECK(param[p].a, #x); assert(x == NULL || x->IsDescendantOf(RUNTIME_CLASS(base)));
|
||||
|
||||
|
||||
// The above, but with an automatically increasing position index.
|
||||
#define PARAM_PROLOGUE int paramnum = -1;
|
||||
|
||||
#define PARAM_INT(x) ++paramnum; PARAM_INT_AT(paramnum,x)
|
||||
#define PARAM_UINT(x) ++paramnum; PARAM_UINT_AT(paramnum,x)
|
||||
#define PARAM_BOOL(x) ++paramnum; PARAM_BOOL_AT(paramnum,x)
|
||||
#define PARAM_NAME(x) ++paramnum; PARAM_NAME_AT(paramnum,x)
|
||||
#define PARAM_SOUND(x) ++paramnum; PARAM_SOUND_AT(paramnum,x)
|
||||
#define PARAM_COLOR(x) ++paramnum; PARAM_COLOR_AT(paramnum,x)
|
||||
#define PARAM_FLOAT(x) ++paramnum; PARAM_FLOAT_AT(paramnum,x)
|
||||
#define PARAM_ANGLE(x) ++paramnum; PARAM_ANGLE_AT(paramnum,x)
|
||||
#define PARAM_STRING(x) ++paramnum; PARAM_STRING_AT(paramnum,x)
|
||||
#define PARAM_STRING_VAL(x) ++paramnum; PARAM_STRING_VAL_AT(paramnum,x)
|
||||
#define PARAM_STATELABEL(x) ++paramnum; PARAM_STATELABEL_AT(paramnum,x)
|
||||
#define PARAM_STATE(x) ++paramnum; PARAM_STATE_AT(paramnum,x)
|
||||
#define PARAM_STATE_ACTION(x) ++paramnum; PARAM_STATE_ACTION_AT(paramnum,x)
|
||||
#define PARAM_POINTER(x,type) ++paramnum; PARAM_POINTER_AT(paramnum,x,type)
|
||||
#define PARAM_OUTPOINTER(x,type) ++paramnum; PARAM_OUTPOINTER_AT(paramnum,x,type)
|
||||
#define PARAM_POINTERTYPE(x,type) ++paramnum; PARAM_POINTERTYPE_AT(paramnum,x,type)
|
||||
#define PARAM_OBJECT(x,type) ++paramnum; PARAM_OBJECT_AT(paramnum,x,type)
|
||||
#define PARAM_CLASS(x,base) ++paramnum; PARAM_CLASS_AT(paramnum,x,base)
|
||||
#define PARAM_CLASS(x,base) ++paramnum; PARAM_CLASS_AT(paramnum,x,base)
|
||||
#define PARAM_POINTER_NOT_NULL(x,type) ++paramnum; PARAM_POINTER_NOT_NULL_AT(paramnum,x,type)
|
||||
#define PARAM_OBJECT_NOT_NULL(x,type) ++paramnum; PARAM_OBJECT_NOT_NULL_AT(paramnum,x,type)
|
||||
#define PARAM_CLASS_NOT_NULL(x,base) ++paramnum; PARAM_CLASS_NOT_NULL_AT(paramnum,x,base)
|
||||
|
||||
typedef int(*actionf_p)(VM_ARGS);
|
||||
|
||||
struct FieldDesc
|
||||
{
|
||||
const char *ClassName;
|
||||
const char *FieldName;
|
||||
size_t FieldOffset;
|
||||
unsigned FieldSize;
|
||||
int BitValue;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
// Traits for the types we are interested in
|
||||
template<typename T> struct native_is_valid { static const bool value = false; static const bool retval = false; };
|
||||
template<typename T> struct native_is_valid<T*> { static const bool value = true; static const bool retval = true; };
|
||||
template<typename T> struct native_is_valid<T&> { static const bool value = true; static const bool retval = true; };
|
||||
template<> struct native_is_valid<void> { static const bool value = true; static const bool retval = true; };
|
||||
template<> struct native_is_valid<int> { static const bool value = true; static const bool retval = true; };
|
||||
template<> struct native_is_valid<unsigned int> { static const bool value = true; static const bool retval = true; };
|
||||
template<> struct native_is_valid<double> { static const bool value = true; static const bool retval = true; };
|
||||
template<> struct native_is_valid<bool> { static const bool value = true; static const bool retval = false;}; // Bool as return does not work!
|
||||
}
|
||||
|
||||
// Compile time validation of direct native functions
|
||||
struct DirectNativeDesc
|
||||
{
|
||||
DirectNativeDesc() = default;
|
||||
|
||||
#define TP(n) typename P##n
|
||||
#define VP(n) ValidateType<P##n>()
|
||||
template<typename Ret> DirectNativeDesc(Ret(*func)()) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); }
|
||||
template<typename Ret, TP(1)> DirectNativeDesc(Ret(*func)(P1)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); }
|
||||
template<typename Ret, TP(1), TP(2)> DirectNativeDesc(Ret(*func)(P1,P2)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3)> DirectNativeDesc(Ret(*func)(P1,P2,P3)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13); }
|
||||
template<typename Ret, TP(1), TP(2), TP(3), TP(4), TP(5), TP(6), TP(7), TP(8), TP(9), TP(10), TP(11), TP(12), TP(13), TP(14)> DirectNativeDesc(Ret(*func)(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14)) : Ptr(reinterpret_cast<void*>(func)) { ValidateRet<Ret>(); VP(1); VP(2); VP(3); VP(4); VP(5); VP(6); VP(7); VP(8); VP(9); VP(10); VP(11); VP(12); VP(13), VP(14); }
|
||||
#undef TP
|
||||
#undef VP
|
||||
|
||||
template<typename T> void ValidateType() { static_assert(native_is_valid<T>::value, "Argument type is not valid as a direct native parameter or return type"); }
|
||||
template<typename T> void ValidateRet() { static_assert(native_is_valid<T>::retval, "Return type is not valid as a direct native parameter or return type"); }
|
||||
|
||||
operator void *() const { return Ptr; }
|
||||
|
||||
void *Ptr;
|
||||
};
|
||||
|
||||
struct AFuncDesc
|
||||
{
|
||||
const char *ClassName;
|
||||
const char *FuncName;
|
||||
actionf_p Function;
|
||||
VMNativeFunction **VMPointer;
|
||||
DirectNativeDesc DirectNative;
|
||||
};
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma section(".areg$u",read)
|
||||
#pragma section(".freg$u",read)
|
||||
|
||||
#define MSVC_ASEG __declspec(allocate(".areg$u"))
|
||||
#define MSVC_FSEG __declspec(allocate(".freg$u"))
|
||||
#define GCC_ASEG
|
||||
#define GCC_FSEG
|
||||
#else
|
||||
#define MSVC_ASEG
|
||||
#define MSVC_FSEG
|
||||
#define GCC_ASEG __attribute__((section(SECTION_AREG))) __attribute__((used))
|
||||
#define GCC_FSEG __attribute__((section(SECTION_FREG))) __attribute__((used))
|
||||
#endif
|
||||
|
||||
// Macros to handle action functions. These are here so that I don't have to
|
||||
// change every single use in case the parameters change.
|
||||
|
||||
#define DEFINE_ACTION_FUNCTION_NATIVE(cls, name, native) \
|
||||
static int AF_##cls##_##name(VM_ARGS); \
|
||||
VMNativeFunction *cls##_##name##_VMPtr; \
|
||||
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr, native }; \
|
||||
extern AFuncDesc const *const cls##_##name##_HookPtr; \
|
||||
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
|
||||
static int AF_##cls##_##name(VM_ARGS)
|
||||
|
||||
#define DEFINE_ACTION_FUNCTION_NATIVE0(cls, name, native) \
|
||||
static int AF_##cls##_##name(VM_ARGS); \
|
||||
VMNativeFunction *cls##_##name##_VMPtr; \
|
||||
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
|
||||
extern AFuncDesc const *const cls##_##name##_HookPtr; \
|
||||
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
|
||||
static int AF_##cls##_##name(VM_ARGS)
|
||||
|
||||
#define DEFINE_ACTION_FUNCTION(cls, name) \
|
||||
static int AF_##cls##_##name(VM_ARGS); \
|
||||
VMNativeFunction *cls##_##name##_VMPtr; \
|
||||
static const AFuncDesc cls##_##name##_Hook = { #cls, #name, AF_##cls##_##name, &cls##_##name##_VMPtr }; \
|
||||
extern AFuncDesc const *const cls##_##name##_HookPtr; \
|
||||
MSVC_ASEG AFuncDesc const *const cls##_##name##_HookPtr GCC_ASEG = &cls##_##name##_Hook; \
|
||||
static int AF_##cls##_##name(VM_ARGS)
|
||||
|
||||
// cls is the scripted class name, icls the internal one (e.g. player_t vs. Player)
|
||||
#define DEFINE_FIELD_X(cls, icls, name) \
|
||||
static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \
|
||||
extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name;
|
||||
|
||||
// This is for cases where the internal size does not match the part that gets exported.
|
||||
#define DEFINE_FIELD_UNSIZED(cls, icls, name) \
|
||||
static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), ~0u, 0 }; \
|
||||
extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##icls##_##name;
|
||||
|
||||
#define DEFINE_FIELD_NAMED_X(cls, icls, name, scriptname) \
|
||||
static const FieldDesc VMField_##cls##_##scriptname = { "A" #cls, #scriptname, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), 0 }; \
|
||||
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
|
||||
|
||||
#define DEFINE_FIELD_X_BIT(cls, icls, name, bitval) \
|
||||
static const FieldDesc VMField_##icls##_##name = { "A" #cls, #name, (unsigned)myoffsetof(icls, name), (unsigned)sizeof(icls::name), bitval }; \
|
||||
extern FieldDesc const *const VMField_##icls##_##name##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##icls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name;
|
||||
|
||||
#define DEFINE_FIELD(cls, name) \
|
||||
static const FieldDesc VMField_##cls##_##name = { #cls, #name, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \
|
||||
extern FieldDesc const *const VMField_##cls##_##name##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##cls##_##name##_HookPtr GCC_FSEG = &VMField_##cls##_##name;
|
||||
|
||||
#define DEFINE_FIELD_NAMED(cls, name, scriptname) \
|
||||
static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), 0 }; \
|
||||
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
|
||||
|
||||
#define DEFINE_FIELD_BIT(cls, name, scriptname, bitval) \
|
||||
static const FieldDesc VMField_##cls##_##scriptname = { #cls, #scriptname, (unsigned)myoffsetof(cls, name), (unsigned)sizeof(cls::name), bitval }; \
|
||||
extern FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMField_##cls##_##scriptname##_HookPtr GCC_FSEG = &VMField_##cls##_##scriptname;
|
||||
|
||||
#define DEFINE_GLOBAL(name) \
|
||||
static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&name, (unsigned)sizeof(name), 0 }; \
|
||||
extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
|
||||
|
||||
#define DEFINE_GLOBAL_NAMED(iname, name) \
|
||||
static const FieldDesc VMGlobal_##name = { "", #name, (size_t)&iname, (unsigned)sizeof(iname), 0 }; \
|
||||
extern FieldDesc const *const VMGlobal_##name##_HookPtr; \
|
||||
MSVC_FSEG FieldDesc const *const VMGlobal_##name##_HookPtr GCC_FSEG = &VMGlobal_##name;
|
||||
|
||||
|
||||
class AActor;
|
||||
|
||||
#define ACTION_RETURN_STATE(v) do { FState *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_POINTER(v) do { void *state = v; if (numret > 0) { assert(ret != NULL); ret->SetPointer(state); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_OBJECT(v) do { auto state = v; if (numret > 0) { assert(ret != NULL); ret->SetObject(state); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_FLOAT(v) do { double u = v; if (numret > 0) { assert(ret != nullptr); ret->SetFloat(u); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_VEC2(v) do { DVector2 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector2(u); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_VEC3(v) do { DVector3 u = v; if (numret > 0) { assert(ret != nullptr); ret[0].SetVector(u); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_INT(v) do { int u = v; if (numret > 0) { assert(ret != NULL); ret->SetInt(u); return 1; } return 0; } while(0)
|
||||
#define ACTION_RETURN_BOOL(v) ACTION_RETURN_INT(v)
|
||||
#define ACTION_RETURN_STRING(v) do { FString u = v; if (numret > 0) { assert(ret != NULL); ret->SetString(u); return 1; } return 0; } while(0)
|
||||
|
||||
// Checks to see what called the current action function
|
||||
#define ACTION_CALL_FROM_ACTOR() (stateinfo == nullptr || stateinfo->mStateType == STATE_Actor)
|
||||
#define ACTION_CALL_FROM_PSPRITE() (self->player && stateinfo != nullptr && stateinfo->mStateType == STATE_Psprite)
|
||||
#define ACTION_CALL_FROM_INVENTORY() (stateinfo != nullptr && stateinfo->mStateType == STATE_StateChain)
|
||||
|
||||
// Standard parameters for all action functions
|
||||
// self - Actor this action is to operate on (player if a weapon)
|
||||
// stateowner - Actor this action really belongs to (may be an item)
|
||||
// callingstate - State this action was called from
|
||||
#define PARAM_ACTION_PROLOGUE(type) \
|
||||
PARAM_PROLOGUE; \
|
||||
PARAM_OBJECT_NOT_NULL (self, AActor); \
|
||||
PARAM_OBJECT (stateowner, type) \
|
||||
PARAM_POINTER (stateinfo, FStateParamInfo) \
|
||||
|
||||
// Number of action paramaters
|
||||
#define NAP 3
|
||||
|
||||
#define PARAM_SELF_PROLOGUE(type) \
|
||||
PARAM_PROLOGUE; \
|
||||
PARAM_OBJECT_NOT_NULL(self, type);
|
||||
|
||||
// for structs we cannot do a class validation
|
||||
#define PARAM_SELF_STRUCT_PROLOGUE(type) \
|
||||
PARAM_PROLOGUE; \
|
||||
PARAM_POINTER_NOT_NULL(self, type);
|
||||
|
||||
class PFunction;
|
||||
|
||||
VMFunction *FindVMFunction(PClass *cls, const char *name);
|
||||
#define DECLARE_VMFUNC(cls, name) static VMFunction *name; if (name == nullptr) name = FindVMFunction(RUNTIME_CLASS(cls), #name);
|
||||
|
||||
FString FStringFormat(VM_ARGS, int offset = 0);
|
||||
|
||||
#define IFVM(cls, funcname) \
|
||||
static VMFunction * func = nullptr; \
|
||||
if (func == nullptr) { \
|
||||
PClass::FindFunction(&func, #cls, #funcname); \
|
||||
assert(func); \
|
||||
} \
|
||||
if (func != nullptr)
|
||||
|
||||
|
||||
|
||||
unsigned GetVirtualIndex(PClass *cls, const char *funcname);
|
||||
|
||||
#define IFVIRTUALPTR(self, cls, funcname) \
|
||||
static unsigned VIndex = ~0u; \
|
||||
if (VIndex == ~0u) { \
|
||||
VIndex = GetVirtualIndex(RUNTIME_CLASS(cls), #funcname); \
|
||||
assert(VIndex != ~0u); \
|
||||
} \
|
||||
auto clss = self->GetClass(); \
|
||||
VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \
|
||||
if (func != nullptr)
|
||||
|
||||
#define IFVIRTUAL(cls, funcname) IFVIRTUALPTR(this, cls, funcname)
|
||||
|
||||
#define IFVIRTUALPTRNAME(self, cls, funcname) \
|
||||
static unsigned VIndex = ~0u; \
|
||||
if (VIndex == ~0u) { \
|
||||
VIndex = GetVirtualIndex(PClass::FindClass(cls), #funcname); \
|
||||
assert(VIndex != ~0u); \
|
||||
} \
|
||||
auto clss = self->GetClass(); \
|
||||
VMFunction *func = clss->Virtuals.Size() > VIndex? clss->Virtuals[VIndex] : nullptr; \
|
||||
if (func != nullptr)
|
||||
|
||||
#endif
|
253
source/common/scripting/vm/vmexec.cpp
Normal file
253
source/common/scripting/vm/vmexec.cpp
Normal file
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
** vmexec.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright -2016 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include "v_video.h"
|
||||
#include "s_soundinternal.h"
|
||||
#include "basics.h"
|
||||
//#include "r_state.h"
|
||||
#include "stats.h"
|
||||
#include "vmintern.h"
|
||||
#include "types.h"
|
||||
#include "basics.h"
|
||||
#include "texturemanager.h"
|
||||
|
||||
extern cycle_t VMCycles[10];
|
||||
extern int VMCalls[10];
|
||||
|
||||
// intentionally implemented in a different source file to prevent inlining.
|
||||
#if 0
|
||||
void ThrowVMException(VMException *x);
|
||||
#endif
|
||||
|
||||
#define IMPLEMENT_VMEXEC
|
||||
|
||||
#if !defined(COMPGOTO) && defined(__GNUC__)
|
||||
#define COMPGOTO 1
|
||||
#endif
|
||||
|
||||
#if COMPGOTO
|
||||
#define OP(x) x
|
||||
#define NEXTOP do { pc++; unsigned op = pc->op; a = pc->a; goto *ops[op]; } while(0)
|
||||
#else
|
||||
#define OP(x) case OP_##x
|
||||
#define NEXTOP pc++; break
|
||||
#endif
|
||||
|
||||
#define luai_nummod(a,b) ((a) - floor((a)/(b))*(b))
|
||||
|
||||
#define A (pc[0].a)
|
||||
#define B (pc[0].b)
|
||||
#define C (pc[0].c)
|
||||
#define Cs (pc[0].cs)
|
||||
#define BC (pc[0].i16u)
|
||||
#define BCs (pc[0].i16)
|
||||
#define ABCs (pc[0].i24)
|
||||
#define JMPOFS(x) ((x)->i24)
|
||||
|
||||
#define KC (konstd[C])
|
||||
#define RC (reg.d[C])
|
||||
|
||||
#define PA (reg.a[A])
|
||||
#define PB (reg.a[B])
|
||||
|
||||
#define ASSERTD(x) assert((unsigned)(x) < f->NumRegD)
|
||||
#define ASSERTF(x) assert((unsigned)(x) < f->NumRegF)
|
||||
#define ASSERTA(x) assert((unsigned)(x) < f->NumRegA)
|
||||
#define ASSERTS(x) assert((unsigned)(x) < f->NumRegS)
|
||||
|
||||
#define ASSERTKD(x) assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstD)
|
||||
#define ASSERTKF(x) assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstF)
|
||||
#define ASSERTKA(x) assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstA)
|
||||
#define ASSERTKS(x) assert(sfunc != NULL && (unsigned)(x) < sfunc->NumKonstS)
|
||||
|
||||
#define CMPJMP(test) \
|
||||
if ((test) == (a & CMP_CHECK)) { \
|
||||
assert(pc[1].op == OP_JMP); \
|
||||
pc += 1 + JMPOFS(pc+1); \
|
||||
} else { \
|
||||
pc += 1; \
|
||||
}
|
||||
|
||||
#define GETADDR(a,o,x) \
|
||||
if (a == NULL) { ThrowAbortException(x, nullptr); return 0; } \
|
||||
ptr = (VM_SBYTE *)a + o
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define WAS_NDEBUG 1
|
||||
#else
|
||||
#define WAS_NDEBUG 0
|
||||
#endif
|
||||
|
||||
#if WAS_NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#undef assert
|
||||
#include <assert.h>
|
||||
struct VMExec_Checked
|
||||
{
|
||||
#include "vmexec.h"
|
||||
};
|
||||
#if WAS_NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#if !WAS_NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
#undef assert
|
||||
#include <assert.h>
|
||||
struct VMExec_Unchecked
|
||||
{
|
||||
#include "vmexec.h"
|
||||
};
|
||||
#if !WAS_NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#undef assert
|
||||
#include <assert.h>
|
||||
|
||||
int (*VMExec)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret) =
|
||||
#ifdef NDEBUG
|
||||
VMExec_Unchecked::Exec
|
||||
#else
|
||||
VMExec_Checked::Exec
|
||||
#endif
|
||||
;
|
||||
|
||||
// Note: If the VM is being used in multiple threads, this should be declared as thread_local.
|
||||
// ZDoom doesn't need this at the moment so this is disabled.
|
||||
|
||||
thread_local VMFrameStack GlobalVMStack;
|
||||
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMSelectEngine
|
||||
//
|
||||
// Selects the VM engine, either checked or unchecked. Default will decide
|
||||
// based on the NDEBUG preprocessor definition.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void VMSelectEngine(EVMEngine engine)
|
||||
{
|
||||
switch (engine)
|
||||
{
|
||||
case VMEngine_Default:
|
||||
#ifdef NDEBUG
|
||||
VMExec = VMExec_Unchecked::Exec;
|
||||
#else
|
||||
#endif
|
||||
VMExec = VMExec_Checked::Exec;
|
||||
break;
|
||||
case VMEngine_Unchecked:
|
||||
VMExec = VMExec_Unchecked::Exec;
|
||||
break;
|
||||
case VMEngine_Checked:
|
||||
VMExec = VMExec_Checked::Exec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFillParams
|
||||
//
|
||||
// Takes parameters from the parameter stack and stores them in the callee's
|
||||
// registers.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void VMFillParams(VMValue *params, VMFrame *callee, int numparam)
|
||||
{
|
||||
unsigned int regd, regf, regs, rega;
|
||||
VMScriptFunction *calleefunc = static_cast<VMScriptFunction *>(callee->Func);
|
||||
const VMRegisters calleereg(callee);
|
||||
|
||||
assert(calleefunc != NULL && !(calleefunc->VarFlags & VARF_Native));
|
||||
assert(numparam == calleefunc->NumArgs);
|
||||
assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3);
|
||||
|
||||
regd = regf = regs = rega = 0;
|
||||
const uint8_t *reginfo = calleefunc->RegTypes;
|
||||
assert(reginfo != nullptr);
|
||||
for (int i = 0; i < calleefunc->NumArgs; ++i, reginfo++)
|
||||
{
|
||||
// copy all parameters to the local registers.
|
||||
VMValue &p = params[i];
|
||||
if (*reginfo < REGT_STRING)
|
||||
{
|
||||
if (*reginfo == REGT_INT)
|
||||
{
|
||||
calleereg.d[regd++] = p.i;
|
||||
}
|
||||
else // p.Type == REGT_FLOAT
|
||||
{
|
||||
calleereg.f[regf++] = p.f;
|
||||
}
|
||||
}
|
||||
else if (*reginfo == REGT_STRING)
|
||||
{
|
||||
calleereg.s[regs++] = p.s();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(*reginfo == REGT_POINTER);
|
||||
calleereg.a[rega++] = p.a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool AssertObject(void * ob)
|
||||
{
|
||||
auto obj = (DObject*)ob;
|
||||
if (obj == nullptr) return true;
|
||||
#ifdef _MSC_VER
|
||||
__try
|
||||
{
|
||||
return obj->MagicID == DObject::MAGIC_ID;
|
||||
}
|
||||
__except (1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// No SEH on non-Microsoft compilers. :(
|
||||
return obj->MagicID == DObject::MAGIC_ID;
|
||||
#endif
|
||||
}
|
||||
#endif
|
2017
source/common/scripting/vm/vmexec.h
Normal file
2017
source/common/scripting/vm/vmexec.h
Normal file
File diff suppressed because it is too large
Load diff
771
source/common/scripting/vm/vmframe.cpp
Normal file
771
source/common/scripting/vm/vmframe.cpp
Normal file
|
@ -0,0 +1,771 @@
|
|||
/*
|
||||
** vmframe.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright -2016 Randy Heit
|
||||
** Copyright 2016 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <new>
|
||||
#include "dobject.h"
|
||||
#include "v_text.h"
|
||||
#include "stats.h"
|
||||
#include "c_dispatch.h"
|
||||
#include "templates.h"
|
||||
#include "vmintern.h"
|
||||
#include "types.h"
|
||||
#include "jit.h"
|
||||
#include "c_cvars.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef HAVE_VM_JIT
|
||||
CUSTOM_CVAR(Bool, vm_jit, true, CVAR_NOINITCALL)
|
||||
{
|
||||
Printf("You must restart " GAMENAME " for this change to take effect.\n");
|
||||
Printf("This cvar is currently not saved. You must specify it on the command line.");
|
||||
}
|
||||
#else
|
||||
CVAR(Bool, vm_jit, false, CVAR_NOINITCALL|CVAR_NOSET)
|
||||
FString JitCaptureStackTrace(int framesToSkip, bool includeNativeFrames) { return FString(); }
|
||||
void JitRelease() {}
|
||||
#endif
|
||||
|
||||
cycle_t VMCycles[10];
|
||||
int VMCalls[10];
|
||||
|
||||
#if 0
|
||||
IMPLEMENT_CLASS(VMException, false, false)
|
||||
#endif
|
||||
|
||||
TArray<VMFunction *> VMFunction::AllFunctions;
|
||||
|
||||
// Creates the register type list for a function.
|
||||
// Native functions only need this to assert their parameters in debug mode, script functions use this to load their registers from the VMValues.
|
||||
void VMFunction::CreateRegUse()
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
if (VarFlags & VARF_Native) return; // we do not need this for native functions in release builds.
|
||||
#endif
|
||||
int count = 0;
|
||||
if (!Proto)
|
||||
{
|
||||
if (RegTypes) return;
|
||||
Printf(TEXTCOLOR_ORANGE "Function without prototype needs register info manually set: %s\n", PrintableName.GetChars());
|
||||
return;
|
||||
}
|
||||
assert(Proto->isPrototype());
|
||||
|
||||
for (auto arg : Proto->ArgumentTypes)
|
||||
{
|
||||
count += arg? arg->GetRegCount() : 1;
|
||||
}
|
||||
uint8_t *regp;
|
||||
RegTypes = regp = (uint8_t*)ClassDataAllocator.Alloc(count);
|
||||
count = 0;
|
||||
for (unsigned i = 0; i < Proto->ArgumentTypes.Size(); i++)
|
||||
{
|
||||
auto arg = Proto->ArgumentTypes[i];
|
||||
auto flg = ArgFlags.Size() > i ? ArgFlags[i] : 0;
|
||||
if (arg == nullptr)
|
||||
{
|
||||
// Marker for start of varargs.
|
||||
*regp++ = REGT_NIL;
|
||||
}
|
||||
else if ((flg & VARF_Out) && !arg->isPointer())
|
||||
{
|
||||
*regp++ = REGT_POINTER;
|
||||
}
|
||||
else for (int j = 0; j < arg->GetRegCount(); j++)
|
||||
{
|
||||
*regp++ = arg->GetRegType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VMScriptFunction::VMScriptFunction(FName name)
|
||||
{
|
||||
Name = name;
|
||||
LineInfo = nullptr;
|
||||
Code = NULL;
|
||||
KonstD = NULL;
|
||||
KonstF = NULL;
|
||||
KonstS = NULL;
|
||||
KonstA = NULL;
|
||||
LineInfoCount = 0;
|
||||
ExtraSpace = 0;
|
||||
CodeSize = 0;
|
||||
NumRegD = 0;
|
||||
NumRegF = 0;
|
||||
NumRegS = 0;
|
||||
NumRegA = 0;
|
||||
NumKonstD = 0;
|
||||
NumKonstF = 0;
|
||||
NumKonstS = 0;
|
||||
NumKonstA = 0;
|
||||
MaxParam = 0;
|
||||
NumArgs = 0;
|
||||
ScriptCall = &VMScriptFunction::FirstScriptCall;
|
||||
}
|
||||
|
||||
VMScriptFunction::~VMScriptFunction()
|
||||
{
|
||||
if (Code != NULL)
|
||||
{
|
||||
if (KonstS != NULL)
|
||||
{
|
||||
for (int i = 0; i < NumKonstS; ++i)
|
||||
{
|
||||
KonstS[i].~FString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers)
|
||||
{
|
||||
assert(Code == NULL);
|
||||
assert(numops > 0);
|
||||
assert(numkonstd >= 0 && numkonstd <= 65535);
|
||||
assert(numkonstf >= 0 && numkonstf <= 65535);
|
||||
assert(numkonsts >= 0 && numkonsts <= 65535);
|
||||
assert(numkonsta >= 0 && numkonsta <= 65535);
|
||||
assert(numlinenumbers >= 0 && numlinenumbers <= 65535);
|
||||
void *mem = ClassDataAllocator.Alloc(numops * sizeof(VMOP) +
|
||||
numkonstd * sizeof(int) +
|
||||
numkonstf * sizeof(double) +
|
||||
numkonsts * sizeof(FString) +
|
||||
numkonsta * sizeof(FVoidObj) +
|
||||
numlinenumbers * sizeof(FStatementInfo));
|
||||
Code = (VMOP *)mem;
|
||||
mem = (void *)((VMOP *)mem + numops);
|
||||
|
||||
if (numlinenumbers > 0)
|
||||
{
|
||||
LineInfo = (FStatementInfo*)mem;
|
||||
LineInfoCount = numlinenumbers;
|
||||
mem = LineInfo + numlinenumbers;
|
||||
}
|
||||
else
|
||||
{
|
||||
LineInfo = nullptr;
|
||||
LineInfoCount = 0;
|
||||
}
|
||||
if (numkonstd > 0)
|
||||
{
|
||||
KonstD = (int *)mem;
|
||||
mem = (void *)((int *)mem + numkonstd);
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstD = NULL;
|
||||
}
|
||||
if (numkonstf > 0)
|
||||
{
|
||||
KonstF = (double *)mem;
|
||||
mem = (void *)((double *)mem + numkonstf);
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstF = NULL;
|
||||
}
|
||||
if (numkonsts > 0)
|
||||
{
|
||||
KonstS = (FString *)mem;
|
||||
for (int i = 0; i < numkonsts; ++i)
|
||||
{
|
||||
::new(&KonstS[i]) FString;
|
||||
}
|
||||
mem = (void *)((FString *)mem + numkonsts);
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstS = NULL;
|
||||
}
|
||||
if (numkonsta > 0)
|
||||
{
|
||||
KonstA = (FVoidObj *)mem;
|
||||
}
|
||||
else
|
||||
{
|
||||
KonstA = NULL;
|
||||
}
|
||||
CodeSize = numops;
|
||||
NumKonstD = numkonstd;
|
||||
NumKonstF = numkonstf;
|
||||
NumKonstS = numkonsts;
|
||||
NumKonstA = numkonsta;
|
||||
}
|
||||
|
||||
void VMScriptFunction::InitExtra(void *addr)
|
||||
{
|
||||
char *caddr = (char*)addr;
|
||||
|
||||
for (auto tao : SpecialInits)
|
||||
{
|
||||
tao.first->InitializeValue(caddr + tao.second, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void VMScriptFunction::DestroyExtra(void *addr)
|
||||
{
|
||||
char *caddr = (char*)addr;
|
||||
|
||||
for (auto tao : SpecialInits)
|
||||
{
|
||||
tao.first->DestroyValue(caddr + tao.second);
|
||||
}
|
||||
}
|
||||
|
||||
int VMScriptFunction::AllocExtraStack(PType *type)
|
||||
{
|
||||
int address = ((ExtraSpace + type->Align - 1) / type->Align) * type->Align;
|
||||
ExtraSpace = address + type->Size;
|
||||
type->SetDefaultValue(nullptr, address, &SpecialInits);
|
||||
return address;
|
||||
}
|
||||
|
||||
int VMScriptFunction::PCToLine(const VMOP *pc)
|
||||
{
|
||||
int PCIndex = int(pc - Code);
|
||||
if (LineInfoCount == 1) return LineInfo[0].LineNumber;
|
||||
for (unsigned i = 1; i < LineInfoCount; i++)
|
||||
{
|
||||
if (LineInfo[i].InstructionIndex > PCIndex)
|
||||
{
|
||||
return LineInfo[i - 1].LineNumber;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool CanJit(VMScriptFunction *func)
|
||||
{
|
||||
// Asmjit has a 256 register limit. Stay safely away from it as the jit compiler uses a few for temporaries as well.
|
||||
// Any function exceeding the limit will use the VM - a fair punishment to someone for writing a function so bloated ;)
|
||||
|
||||
int maxregs = 200;
|
||||
if (func->NumRegA + func->NumRegD + func->NumRegF + func->NumRegS < maxregs)
|
||||
return true;
|
||||
|
||||
Printf(TEXTCOLOR_ORANGE "%s is using too many registers (%d of max %d)! Function will not use native code.\n", func->PrintableName.GetChars(), func->NumRegA + func->NumRegD + func->NumRegF + func->NumRegS, maxregs);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int VMScriptFunction::FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret)
|
||||
{
|
||||
#ifdef HAVE_VM_JIT
|
||||
if (vm_jit && CanJit(static_cast<VMScriptFunction*>(func)))
|
||||
{
|
||||
func->ScriptCall = JitCompile(static_cast<VMScriptFunction*>(func));
|
||||
if (!func->ScriptCall)
|
||||
func->ScriptCall = VMExec;
|
||||
}
|
||||
else
|
||||
#endif // HAVE_VM_JIT
|
||||
{
|
||||
func->ScriptCall = VMExec;
|
||||
}
|
||||
|
||||
return func->ScriptCall(func, params, numparams, ret, numret);
|
||||
}
|
||||
|
||||
int VMNativeFunction::NativeScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *returns, int numret)
|
||||
{
|
||||
try
|
||||
{
|
||||
VMCycles[0].Unclock();
|
||||
numret = static_cast<VMNativeFunction *>(func)->NativeCall(VM_INVOKE(params, numparams, returns, numret, func->RegTypes));
|
||||
VMCycles[0].Clock();
|
||||
|
||||
return numret;
|
||||
}
|
||||
catch (CVMAbortException &err)
|
||||
{
|
||||
err.MaybePrintMessage();
|
||||
err.stacktrace.AppendFormat("Called from %s\n", func->PrintableName.GetChars());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrame :: InitRegS
|
||||
//
|
||||
// Initialize the string registers of a newly-allocated VMFrame.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void VMFrame::InitRegS()
|
||||
{
|
||||
FString *regs = GetRegS();
|
||||
for (int i = 0; i < NumRegS; ++i)
|
||||
{
|
||||
::new(®s[i]) FString;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack - Constructor
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrameStack::VMFrameStack()
|
||||
{
|
||||
Blocks = NULL;
|
||||
UnusedBlocks = NULL;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack - Destructor
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrameStack::~VMFrameStack()
|
||||
{
|
||||
while (PopFrame() != NULL)
|
||||
{ }
|
||||
if (Blocks != NULL)
|
||||
{
|
||||
BlockHeader *block, *next;
|
||||
for (block = Blocks; block != NULL; block = next)
|
||||
{
|
||||
next = block->NextBlock;
|
||||
delete[] (VM_UBYTE *)block;
|
||||
}
|
||||
Blocks = NULL;
|
||||
}
|
||||
if (UnusedBlocks != NULL)
|
||||
{
|
||||
BlockHeader *block, *next;
|
||||
for (block = UnusedBlocks; block != NULL; block = next)
|
||||
{
|
||||
next = block->NextBlock;
|
||||
delete[] (VM_UBYTE *)block;
|
||||
}
|
||||
UnusedBlocks = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: AllocFrame
|
||||
//
|
||||
// Allocates a frame from the stack suitable for calling a particular
|
||||
// function.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::AllocFrame(VMScriptFunction *func)
|
||||
{
|
||||
VMFrame *frame = Alloc(func->StackSize);
|
||||
frame->Func = func;
|
||||
frame->NumRegD = func->NumRegD;
|
||||
frame->NumRegF = func->NumRegF;
|
||||
frame->NumRegS = func->NumRegS;
|
||||
frame->NumRegA = func->NumRegA;
|
||||
frame->MaxParam = func->MaxParam;
|
||||
frame->Func = func;
|
||||
frame->InitRegS();
|
||||
if (func->SpecialInits.Size())
|
||||
{
|
||||
func->InitExtra(frame->GetExtra());
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: Alloc
|
||||
//
|
||||
// Allocates space for a frame. Its size will be rounded up to a multiple
|
||||
// of 16 bytes.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::Alloc(int size)
|
||||
{
|
||||
BlockHeader *block;
|
||||
VMFrame *frame, *parent;
|
||||
|
||||
size = (size + 15) & ~15;
|
||||
block = Blocks;
|
||||
if (block != NULL)
|
||||
{
|
||||
parent = block->LastFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent = NULL;
|
||||
}
|
||||
if (block == NULL || ((VM_UBYTE *)block + block->BlockSize) < (block->FreeSpace + size))
|
||||
{ // Not enough space. Allocate a new block.
|
||||
int blocksize = ((sizeof(BlockHeader) + 15) & ~15) + size;
|
||||
BlockHeader **blockp;
|
||||
if (blocksize < BLOCK_SIZE)
|
||||
{
|
||||
blocksize = BLOCK_SIZE;
|
||||
}
|
||||
for (blockp = &UnusedBlocks, block = *blockp; block != NULL; block = block->NextBlock)
|
||||
{
|
||||
if (block->BlockSize >= blocksize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (block != NULL)
|
||||
{
|
||||
*blockp = block->NextBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
block = (BlockHeader *)new VM_UBYTE[blocksize];
|
||||
block->BlockSize = blocksize;
|
||||
}
|
||||
block->InitFreeSpace();
|
||||
block->LastFrame = NULL;
|
||||
block->NextBlock = Blocks;
|
||||
Blocks = block;
|
||||
}
|
||||
frame = (VMFrame *)block->FreeSpace;
|
||||
memset(frame, 0, size);
|
||||
frame->ParentFrame = parent;
|
||||
block->FreeSpace += size;
|
||||
block->LastFrame = frame;
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: PopFrame
|
||||
//
|
||||
// Pops the top frame off the stack, returning a pointer to the new top
|
||||
// frame.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::PopFrame()
|
||||
{
|
||||
if (Blocks == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
VMFrame *frame = Blocks->LastFrame;
|
||||
if (frame == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
auto Func = static_cast<VMScriptFunction *>(frame->Func);
|
||||
if (Func->SpecialInits.Size())
|
||||
{
|
||||
Func->DestroyExtra(frame->GetExtra());
|
||||
}
|
||||
// Free any string registers this frame had.
|
||||
FString *regs = frame->GetRegS();
|
||||
for (int i = frame->NumRegS; i != 0; --i)
|
||||
{
|
||||
(regs++)->~FString();
|
||||
}
|
||||
VMFrame *parent = frame->ParentFrame;
|
||||
if (parent == NULL)
|
||||
{
|
||||
// Popping the last frame off the stack.
|
||||
if (Blocks != NULL)
|
||||
{
|
||||
assert(Blocks->NextBlock == NULL);
|
||||
Blocks->LastFrame = NULL;
|
||||
Blocks->InitFreeSpace();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if ((VM_UBYTE *)parent < (VM_UBYTE *)Blocks || (VM_UBYTE *)parent >= (VM_UBYTE *)Blocks + Blocks->BlockSize)
|
||||
{ // Parent frame is in a different block, so move this one to the unused list.
|
||||
BlockHeader *next = Blocks->NextBlock;
|
||||
assert(next != NULL);
|
||||
assert((VM_UBYTE *)parent >= (VM_UBYTE *)next && (VM_UBYTE *)parent < (VM_UBYTE *)next + next->BlockSize);
|
||||
Blocks->NextBlock = UnusedBlocks;
|
||||
UnusedBlocks = Blocks;
|
||||
Blocks = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
Blocks->LastFrame = parent;
|
||||
Blocks->FreeSpace = (VM_UBYTE *)frame;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: Call
|
||||
//
|
||||
// Calls a function, either native or scripted. If an exception occurs while
|
||||
// executing, the stack is cleaned up. If trap is non-NULL, it is set to the
|
||||
// VMException that was caught and the return value is negative. Otherwise,
|
||||
// any caught exceptions will be rethrown. Under normal termination, the
|
||||
// return value is the number of results from the function.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
int VMCall(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults/*, VMException **trap*/)
|
||||
{
|
||||
#if 0
|
||||
try
|
||||
#endif
|
||||
{
|
||||
if (func->VarFlags & VARF_Native)
|
||||
{
|
||||
return static_cast<VMNativeFunction *>(func)->NativeCall(VM_INVOKE(params, numparams, results, numresults, func->RegTypes));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto code = static_cast<VMScriptFunction *>(func)->Code;
|
||||
// handle empty functions consisting of a single return explicitly so that empty virtual callbacks do not need to set up an entire VM frame.
|
||||
// code cann be null here in case of some non-fatal DECORATE errors.
|
||||
if (code == nullptr || code->word == (0x00808000|OP_RET))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (code->word == (0x00048000|OP_RET))
|
||||
{
|
||||
if (numresults == 0) return 0;
|
||||
results[0].SetInt(static_cast<VMScriptFunction *>(func)->KonstD[0]);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
VMCycles[0].Clock();
|
||||
|
||||
auto sfunc = static_cast<VMScriptFunction *>(func);
|
||||
int numret = sfunc->ScriptCall(sfunc, params, numparams, results, numresults);
|
||||
VMCycles[0].Unclock();
|
||||
return numret;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
catch (VMException *exception)
|
||||
{
|
||||
if (trap != NULL)
|
||||
{
|
||||
*trap = exception;
|
||||
return -1;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int VMCallWithDefaults(VMFunction *func, TArray<VMValue> ¶ms, VMReturn *results, int numresults/*, VMException **trap = NULL*/)
|
||||
{
|
||||
if (func->DefaultArgs.Size() > params.Size())
|
||||
{
|
||||
auto oldp = params.Size();
|
||||
params.Resize(func->DefaultArgs.Size());
|
||||
for (unsigned i = oldp; i < params.Size(); i++)
|
||||
{
|
||||
params[i] = func->DefaultArgs[i];
|
||||
}
|
||||
}
|
||||
return VMCall(func, params.Data(), params.Size(), results, numresults);
|
||||
}
|
||||
|
||||
|
||||
// Exception stuff for the VM is intentionally placed there, because having this in vmexec.cpp would subject it to inlining
|
||||
// which we do not want because it increases the local stack requirements of Exec which are already too high.
|
||||
FString CVMAbortException::stacktrace;
|
||||
|
||||
CVMAbortException::CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap)
|
||||
{
|
||||
SetMessage("VM execution aborted: ");
|
||||
switch (reason)
|
||||
{
|
||||
case X_READ_NIL:
|
||||
AppendMessage("tried to read from address zero.");
|
||||
break;
|
||||
|
||||
case X_WRITE_NIL:
|
||||
AppendMessage("tried to write to address zero.");
|
||||
break;
|
||||
|
||||
case X_TOO_MANY_TRIES:
|
||||
AppendMessage("too many try-catch blocks.");
|
||||
break;
|
||||
|
||||
case X_ARRAY_OUT_OF_BOUNDS:
|
||||
AppendMessage("array access out of bounds.");
|
||||
break;
|
||||
|
||||
case X_DIVISION_BY_ZERO:
|
||||
AppendMessage("division by zero.");
|
||||
break;
|
||||
|
||||
case X_BAD_SELF:
|
||||
AppendMessage("invalid self pointer.");
|
||||
break;
|
||||
|
||||
case X_FORMAT_ERROR:
|
||||
AppendMessage("string format failed.");
|
||||
break;
|
||||
|
||||
case X_OTHER:
|
||||
// no prepended message.
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
size_t len = strlen(m_Message);
|
||||
mysnprintf(m_Message + len, MAX_ERRORTEXT - len, "Unknown reason %d", reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (moreinfo != nullptr)
|
||||
{
|
||||
AppendMessage(" ");
|
||||
size_t len = strlen(m_Message);
|
||||
myvsnprintf(m_Message + len, MAX_ERRORTEXT - len, moreinfo, ap);
|
||||
}
|
||||
|
||||
if (vm_jit)
|
||||
stacktrace = JitCaptureStackTrace(1, false);
|
||||
else
|
||||
stacktrace = "";
|
||||
}
|
||||
|
||||
// Print this only once on the first catch block.
|
||||
void CVMAbortException::MaybePrintMessage()
|
||||
{
|
||||
auto m = GetMessage();
|
||||
if (m != nullptr)
|
||||
{
|
||||
Printf(TEXTCOLOR_RED);
|
||||
Printf("%s\n", m);
|
||||
SetMessage("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, moreinfo);
|
||||
throw CVMAbortException(reason, moreinfo, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, moreinfo);
|
||||
|
||||
CVMAbortException err(reason, moreinfo, ap);
|
||||
|
||||
err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(line));
|
||||
throw err;
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(DObject, ThrowAbortException)
|
||||
{
|
||||
PARAM_PROLOGUE;
|
||||
FString s = FStringFormat(VM_ARGS_NAMES);
|
||||
ThrowAbortException(X_OTHER, s.GetChars());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NullParam(const char *varname)
|
||||
{
|
||||
ThrowAbortException(X_READ_NIL, "In function parameter %s", varname);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void ThrowVMException(VMException *x)
|
||||
{
|
||||
throw x;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void ClearGlobalVMStack()
|
||||
{
|
||||
while (GlobalVMStack.PopFrame() != nullptr)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ADD_STAT(VM)
|
||||
{
|
||||
double added = 0;
|
||||
int addedc = 0;
|
||||
double peak = 0;
|
||||
for (auto d : VMCycles)
|
||||
{
|
||||
added += d.TimeMS();
|
||||
peak = MAX<double>(peak, d.TimeMS());
|
||||
}
|
||||
for (auto d : VMCalls) addedc += d;
|
||||
memmove(&VMCycles[1], &VMCycles[0], 9 * sizeof(cycle_t));
|
||||
memmove(&VMCalls[1], &VMCalls[0], 9 * sizeof(int));
|
||||
VMCycles[0].Reset();
|
||||
VMCalls[0] = 0;
|
||||
return FStringf("VM time in last 10 tics: %f ms, %d calls, peak = %f ms", added, addedc, peak);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
CCMD(vmengine)
|
||||
{
|
||||
if (argv.argc() == 2)
|
||||
{
|
||||
if (stricmp(argv[1], "default") == 0)
|
||||
{
|
||||
VMSelectEngine(VMEngine_Default);
|
||||
return;
|
||||
}
|
||||
else if (stricmp(argv[1], "checked") == 0)
|
||||
{
|
||||
VMSelectEngine(VMEngine_Checked);
|
||||
return;
|
||||
}
|
||||
else if (stricmp(argv[1], "unchecked") == 0)
|
||||
{
|
||||
VMSelectEngine(VMEngine_Unchecked);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Printf("Usage: vmengine <default|checked|unchecked>\n");
|
||||
}
|
||||
|
483
source/common/scripting/vm/vmintern.h
Normal file
483
source/common/scripting/vm/vmintern.h
Normal file
|
@ -0,0 +1,483 @@
|
|||
#pragma once
|
||||
|
||||
#include "vm.h"
|
||||
#include <csetjmp>
|
||||
|
||||
class VMScriptFunction;
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#define VM_DEFINE_OP2(TYPE, ARG1, ARG2) TYPE ARG2, ARG1
|
||||
#define VM_DEFINE_OP4(TYPE, ARG1, ARG2, ARG3, ARG4) TYPE ARG4, ARG3, ARG2, ARG1
|
||||
#else // little endian
|
||||
#define VM_DEFINE_OP2(TYPE, ARG1, ARG2) TYPE ARG1, ARG2
|
||||
#define VM_DEFINE_OP4(TYPE, ARG1, ARG2, ARG3, ARG4) TYPE ARG1, ARG2, ARG3, ARG4
|
||||
#endif // __BIG_ENDIAN__
|
||||
|
||||
union VMOP
|
||||
{
|
||||
struct
|
||||
{
|
||||
VM_DEFINE_OP4(VM_UBYTE, op, a, b, c);
|
||||
};
|
||||
struct
|
||||
{
|
||||
VM_DEFINE_OP4(VM_SBYTE, pad0, as, bs, cs);
|
||||
};
|
||||
struct
|
||||
{
|
||||
VM_DEFINE_OP2(VM_SWORD, pad1:8, i24:24);
|
||||
};
|
||||
struct
|
||||
{
|
||||
VM_DEFINE_OP2(VM_SWORD, pad2:16, i16:16);
|
||||
};
|
||||
struct
|
||||
{
|
||||
VM_DEFINE_OP2(VM_UHALF, pad3, i16u);
|
||||
};
|
||||
VM_UWORD word;
|
||||
|
||||
// Interesting fact: VC++ produces better code for i16 when it's defined
|
||||
// as a bitfield than when it's defined as two discrete units.
|
||||
// Compare:
|
||||
// mov eax,dword ptr [op] ; As two discrete units
|
||||
// shr eax,10h
|
||||
// movsx eax,ax
|
||||
// versus:
|
||||
// mov eax,dword ptr [op] ; As a bitfield
|
||||
// sar eax,10h
|
||||
};
|
||||
|
||||
#undef VM_DEFINE_OP4
|
||||
#undef VM_DEFINE_OP2
|
||||
|
||||
enum
|
||||
{
|
||||
#include "vmops.h"
|
||||
NUM_OPS
|
||||
};
|
||||
|
||||
// Flags for A field of CMPS
|
||||
enum
|
||||
{
|
||||
CMP_CHECK = 1,
|
||||
|
||||
CMP_EQ = 0,
|
||||
CMP_LT = 2,
|
||||
CMP_LE = 4,
|
||||
CMP_METHOD_MASK = 6,
|
||||
|
||||
CMP_BK = 8,
|
||||
CMP_CK = 16,
|
||||
CMP_APPROX = 32,
|
||||
};
|
||||
|
||||
// Floating point operations for FLOP
|
||||
enum
|
||||
{
|
||||
FLOP_ABS,
|
||||
FLOP_NEG,
|
||||
FLOP_EXP,
|
||||
FLOP_LOG,
|
||||
FLOP_LOG10,
|
||||
FLOP_SQRT,
|
||||
FLOP_CEIL,
|
||||
FLOP_FLOOR,
|
||||
|
||||
FLOP_ACOS, // This group works with radians
|
||||
FLOP_ASIN,
|
||||
FLOP_ATAN,
|
||||
FLOP_COS,
|
||||
FLOP_SIN,
|
||||
FLOP_TAN,
|
||||
|
||||
FLOP_ACOS_DEG, // This group works with degrees
|
||||
FLOP_ASIN_DEG,
|
||||
FLOP_ATAN_DEG,
|
||||
FLOP_COS_DEG,
|
||||
FLOP_SIN_DEG,
|
||||
FLOP_TAN_DEG,
|
||||
|
||||
FLOP_COSH,
|
||||
FLOP_SINH,
|
||||
FLOP_TANH,
|
||||
|
||||
FLOP_ROUND,
|
||||
};
|
||||
|
||||
// Cast operations
|
||||
enum
|
||||
{
|
||||
CAST_I2F,
|
||||
CAST_I2S,
|
||||
CAST_U2F,
|
||||
CAST_U2S,
|
||||
CAST_F2I,
|
||||
CAST_F2U,
|
||||
CAST_F2S,
|
||||
CAST_P2S,
|
||||
CAST_S2I,
|
||||
CAST_S2F,
|
||||
CAST_S2N,
|
||||
CAST_N2S,
|
||||
CAST_S2Co,
|
||||
CAST_S2So,
|
||||
CAST_Co2S,
|
||||
CAST_So2S,
|
||||
CAST_V22S,
|
||||
CAST_V32S,
|
||||
CAST_SID2S,
|
||||
CAST_TID2S,
|
||||
|
||||
CASTB_I,
|
||||
CASTB_F,
|
||||
CASTB_A,
|
||||
CASTB_S
|
||||
};
|
||||
|
||||
enum EVMOpMode
|
||||
{
|
||||
MODE_ASHIFT = 0,
|
||||
MODE_BSHIFT = 4,
|
||||
MODE_CSHIFT = 8,
|
||||
MODE_BCSHIFT = 12,
|
||||
|
||||
MODE_ATYPE = 15 << MODE_ASHIFT,
|
||||
MODE_BTYPE = 15 << MODE_BSHIFT,
|
||||
MODE_CTYPE = 15 << MODE_CSHIFT,
|
||||
MODE_BCTYPE = 31 << MODE_BCSHIFT,
|
||||
|
||||
MODE_I = 0,
|
||||
MODE_F,
|
||||
MODE_S,
|
||||
MODE_P,
|
||||
MODE_V,
|
||||
MODE_X,
|
||||
MODE_KI,
|
||||
MODE_KF,
|
||||
MODE_KS,
|
||||
MODE_KP,
|
||||
MODE_KV,
|
||||
MODE_UNUSED,
|
||||
MODE_IMMS,
|
||||
MODE_IMMZ,
|
||||
MODE_JOINT,
|
||||
MODE_CMP,
|
||||
|
||||
MODE_PARAM,
|
||||
MODE_PARAM24,
|
||||
MODE_THROW,
|
||||
MODE_CATCH,
|
||||
MODE_CAST,
|
||||
|
||||
MODE_AI = MODE_I << MODE_ASHIFT,
|
||||
MODE_AF = MODE_F << MODE_ASHIFT,
|
||||
MODE_AS = MODE_S << MODE_ASHIFT,
|
||||
MODE_AP = MODE_P << MODE_ASHIFT,
|
||||
MODE_AV = MODE_V << MODE_ASHIFT,
|
||||
MODE_AX = MODE_X << MODE_ASHIFT,
|
||||
MODE_AKP = MODE_KP << MODE_ASHIFT,
|
||||
MODE_AUNUSED = MODE_UNUSED << MODE_ASHIFT,
|
||||
MODE_AIMMS = MODE_IMMS << MODE_ASHIFT,
|
||||
MODE_AIMMZ = MODE_IMMZ << MODE_ASHIFT,
|
||||
MODE_ACMP = MODE_CMP << MODE_ASHIFT,
|
||||
|
||||
MODE_BI = MODE_I << MODE_BSHIFT,
|
||||
MODE_BF = MODE_F << MODE_BSHIFT,
|
||||
MODE_BS = MODE_S << MODE_BSHIFT,
|
||||
MODE_BP = MODE_P << MODE_BSHIFT,
|
||||
MODE_BV = MODE_V << MODE_BSHIFT,
|
||||
MODE_BX = MODE_X << MODE_BSHIFT,
|
||||
MODE_BKI = MODE_KI << MODE_BSHIFT,
|
||||
MODE_BKF = MODE_KF << MODE_BSHIFT,
|
||||
MODE_BKS = MODE_KS << MODE_BSHIFT,
|
||||
MODE_BKP = MODE_KP << MODE_BSHIFT,
|
||||
MODE_BKV = MODE_KV << MODE_BSHIFT,
|
||||
MODE_BUNUSED = MODE_UNUSED << MODE_BSHIFT,
|
||||
MODE_BIMMS = MODE_IMMS << MODE_BSHIFT,
|
||||
MODE_BIMMZ = MODE_IMMZ << MODE_BSHIFT,
|
||||
|
||||
MODE_CI = MODE_I << MODE_CSHIFT,
|
||||
MODE_CF = MODE_F << MODE_CSHIFT,
|
||||
MODE_CS = MODE_S << MODE_CSHIFT,
|
||||
MODE_CP = MODE_P << MODE_CSHIFT,
|
||||
MODE_CV = MODE_V << MODE_CSHIFT,
|
||||
MODE_CX = MODE_X << MODE_CSHIFT,
|
||||
MODE_CKI = MODE_KI << MODE_CSHIFT,
|
||||
MODE_CKF = MODE_KF << MODE_CSHIFT,
|
||||
MODE_CKS = MODE_KS << MODE_CSHIFT,
|
||||
MODE_CKP = MODE_KP << MODE_CSHIFT,
|
||||
MODE_CKV = MODE_KV << MODE_CSHIFT,
|
||||
MODE_CUNUSED = MODE_UNUSED << MODE_CSHIFT,
|
||||
MODE_CIMMS = MODE_IMMS << MODE_CSHIFT,
|
||||
MODE_CIMMZ = MODE_IMMZ << MODE_CSHIFT,
|
||||
|
||||
MODE_ABCJOINT = (MODE_JOINT << MODE_ASHIFT) | (MODE_JOINT << MODE_BSHIFT) | (MODE_JOINT << MODE_CSHIFT),
|
||||
MODE_BCJOINT = (MODE_JOINT << MODE_BSHIFT) | (MODE_JOINT << MODE_CSHIFT),
|
||||
MODE_BCKI = MODE_KI << MODE_BCSHIFT,
|
||||
MODE_BCKF = MODE_KF << MODE_BCSHIFT,
|
||||
MODE_BCKS = MODE_KS << MODE_BCSHIFT,
|
||||
MODE_BCKP = MODE_KP << MODE_BCSHIFT,
|
||||
MODE_BCIMMS = MODE_IMMS << MODE_BCSHIFT,
|
||||
MODE_BCIMMZ = MODE_IMMZ << MODE_BCSHIFT,
|
||||
MODE_BCPARAM = MODE_PARAM << MODE_BCSHIFT,
|
||||
MODE_BCTHROW = MODE_THROW << MODE_BCSHIFT,
|
||||
MODE_BCCATCH = MODE_CATCH << MODE_BCSHIFT,
|
||||
MODE_BCCAST = MODE_CAST << MODE_BCSHIFT,
|
||||
};
|
||||
|
||||
struct VMOpInfo
|
||||
{
|
||||
const char *Name;
|
||||
int Mode;
|
||||
};
|
||||
|
||||
extern const VMOpInfo OpInfo[NUM_OPS];
|
||||
|
||||
|
||||
// VM frame layout:
|
||||
// VMFrame header
|
||||
// parameter stack - 16 byte boundary, 16 bytes each
|
||||
// double registers - 8 bytes each
|
||||
// string registers - 4 or 8 bytes each
|
||||
// address registers - 4 or 8 bytes each
|
||||
// data registers - 4 bytes each
|
||||
// address register tags-1 byte each
|
||||
// extra space - 16 byte boundary
|
||||
struct VMFrame
|
||||
{
|
||||
VMFrame *ParentFrame;
|
||||
VMFunction *Func;
|
||||
VM_UBYTE NumRegD;
|
||||
VM_UBYTE NumRegF;
|
||||
VM_UBYTE NumRegS;
|
||||
VM_UBYTE NumRegA;
|
||||
VM_UHALF MaxParam;
|
||||
VM_UHALF NumParam; // current number of parameters
|
||||
|
||||
static int FrameSize(int numregd, int numregf, int numregs, int numrega, int numparam, int numextra)
|
||||
{
|
||||
int size = (sizeof(VMFrame) + 15) & ~15;
|
||||
size += numparam * sizeof(VMValue);
|
||||
size += numregf * sizeof(double);
|
||||
size += numrega * sizeof(void *);
|
||||
size += numregs * sizeof(FString);
|
||||
size += numregd * sizeof(int);
|
||||
if (numextra != 0)
|
||||
{
|
||||
size = (size + 15) & ~15;
|
||||
size += numextra;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
VMValue *GetParam() const
|
||||
{
|
||||
assert(((size_t)this & 15) == 0 && "VM frame is unaligned");
|
||||
return (VMValue *)(((size_t)(this + 1) + 15) & ~15);
|
||||
}
|
||||
|
||||
double *GetRegF() const
|
||||
{
|
||||
return (double *)(GetParam() + MaxParam);
|
||||
}
|
||||
|
||||
FString *GetRegS() const
|
||||
{
|
||||
return (FString *)(GetRegF() + NumRegF);
|
||||
}
|
||||
|
||||
void **GetRegA() const
|
||||
{
|
||||
return (void **)(GetRegS() + NumRegS);
|
||||
}
|
||||
|
||||
int *GetRegD() const
|
||||
{
|
||||
return (int *)(GetRegA() + NumRegA);
|
||||
}
|
||||
|
||||
void *GetExtra() const
|
||||
{
|
||||
uint8_t *pbeg = (uint8_t*)(GetRegD() + NumRegD);
|
||||
ptrdiff_t ofs = pbeg - (uint8_t *)this;
|
||||
return (VM_UBYTE *)this + ((ofs + 15) & ~15);
|
||||
}
|
||||
|
||||
void GetAllRegs(int *&d, double *&f, FString *&s, void **&a, VMValue *¶m) const
|
||||
{
|
||||
// Calling the individual functions produces suboptimal code. :(
|
||||
param = GetParam();
|
||||
f = (double *)(param + MaxParam);
|
||||
s = (FString *)(f + NumRegF);
|
||||
a = (void **)(s + NumRegS);
|
||||
d = (int *)(a + NumRegA);
|
||||
}
|
||||
|
||||
void InitRegS();
|
||||
};
|
||||
|
||||
struct VMRegisters
|
||||
{
|
||||
VMRegisters(const VMFrame *frame)
|
||||
{
|
||||
frame->GetAllRegs(d, f, s, a, param);
|
||||
}
|
||||
|
||||
VMRegisters(const VMRegisters &o)
|
||||
: d(o.d), f(o.f), s(o.s), a(o.a), param(o.param)
|
||||
{ }
|
||||
|
||||
int *d;
|
||||
double *f;
|
||||
FString *s;
|
||||
void **a;
|
||||
VMValue *param;
|
||||
};
|
||||
|
||||
union FVoidObj
|
||||
{
|
||||
DObject *o;
|
||||
void *v;
|
||||
};
|
||||
|
||||
struct FStatementInfo
|
||||
{
|
||||
uint16_t InstructionIndex;
|
||||
uint16_t LineNumber;
|
||||
};
|
||||
|
||||
class VMFrameStack
|
||||
{
|
||||
public:
|
||||
VMFrameStack();
|
||||
~VMFrameStack();
|
||||
VMFrame *AllocFrame(VMScriptFunction *func);
|
||||
VMFrame *PopFrame();
|
||||
VMFrame *TopFrame()
|
||||
{
|
||||
assert(Blocks != NULL && Blocks->LastFrame != NULL);
|
||||
return Blocks->LastFrame;
|
||||
}
|
||||
static int OffsetLastFrame() { return (int)(ptrdiff_t)offsetof(BlockHeader, LastFrame); }
|
||||
private:
|
||||
enum { BLOCK_SIZE = 4096 }; // Default block size
|
||||
struct BlockHeader
|
||||
{
|
||||
BlockHeader *NextBlock;
|
||||
VMFrame *LastFrame;
|
||||
VM_UBYTE *FreeSpace;
|
||||
int BlockSize;
|
||||
|
||||
void InitFreeSpace()
|
||||
{
|
||||
FreeSpace = (VM_UBYTE *)(((size_t)(this + 1) + 15) & ~15);
|
||||
}
|
||||
};
|
||||
BlockHeader *Blocks;
|
||||
BlockHeader *UnusedBlocks;
|
||||
VMFrame *Alloc(int size);
|
||||
};
|
||||
|
||||
class VMParamFiller
|
||||
{
|
||||
public:
|
||||
VMParamFiller(const VMFrame *frame) : Reg(frame), RegD(0), RegF(0), RegS(0), RegA(0) {}
|
||||
VMParamFiller(const VMRegisters *reg) : Reg(*reg), RegD(0), RegF(0), RegS(0), RegA(0) {}
|
||||
|
||||
void ParamInt(int val)
|
||||
{
|
||||
Reg.d[RegD++] = val;
|
||||
}
|
||||
|
||||
void ParamFloat(double val)
|
||||
{
|
||||
Reg.f[RegF++] = val;
|
||||
}
|
||||
|
||||
void ParamString(FString &val)
|
||||
{
|
||||
Reg.s[RegS++] = val;
|
||||
}
|
||||
|
||||
void ParamString(const char *val)
|
||||
{
|
||||
Reg.s[RegS++] = val;
|
||||
}
|
||||
|
||||
void ParamObject(DObject *obj)
|
||||
{
|
||||
Reg.a[RegA] = obj;
|
||||
RegA++;
|
||||
}
|
||||
|
||||
void ParamPointer(void *ptr)
|
||||
{
|
||||
Reg.a[RegA] = ptr;
|
||||
RegA++;
|
||||
}
|
||||
|
||||
private:
|
||||
const VMRegisters Reg;
|
||||
int RegD, RegF, RegS, RegA;
|
||||
};
|
||||
|
||||
|
||||
enum EVMEngine
|
||||
{
|
||||
VMEngine_Default,
|
||||
VMEngine_Unchecked,
|
||||
VMEngine_Checked
|
||||
};
|
||||
|
||||
void VMSelectEngine(EVMEngine engine);
|
||||
extern int (*VMExec)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
|
||||
void VMFillParams(VMValue *params, VMFrame *callee, int numparam);
|
||||
|
||||
void VMDumpConstants(FILE *out, const VMScriptFunction *func);
|
||||
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func);
|
||||
|
||||
extern thread_local VMFrameStack GlobalVMStack;
|
||||
|
||||
typedef std::pair<const class PType *, unsigned> FTypeAndOffset;
|
||||
|
||||
typedef int(*JitFuncPtr)(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
|
||||
|
||||
class VMScriptFunction : public VMFunction
|
||||
{
|
||||
public:
|
||||
VMScriptFunction(FName name = NAME_None);
|
||||
~VMScriptFunction();
|
||||
void Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers);
|
||||
|
||||
VMOP *Code;
|
||||
FStatementInfo *LineInfo;
|
||||
FString SourceFileName;
|
||||
int *KonstD;
|
||||
double *KonstF;
|
||||
FString *KonstS;
|
||||
FVoidObj *KonstA;
|
||||
int ExtraSpace;
|
||||
int CodeSize; // Size of code in instructions (not bytes)
|
||||
unsigned LineInfoCount;
|
||||
unsigned StackSize;
|
||||
VM_UBYTE NumRegD;
|
||||
VM_UBYTE NumRegF;
|
||||
VM_UBYTE NumRegS;
|
||||
VM_UBYTE NumRegA;
|
||||
VM_UHALF NumKonstD;
|
||||
VM_UHALF NumKonstF;
|
||||
VM_UHALF NumKonstS;
|
||||
VM_UHALF NumKonstA;
|
||||
VM_UHALF MaxParam; // Maximum number of parameters this function has on the stack at once
|
||||
VM_UBYTE NumArgs; // Number of arguments this function takes
|
||||
TArray<FTypeAndOffset> SpecialInits; // list of all contents on the extra stack which require construction and destruction
|
||||
|
||||
void InitExtra(void *addr);
|
||||
void DestroyExtra(void *addr);
|
||||
int AllocExtraStack(PType *type);
|
||||
int PCToLine(const VMOP *pc);
|
||||
|
||||
private:
|
||||
static int FirstScriptCall(VMFunction *func, VMValue *params, int numparams, VMReturn *ret, int numret);
|
||||
};
|
258
source/common/scripting/vm/vmops.h
Normal file
258
source/common/scripting/vm/vmops.h
Normal file
|
@ -0,0 +1,258 @@
|
|||
#ifndef xx
|
||||
#define xx(op, name, mode, alt, kreg, ktype) OP_##op,
|
||||
#endif
|
||||
|
||||
// first row is the opcode
|
||||
// second row is the disassembly name
|
||||
// third row is the disassembly flags
|
||||
// fourth row is the alternative opcode if all 256 constant registers are exhausted.
|
||||
// fifth row is the constant register index in the opcode
|
||||
// sixth row is the constant register type.
|
||||
// OP_PARAM and OP_CMPS need special treatment because they encode this information in the instruction.
|
||||
|
||||
xx(NOP, nop, NOP, NOP, 0, 0) // no operation
|
||||
|
||||
// Load constants.
|
||||
xx(LI, li, LI, NOP, 0, 0) // load immediate signed 16-bit constant
|
||||
xx(LK, lk, LKI, NOP, 0, 0) // load integer constant
|
||||
xx(LKF, lk, LKF, NOP, 0, 0) // load float constant
|
||||
xx(LKS, lk, LKS, NOP, 0, 0) // load string constant
|
||||
xx(LKP, lk, LKP, NOP, 0, 0) // load pointer constant
|
||||
xx(LK_R, lk, RIRII8, NOP, 0, 0) // load integer constant indexed
|
||||
xx(LKF_R, lk, RFRII8, NOP, 0, 0) // load float constant indexed
|
||||
xx(LKS_R, lk, RSRII8, NOP, 0, 0) // load string constant indexed
|
||||
xx(LKP_R, lk, RPRII8, NOP, 0, 0) // load pointer constant indexed
|
||||
xx(LFP, lf, LFP, NOP, 0, 0) // load frame pointer
|
||||
xx(META, meta, RPRP, NOP, 0, 0) // load a class's meta data address
|
||||
xx(CLSS, clss, RPRP, NOP, 0, 0) // load a class's descriptor address
|
||||
|
||||
// Load from memory. rA = *(rB + rkC)
|
||||
xx(LB, lb, RIRPKI, LB_R, 4, REGT_INT) // load byte
|
||||
xx(LB_R, lb, RIRPRI, NOP, 0, 0)
|
||||
xx(LH, lh, RIRPKI, LH_R, 4, REGT_INT) // load halfword
|
||||
xx(LH_R, lh, RIRPRI, NOP, 0, 0)
|
||||
xx(LW, lw, RIRPKI, LW_R, 4, REGT_INT) // load word
|
||||
xx(LW_R, lw, RIRPRI, NOP, 0, 0)
|
||||
xx(LBU, lbu, RIRPKI, LBU_R, 4, REGT_INT) // load byte unsigned
|
||||
xx(LBU_R, lbu, RIRPRI, NOP, 0, 0)
|
||||
xx(LHU, lhu, RIRPKI, LHU_R, 4, REGT_INT) // load halfword unsigned
|
||||
xx(LHU_R, lhu, RIRPRI, NOP, 0, 0)
|
||||
xx(LSP, lsp, RFRPKI, LSP_R, 4, REGT_INT) // load single-precision fp
|
||||
xx(LSP_R, lsp, RFRPRI, NOP, 0, 0)
|
||||
xx(LDP, ldp, RFRPKI, LDP_R, 4, REGT_INT) // load double-precision fp
|
||||
xx(LDP_R, ldp, RFRPRI, NOP, 0, 0)
|
||||
xx(LS, ls, RSRPKI, LS_R, 4, REGT_INT) // load string
|
||||
xx(LS_R, ls, RSRPRI, NOP, 0, 0)
|
||||
xx(LO, lo, RPRPKI, LO_R, 4, REGT_INT) // load object
|
||||
xx(LO_R, lo, RPRPRI, NOP, 0, 0)
|
||||
xx(LP, lp, RPRPKI, LP_R, 4, REGT_INT) // load pointer
|
||||
xx(LP_R, lp, RPRPRI, NOP, 0, 0)
|
||||
xx(LV2, lv2, RVRPKI, LV2_R, 4, REGT_INT) // load vector2
|
||||
xx(LV2_R, lv2, RVRPRI, NOP, 0, 0)
|
||||
xx(LV3, lv3, RVRPKI, LV3_R, 4, REGT_INT) // load vector3
|
||||
xx(LV3_R, lv3, RVRPRI, NOP, 0, 0)
|
||||
xx(LCS, lcs, RSRPKI, LCS_R, 4, REGT_INT) // load string from char ptr.
|
||||
xx(LCS_R, lcs, RSRPRI, NOP, 0, 0)
|
||||
|
||||
xx(LBIT, lbit, RIRPI8, NOP, 0, 0) // rA = !!(*rB & C) -- *rB is a byte
|
||||
|
||||
// Store instructions. *(rA + rkC) = rB
|
||||
xx(SB, sb, RPRIKI, SB_R, 4, REGT_INT) // store byte
|
||||
xx(SB_R, sb, RPRIRI, NOP, 0, 0)
|
||||
xx(SH, sh, RPRIKI, SH_R, 4, REGT_INT) // store halfword
|
||||
xx(SH_R, sh, RPRIRI, NOP, 0, 0)
|
||||
xx(SW, sw, RPRIKI, SW_R, 4, REGT_INT) // store word
|
||||
xx(SW_R, sw, RPRIRI, NOP, 0, 0)
|
||||
xx(SSP, ssp, RPRFKI, SSP_R, 4, REGT_INT) // store single-precision fp
|
||||
xx(SSP_R, ssp, RPRFRI, NOP, 0, 0)
|
||||
xx(SDP, sdp, RPRFKI, SDP_R, 4, REGT_INT) // store double-precision fp
|
||||
xx(SDP_R, sdp, RPRFRI, NOP, 0, 0)
|
||||
xx(SS, ss, RPRSKI, SS_R, 4, REGT_INT) // store string
|
||||
xx(SS_R, ss, RPRSRI, NOP, 0, 0)
|
||||
xx(SP, sp, RPRPKI, SP_R, 4, REGT_INT) // store pointer
|
||||
xx(SP_R, sp, RPRPRI, NOP, 0, 0)
|
||||
xx(SO, so, RPRPKI, SO_R, 4, REGT_INT) // store object pointer with write barrier (only needed for non thinkers and non types)
|
||||
xx(SO_R, so, RPRPRI, NOP, 0, 0)
|
||||
xx(SV2, sv2, RPRVKI, SV2_R, 4, REGT_INT) // store vector2
|
||||
xx(SV2_R, sv2, RPRVRI, NOP, 0, 0)
|
||||
xx(SV3, sv3, RPRVKI, SV3_R, 4, REGT_INT) // store vector3
|
||||
xx(SV3_R, sv3, RPRVRI, NOP, 0, 0)
|
||||
|
||||
xx(SBIT, sbit, RPRII8, NOP, 0, 0) // *rA |= C if rB is true, *rA &= ~C otherwise
|
||||
|
||||
// Move instructions.
|
||||
xx(MOVE, mov, RIRI, NOP, 0, 0) // dA = dB
|
||||
xx(MOVEF, mov, RFRF, NOP, 0, 0) // fA = fB
|
||||
xx(MOVES, mov, RSRS, NOP, 0, 0) // sA = sB
|
||||
xx(MOVEA, mov, RPRP, NOP, 0, 0) // aA = aB
|
||||
xx(MOVEV2, mov2, RFRF, NOP, 0, 0) // fA = fB (2 elements)
|
||||
xx(MOVEV3, mov3, RFRF, NOP, 0, 0) // fA = fB (3 elements)
|
||||
xx(CAST, cast, CAST, NOP, 0, 0) // xA = xB, conversion specified by C
|
||||
xx(CASTB, castb, CAST, NOP, 0, 0) // xA = !!xB, type specified by C
|
||||
xx(DYNCAST_R, dyncast, RPRPRP, NOP, 0, 0) // aA = dyn_cast<aC>(aB);
|
||||
xx(DYNCAST_K, dyncast, RPRPKP, NOP, 0, 0) // aA = dyn_cast<aKC>(aB);
|
||||
xx(DYNCASTC_R, dyncastc, RPRPRP, NOP, 0, 0) // aA = dyn_cast<aC>(aB); for class types
|
||||
xx(DYNCASTC_K, dyncastc, RPRPKP, NOP, 0, 0) // aA = dyn_cast<aKC>(aB);
|
||||
|
||||
// Control flow.
|
||||
xx(TEST, test, RII16, NOP, 0, 0) // if (dA != BC) then pc++
|
||||
xx(TESTN, testn, RII16, NOP, 0, 0) // if (dA != -BC) then pc++
|
||||
xx(JMP, jmp, I24, NOP, 0, 0) // pc += ABC -- The ABC fields contain a signed 24-bit offset.
|
||||
xx(IJMP, ijmp, RII16, NOP, 0, 0) // pc += dA + BC -- BC is a signed offset. The target instruction must be a JMP.
|
||||
xx(PARAM, param, __BCP, NOP, 0, 0) // push parameter encoded in BC for function call (B=regtype, C=regnum)
|
||||
xx(PARAMI, parami, I24, NOP, 0, 0) // push immediate, signed integer for function call
|
||||
xx(CALL, call, RPI8I8, NOP, 0, 0) // Call function pkA with parameter count B and expected result count C
|
||||
xx(CALL_K, call, KPI8I8, CALL, 1, REGT_POINTER)
|
||||
xx(VTBL, vtbl, RPRPI8, NOP, 0, 0) // dereferences a virtual method table.
|
||||
xx(SCOPE, scope, RPI8, NOP, 0, 0) // Scope check at runtime.
|
||||
xx(RESULT, result, __BCP, NOP, 0, 0) // Result should go in register encoded in BC (in caller, after CALL)
|
||||
xx(RET, ret, I8BCP, NOP, 0, 0) // Copy value from register encoded in BC to return value A, possibly returning
|
||||
xx(RETI, reti, I8I16, NOP, 0, 0) // Copy immediate from BC to return value A, possibly returning
|
||||
//xx(TRY, try, I24, NOP, 0, 0) // When an exception is thrown, start searching for a handler at pc + ABC
|
||||
//xx(UNTRY, untry, I8, NOP, 0, 0) // Pop A entries off the exception stack
|
||||
xx(THROW, throw, THROW, NOP, 0, 0) // A == 0: Throw exception object pB
|
||||
// A == 1: Throw exception object pkB
|
||||
// A >= 2: Throw VM exception of type BC
|
||||
//xx(CATCH, catch, CATCH, NOP, 0, 0) // A == 0: continue search on next try
|
||||
// A == 1: continue execution at instruction immediately following CATCH (catches any exception)
|
||||
// A == 2: (pB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
|
||||
// A == 3: (pkB == <type of exception thrown>) then pc++ ; next instruction must JMP to another CATCH
|
||||
// for A > 0, exception is stored in pC
|
||||
xx(BOUND, bound, RII16, NOP, 0, 0) // if rA < 0 or rA >= BC, throw exception
|
||||
xx(BOUND_K, bound, LKI, NOP, 0, 0) // if rA < 0 or rA >= const[BC], throw exception
|
||||
xx(BOUND_R, bound, RIRI, NOP, 0, 0) // if rA < 0 or rA >= rB, throw exception
|
||||
|
||||
// String instructions.
|
||||
xx(CONCAT, concat, RSRSRS, NOP, 0, 0) // sA = sB..sC
|
||||
xx(LENS, lens, RIRS, NOP, 0, 0) // dA = sB.Length
|
||||
xx(CMPS, cmps, I8RXRX, NOP, 0, 0) // if ((skB op skC) != (A & 1)) then pc++
|
||||
|
||||
// Integer math.
|
||||
xx(SLL_RR, sll, RIRIRI, NOP, 0, 0) // dA = dkB << diC
|
||||
xx(SLL_RI, sll, RIRII8, NOP, 0, 0)
|
||||
xx(SLL_KR, sll, RIKIRI, SLL_RR, 2, REGT_INT)
|
||||
xx(SRL_RR, srl, RIRIRI, NOP, 0, 0) // dA = dkB >> diC -- unsigned
|
||||
xx(SRL_RI, srl, RIRII8, NOP, 0, 0)
|
||||
xx(SRL_KR, srl, RIKIRI, SRL_RR, 2, REGT_INT)
|
||||
xx(SRA_RR, sra, RIRIRI, NOP, 0, 0) // dA = dkB >> diC -- signed
|
||||
xx(SRA_RI, sra, RIRII8, NOP, 0, 0)
|
||||
xx(SRA_KR, sra, RIKIRI, SRA_RR, 2, REGT_INT)
|
||||
xx(ADD_RR, add, RIRIRI, NOP, 0, 0) // dA = dB + dkC
|
||||
xx(ADD_RK, add, RIRIKI, ADD_RR, 4, REGT_INT)
|
||||
xx(ADDI, addi, RIRIIs, NOP, 0, 0) // dA = dB + C -- C is a signed 8-bit constant
|
||||
xx(SUB_RR, sub, RIRIRI, NOP, 0, 0) // dA = dkB - dkC
|
||||
xx(SUB_RK, sub, RIRIKI, SUB_RR, 4, REGT_INT)
|
||||
xx(SUB_KR, sub, RIKIRI, SUB_RR, 2, REGT_INT)
|
||||
xx(MUL_RR, mul, RIRIRI, NOP, 0, 0) // dA = dB * dkC
|
||||
xx(MUL_RK, mul, RIRIKI, MUL_RR, 4, REGT_INT)
|
||||
xx(DIV_RR, div, RIRIRI, NOP, 0, 0) // dA = dkB / dkC (signed)
|
||||
xx(DIV_RK, div, RIRIKI, DIV_RR, 4, REGT_INT)
|
||||
xx(DIV_KR, div, RIKIRI, DIV_RR, 2, REGT_INT)
|
||||
xx(DIVU_RR, divu, RIRIRI, NOP, 0, 0) // dA = dkB / dkC (unsigned)
|
||||
xx(DIVU_RK, divu, RIRIKI, DIVU_RR,4, REGT_INT)
|
||||
xx(DIVU_KR, divu, RIKIRI, DIVU_RR,2, REGT_INT)
|
||||
xx(MOD_RR, mod, RIRIRI, NOP, 0, 0) // dA = dkB % dkC (signed)
|
||||
xx(MOD_RK, mod, RIRIKI, MOD_RR, 4, REGT_INT)
|
||||
xx(MOD_KR, mod, RIKIRI, MOD_RR, 2, REGT_INT)
|
||||
xx(MODU_RR, modu, RIRIRI, NOP, 0, 0) // dA = dkB % dkC (unsigned)
|
||||
xx(MODU_RK, modu, RIRIKI, MODU_RR,4, REGT_INT)
|
||||
xx(MODU_KR, modu, RIKIRI, MODU_RR,2, REGT_INT)
|
||||
xx(AND_RR, and, RIRIRI, NOP, 0, 0) // dA = dB & dkC
|
||||
xx(AND_RK, and, RIRIKI, AND_RR, 4, REGT_INT)
|
||||
xx(OR_RR, or, RIRIRI, NOP, 0, 0) // dA = dB | dkC
|
||||
xx(OR_RK, or, RIRIKI, OR_RR, 4, REGT_INT)
|
||||
xx(XOR_RR, xor, RIRIRI, NOP, 0, 0) // dA = dB ^ dkC
|
||||
xx(XOR_RK, xor, RIRIKI, XOR_RR, 4, REGT_INT)
|
||||
xx(MIN_RR, min, RIRIRI, NOP, 0, 0) // dA = min(dB,dkC)
|
||||
xx(MIN_RK, min, RIRIKI, MIN_RR, 4, REGT_INT)
|
||||
xx(MAX_RR, max, RIRIRI, NOP, 0, 0) // dA = max(dB,dkC)
|
||||
xx(MAX_RK, max, RIRIKI, MAX_RR, 4, REGT_INT)
|
||||
xx(MINU_RR, minu, RIRIRI, NOP, 0, 0) // dA = min(dB,dkC) unsigned
|
||||
xx(MINU_RK, minu, RIRIKI, MIN_RR, 4, REGT_INT)
|
||||
xx(MAXU_RR, maxu, RIRIRI, NOP, 0, 0) // dA = max(dB,dkC) unsigned
|
||||
xx(MAXU_RK, maxu, RIRIKI, MAX_RR, 4, REGT_INT)
|
||||
xx(ABS, abs, RIRI, NOP, 0, 0) // dA = abs(dB)
|
||||
xx(NEG, neg, RIRI, NOP, 0, 0) // dA = -dB
|
||||
xx(NOT, not, RIRI, NOP, 0, 0) // dA = ~dB
|
||||
xx(EQ_R, beq, CIRR, NOP, 0, 0) // if ((dB == dkC) != A) then pc++
|
||||
xx(EQ_K, beq, CIRK, EQ_R, 4, REGT_INT)
|
||||
xx(LT_RR, blt, CIRR, NOP, 0, 0) // if ((dkB < dkC) != A) then pc++
|
||||
xx(LT_RK, blt, CIRK, LT_RR, 4, REGT_INT)
|
||||
xx(LT_KR, blt, CIKR, LT_RR, 2, REGT_INT)
|
||||
xx(LE_RR, ble, CIRR, NOP, 0, 0) // if ((dkB <= dkC) != A) then pc++
|
||||
xx(LE_RK, ble, CIRK, LE_RR, 4, REGT_INT)
|
||||
xx(LE_KR, ble, CIKR, LE_RR, 2, REGT_INT)
|
||||
xx(LTU_RR, bltu, CIRR, NOP, 0, 0) // if ((dkB < dkC) != A) then pc++ -- unsigned
|
||||
xx(LTU_RK, bltu, CIRK, LTU_RR, 4, REGT_INT)
|
||||
xx(LTU_KR, bltu, CIKR, LTU_RR, 2, REGT_INT)
|
||||
xx(LEU_RR, bleu, CIRR, NOP, 0, 0) // if ((dkB <= dkC) != A) then pc++ -- unsigned
|
||||
xx(LEU_RK, bleu, CIRK, LEU_RR, 4, REGT_INT)
|
||||
xx(LEU_KR, bleu, CIKR, LEU_RR, 2, REGT_INT)
|
||||
|
||||
// Double-precision floating point math.
|
||||
xx(ADDF_RR, add, RFRFRF, NOP, 0, 0) // fA = fB + fkC
|
||||
xx(ADDF_RK, add, RFRFKF, ADDF_RR,4, REGT_FLOAT)
|
||||
xx(SUBF_RR, sub, RFRFRF, NOP, 0, 0) // fA = fkB - fkC
|
||||
xx(SUBF_RK, sub, RFRFKF, SUBF_RR,4, REGT_FLOAT)
|
||||
xx(SUBF_KR, sub, RFKFRF, SUBF_RR,2, REGT_FLOAT)
|
||||
xx(MULF_RR, mul, RFRFRF, NOP, 0, 0) // fA = fB * fkC
|
||||
xx(MULF_RK, mul, RFRFKF, MULF_RR,4, REGT_FLOAT)
|
||||
xx(DIVF_RR, div, RFRFRF, NOP, 0, 0) // fA = fkB / fkC
|
||||
xx(DIVF_RK, div, RFRFKF, DIVF_RR,4, REGT_FLOAT)
|
||||
xx(DIVF_KR, div, RFKFRF, DIVF_RR,2, REGT_FLOAT)
|
||||
xx(MODF_RR, mod, RFRFRF, NOP, 0, 0) // fA = fkB % fkC
|
||||
xx(MODF_RK, mod, RFRFKF, MODF_RR,4, REGT_FLOAT)
|
||||
xx(MODF_KR, mod, RFKFRF, MODF_RR,4, REGT_FLOAT)
|
||||
xx(POWF_RR, pow, RFRFRF, NOP, 0, 0) // fA = fkB ** fkC
|
||||
xx(POWF_RK, pow, RFRFKF, POWF_RR,4, REGT_FLOAT)
|
||||
xx(POWF_KR, pow, RFKFRF, POWF_RR,2, REGT_FLOAT)
|
||||
xx(MINF_RR, min, RFRFRF, NOP, 0, 0) // fA = min(fB)fkC)
|
||||
xx(MINF_RK, min, RFRFKF, MINF_RR,4, REGT_FLOAT)
|
||||
xx(MAXF_RR, max, RFRFRF, NOP, 0, 0) // fA = max(fB)fkC)
|
||||
xx(MAXF_RK, max, RFRFKF, MAXF_RR,4, REGT_FLOAT)
|
||||
xx(ATAN2, atan2, RFRFRF, NOP, 0, 0) // fA = atan2(fB,fC) result is in degrees
|
||||
xx(FLOP, flop, RFRFI8, NOP, 0, 0) // fA = f(fB) where function is selected by C
|
||||
xx(EQF_R, beq, CFRR, NOP, 0, 0) // if ((fB == fkC) != (A & 1)) then pc++
|
||||
xx(EQF_K, beq, CFRK, EQF_R, 4, REGT_FLOAT)
|
||||
xx(LTF_RR, blt, CFRR, NOP, 0, 0) // if ((fkB < fkC) != (A & 1)) then pc++
|
||||
xx(LTF_RK, blt, CFRK, LTF_RR, 4, REGT_FLOAT)
|
||||
xx(LTF_KR, blt, CFKR, LTF_RR, 2, REGT_FLOAT)
|
||||
xx(LEF_RR, ble, CFRR, NOP, 0, 0) // if ((fkb <= fkC) != (A & 1)) then pc++
|
||||
xx(LEF_RK, ble, CFRK, LEF_RR, 4, REGT_FLOAT)
|
||||
xx(LEF_KR, ble, CFKR, LEF_RR, 2, REGT_FLOAT)
|
||||
|
||||
// Vector math. (2D)
|
||||
xx(NEGV2, negv2, RVRV, NOP, 0, 0) // vA = -vB
|
||||
xx(ADDV2_RR, addv2, RVRVRV, NOP, 0, 0) // vA = vB + vkC
|
||||
xx(SUBV2_RR, subv2, RVRVRV, NOP, 0, 0) // vA = vkB - vkC
|
||||
xx(DOTV2_RR, dotv2, RVRVRV, NOP, 0, 0) // va = vB dot vkC
|
||||
xx(MULVF2_RR, mulv2, RVRVRF, NOP, 0, 0) // vA = vkB * fkC
|
||||
xx(MULVF2_RK, mulv2, RVRVKF, MULVF2_RR,4, REGT_FLOAT)
|
||||
xx(DIVVF2_RR, divv2, RVRVRF, NOP, 0, 0) // vA = vkB / fkC
|
||||
xx(DIVVF2_RK, divv2, RVRVKF, DIVVF2_RR,4, REGT_FLOAT)
|
||||
xx(LENV2, lenv2, RFRV, NOP, 0, 0) // fA = vB.Length
|
||||
xx(EQV2_R, beqv2, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 32)
|
||||
xx(EQV2_K, beqv2, CVRK, NOP, 0, 0) // this will never be used.
|
||||
|
||||
// Vector math (3D)
|
||||
xx(NEGV3, negv3, RVRV, NOP, 0, 0) // vA = -vB
|
||||
xx(ADDV3_RR, addv3, RVRVRV, NOP, 0, 0) // vA = vB + vkC
|
||||
xx(SUBV3_RR, subv3, RVRVRV, NOP, 0, 0) // vA = vkB - vkC
|
||||
xx(DOTV3_RR, dotv3, RVRVRV, NOP, 0, 0) // va = vB dot vkC
|
||||
xx(CROSSV_RR, crossv, RVRVRV, NOP, 0, 0) // vA = vkB cross vkC
|
||||
xx(MULVF3_RR, mulv3, RVRVRF, NOP, 0, 0) // vA = vkB * fkC
|
||||
xx(MULVF3_RK, mulv3, RVRVKF, MULVF3_RR,4, REGT_FLOAT)
|
||||
xx(DIVVF3_RR, divv3, RVRVRF, NOP, 0, 0) // vA = vkB / fkC
|
||||
xx(DIVVF3_RK, divv3, RVRVKF, DIVVF3_RR,4, REGT_FLOAT)
|
||||
xx(LENV3, lenv3, RFRV, NOP, 0, 0) // fA = vB.Length
|
||||
xx(EQV3_R, beqv3, CVRR, NOP, 0, 0) // if ((vB == vkC) != A) then pc++ (inexact if A & 33)
|
||||
xx(EQV3_K, beqv3, CVRK, NOP, 0, 0) // this will never be used.
|
||||
|
||||
// Pointer math.
|
||||
xx(ADDA_RR, add, RPRPRI, NOP, 0, 0) // pA = pB + dkC
|
||||
xx(ADDA_RK, add, RPRPKI, ADDA_RR,4, REGT_INT)
|
||||
xx(SUBA, sub, RIRPRP, NOP, 0, 0) // dA = pB - pC
|
||||
xx(EQA_R, beq, CPRR, NOP, 0, 0) // if ((pB == pkC) != A) then pc++
|
||||
xx(EQA_K, beq, CPRK, EQA_R, 4, REGT_POINTER)
|
||||
|
||||
#undef xx
|
Loading…
Add table
Add a link
Reference in a new issue