- started with cleanup and separation of DECORATE code.
* everything related to scripting is now placed in a subdirectory 'scripting', which itself is separated into DECORATE, ZSCRIPT, the VM and code generation. * a few items have been moved to different headers so that the DECORATE parser definitions can mostly be kept local. The only exception at the moment is the flags interface on which 3 source files depend.
This commit is contained in:
parent
6a8ab9a4d3
commit
b1a83bfd26
132 changed files with 234 additions and 198 deletions
1023
src/scripting/vm/vm.h
Normal file
1023
src/scripting/vm/vm.h
Normal file
File diff suppressed because it is too large
Load diff
636
src/scripting/vm/vmbuilder.cpp
Normal file
636
src/scripting/vm/vmbuilder.cpp
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
/*
|
||||
** vmbuilder.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 "vmbuilder.h"
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder - Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
VMFunctionBuilder::VMFunctionBuilder(bool selfcheck)
|
||||
{
|
||||
NumIntConstants = 0;
|
||||
NumFloatConstants = 0;
|
||||
NumAddressConstants = 0;
|
||||
NumStringConstants = 0;
|
||||
MaxParam = 0;
|
||||
ActiveParam = 0;
|
||||
IsActionFunc = selfcheck;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder - Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
VMFunctionBuilder::~VMFunctionBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: MakeFunction
|
||||
//
|
||||
// Creates a new VMScriptFunction out of the data passed to this class.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
VMScriptFunction *VMFunctionBuilder::MakeFunction()
|
||||
{
|
||||
VMScriptFunction *func = new VMScriptFunction;
|
||||
|
||||
func->Alloc(Code.Size(), NumIntConstants, NumFloatConstants, NumStringConstants, NumAddressConstants);
|
||||
|
||||
// Copy code block.
|
||||
memcpy(func->Code, &Code[0], Code.Size() * sizeof(VMOP));
|
||||
|
||||
// Create constant tables.
|
||||
if (NumIntConstants > 0)
|
||||
{
|
||||
FillIntConstants(func->KonstD);
|
||||
}
|
||||
if (NumFloatConstants > 0)
|
||||
{
|
||||
FillFloatConstants(func->KonstF);
|
||||
}
|
||||
if (NumAddressConstants > 0)
|
||||
{
|
||||
FillAddressConstants(func->KonstA, func->KonstATags());
|
||||
}
|
||||
if (NumStringConstants > 0)
|
||||
{
|
||||
FillStringConstants(func->KonstS);
|
||||
}
|
||||
|
||||
// Assign required register space.
|
||||
func->NumRegD = Registers[REGT_INT].MostUsed;
|
||||
func->NumRegF = Registers[REGT_FLOAT].MostUsed;
|
||||
func->NumRegA = Registers[REGT_POINTER].MostUsed;
|
||||
func->NumRegS = Registers[REGT_STRING].MostUsed;
|
||||
func->MaxParam = MaxParam;
|
||||
|
||||
// Technically, there's no reason why we can't end the function with
|
||||
// entries on the parameter stack, but it means the caller probably
|
||||
// did something wrong.
|
||||
assert(ActiveParam == 0);
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: FillIntConstants
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::FillIntConstants(int *konst)
|
||||
{
|
||||
TMapIterator<int, int> it(IntConstants);
|
||||
TMap<int, int>::Pair *pair;
|
||||
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
konst[pair->Value] = pair->Key;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: FillFloatConstants
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::FillFloatConstants(double *konst)
|
||||
{
|
||||
TMapIterator<double, int> it(FloatConstants);
|
||||
TMap<double, int>::Pair *pair;
|
||||
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
konst[pair->Value] = pair->Key;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: FillAddressConstants
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::FillAddressConstants(FVoidObj *konst, VM_ATAG *tags)
|
||||
{
|
||||
TMapIterator<void *, AddrKonst> it(AddressConstants);
|
||||
TMap<void *, AddrKonst>::Pair *pair;
|
||||
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
konst[pair->Value.KonstNum].v = pair->Key;
|
||||
tags[pair->Value.KonstNum] = pair->Value.Tag;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: FillStringConstants
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::FillStringConstants(FString *konst)
|
||||
{
|
||||
TMapIterator<FString, int> it(StringConstants);
|
||||
TMap<FString, int>::Pair *pair;
|
||||
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
konst[pair->Value] = pair->Key;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: GetConstantInt
|
||||
//
|
||||
// Returns a constant register initialized with the given value, or -1 if
|
||||
// there were no more constants free.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int VMFunctionBuilder::GetConstantInt(int val)
|
||||
{
|
||||
int *locp = IntConstants.CheckKey(val);
|
||||
if (locp != NULL)
|
||||
{
|
||||
return *locp;
|
||||
}
|
||||
else
|
||||
{
|
||||
int loc = NumIntConstants++;
|
||||
IntConstants.Insert(val, loc);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: GetConstantFloat
|
||||
//
|
||||
// Returns a constant register initialized with the given value, or -1 if
|
||||
// there were no more constants free.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int VMFunctionBuilder::GetConstantFloat(double val)
|
||||
{
|
||||
int *locp = FloatConstants.CheckKey(val);
|
||||
if (locp != NULL)
|
||||
{
|
||||
return *locp;
|
||||
}
|
||||
else
|
||||
{
|
||||
int loc = NumFloatConstants++;
|
||||
FloatConstants.Insert(val, loc);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: GetConstantString
|
||||
//
|
||||
// Returns a constant register initialized with the given value, or -1 if
|
||||
// there were no more constants free.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int VMFunctionBuilder::GetConstantString(FString val)
|
||||
{
|
||||
int *locp = StringConstants.CheckKey(val);
|
||||
if (locp != NULL)
|
||||
{
|
||||
return *locp;
|
||||
}
|
||||
else
|
||||
{
|
||||
int loc = NumStringConstants++;
|
||||
StringConstants.Insert(val, loc);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: GetConstantAddress
|
||||
//
|
||||
// Returns a constant register initialized with the given value, or -1 if
|
||||
// there were no more constants free.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int VMFunctionBuilder::GetConstantAddress(void *ptr, VM_ATAG tag)
|
||||
{
|
||||
if (ptr == NULL)
|
||||
{ // Make all NULL pointers generic. (Or should we allow typed NULLs?)
|
||||
tag = ATAG_GENERIC;
|
||||
}
|
||||
AddrKonst *locp = AddressConstants.CheckKey(ptr);
|
||||
if (locp != NULL)
|
||||
{
|
||||
// There should only be one tag associated with a memory location.
|
||||
assert(locp->Tag == tag);
|
||||
return locp->KonstNum;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddrKonst loc = { NumAddressConstants++, tag };
|
||||
AddressConstants.Insert(ptr, loc);
|
||||
return loc.KonstNum;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: ParamChange
|
||||
//
|
||||
// Adds delta to ActiveParam and keeps track of MaxParam.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::ParamChange(int delta)
|
||||
{
|
||||
assert(delta > 0 || -delta <= ActiveParam);
|
||||
ActiveParam += delta;
|
||||
if (ActiveParam > MaxParam)
|
||||
{
|
||||
MaxParam = ActiveParam;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: RegAvailability - Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
VMFunctionBuilder::RegAvailability::RegAvailability()
|
||||
{
|
||||
memset(Used, 0, sizeof(Used));
|
||||
MostUsed = 0;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: RegAvailability :: Get
|
||||
//
|
||||
// Gets one or more unused registers. If getting multiple registers, they
|
||||
// will all be consecutive. Returns -1 if there were not enough consecutive
|
||||
// registers to satisfy the request.
|
||||
//
|
||||
// Preference is given to low-numbered registers in an attempt to keep
|
||||
// the maximum register count low so as to preserve VM stack space when this
|
||||
// function is executed.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int VMFunctionBuilder::RegAvailability::Get(int count)
|
||||
{
|
||||
VM_UWORD mask;
|
||||
int i, firstbit;
|
||||
|
||||
// Getting fewer than one register makes no sense, and
|
||||
// the algorithm used here can only obtain ranges of up to 32 bits.
|
||||
if (count < 1 || count > 32)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
mask = count == 32 ? ~0u : (1 << count) - 1;
|
||||
|
||||
for (i = 0; i < 256/32; ++i)
|
||||
{
|
||||
// Find the first word with free registers
|
||||
VM_UWORD bits = Used[i];
|
||||
if (bits != ~0u)
|
||||
{
|
||||
// Are there enough consecutive bits to satisfy the request?
|
||||
// Search by 16, then 8, then 1 bit at a time for the first
|
||||
// free register.
|
||||
if ((bits & 0xFFFF) == 0xFFFF)
|
||||
{
|
||||
firstbit = ((bits & 0xFF0000) == 0xFF0000) ? 24 : 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstbit = ((bits & 0xFF) == 0xFF) ? 8 : 0;
|
||||
}
|
||||
for (; firstbit < 32; ++firstbit)
|
||||
{
|
||||
if (((bits >> firstbit) & mask) == 0)
|
||||
{
|
||||
if (firstbit + count <= 32)
|
||||
{ // Needed bits all fit in one word, so we got it.
|
||||
if (firstbit + count > MostUsed)
|
||||
{
|
||||
MostUsed = firstbit + count;
|
||||
}
|
||||
Used[i] |= mask << firstbit;
|
||||
return i * 32 + firstbit;
|
||||
}
|
||||
// Needed bits span two words, so check the next word.
|
||||
else if (i < 256/32 - 1)
|
||||
{ // There is a next word.
|
||||
if (((Used[i + 1]) & (mask >> (32 - firstbit))) == 0)
|
||||
{ // The next word has the needed open space, too.
|
||||
if (firstbit + count > MostUsed)
|
||||
{
|
||||
MostUsed = firstbit + count;
|
||||
}
|
||||
Used[i] |= mask << firstbit;
|
||||
Used[i + 1] |= mask >> (32 - firstbit);
|
||||
return i * 32 + firstbit;
|
||||
}
|
||||
else
|
||||
{ // Skip to the next word, because we know we won't find
|
||||
// what we need if we stay inside this one. All bits
|
||||
// from firstbit to the end of the word are 0. If the
|
||||
// next word does not start with the x amount of 0's, we
|
||||
// need to satisfy the request, then it certainly won't
|
||||
// have the x+1 0's we would need if we started at
|
||||
// firstbit+1 in this one.
|
||||
firstbit = 32;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // Out of words.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// No room!
|
||||
return -1;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: RegAvailibity :: Return
|
||||
//
|
||||
// Marks a range of registers as free again.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::RegAvailability::Return(int reg, int count)
|
||||
{
|
||||
assert(count >= 1 && count <= 32);
|
||||
assert(reg >= 0 && reg + count <= 256);
|
||||
|
||||
VM_UWORD mask, partialmask;
|
||||
int firstword, firstbit;
|
||||
|
||||
mask = count == 32 ? ~0u : (1 << count) - 1;
|
||||
firstword = reg / 32;
|
||||
firstbit = reg & 31;
|
||||
|
||||
if (firstbit + count <= 32)
|
||||
{ // Range is all in one word.
|
||||
mask <<= firstbit;
|
||||
// If we are trying to return registers that are already free,
|
||||
// it probably means that the caller messed up somewhere.
|
||||
assert((Used[firstword] & mask) == mask);
|
||||
Used[firstword] &= ~mask;
|
||||
}
|
||||
else
|
||||
{ // Range is in two words.
|
||||
partialmask = mask << firstbit;
|
||||
assert((Used[firstword] & partialmask) == partialmask);
|
||||
Used[firstword] &= ~partialmask;
|
||||
|
||||
partialmask = mask >> (32 - firstbit);
|
||||
assert((Used[firstword + 1] & partialmask) == partialmask);
|
||||
Used[firstword + 1] &= ~partialmask;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: RegAvailability :: Reuse
|
||||
//
|
||||
// Marks an unused register as in-use. Returns false if the register is
|
||||
// already in use or true if it was successfully reused.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
bool VMFunctionBuilder::RegAvailability::Reuse(int reg)
|
||||
{
|
||||
assert(reg >= 0 && reg <= 255);
|
||||
assert(reg < MostUsed && "Attempt to reuse a register that was never used");
|
||||
|
||||
VM_UWORD mask = 1 << (reg & 31);
|
||||
int word = reg / 32;
|
||||
|
||||
if (Used[word] & mask)
|
||||
{ // It's already in use!
|
||||
return false;
|
||||
}
|
||||
Used[word] |= mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: GetAddress
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
size_t VMFunctionBuilder::GetAddress()
|
||||
{
|
||||
return Code.Size();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: Emit
|
||||
//
|
||||
// Just dumbly output an instruction. Returns instruction position, not
|
||||
// byte position. (Because all instructions are exactly four bytes long.)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
size_t VMFunctionBuilder::Emit(int opcode, int opa, int opb, int opc)
|
||||
{
|
||||
assert(opcode >= 0 && opcode < NUM_OPS);
|
||||
assert(opa >= 0 && opa <= 255);
|
||||
assert(opb >= 0 && opb <= 255);
|
||||
assert(opc >= 0 && opc <= 255);
|
||||
if (opcode == OP_PARAM)
|
||||
{
|
||||
ParamChange(1);
|
||||
}
|
||||
else if (opcode == OP_CALL || opcode == OP_CALL_K || opcode == OP_TAIL || opcode == OP_TAIL_K)
|
||||
{
|
||||
ParamChange(-opb);
|
||||
}
|
||||
VMOP op;
|
||||
op.op = opcode;
|
||||
op.a = opa;
|
||||
op.b = opb;
|
||||
op.c = opc;
|
||||
return Code.Push(op);
|
||||
}
|
||||
|
||||
size_t VMFunctionBuilder::Emit(int opcode, int opa, VM_SHALF opbc)
|
||||
{
|
||||
assert(opcode >= 0 && opcode < NUM_OPS);
|
||||
assert(opa >= 0 && opa <= 255);
|
||||
//assert(opbc >= -32768 && opbc <= 32767); always true due to parameter's width
|
||||
VMOP op;
|
||||
op.op = opcode;
|
||||
op.a = opa;
|
||||
op.i16 = opbc;
|
||||
return Code.Push(op);
|
||||
}
|
||||
|
||||
size_t VMFunctionBuilder::Emit(int opcode, int opabc)
|
||||
{
|
||||
assert(opcode >= 0 && opcode < NUM_OPS);
|
||||
assert(opabc >= -(1 << 23) && opabc <= (1 << 24) - 1);
|
||||
if (opcode == OP_PARAMI)
|
||||
{
|
||||
ParamChange(1);
|
||||
}
|
||||
VMOP op;
|
||||
op.op = opcode;
|
||||
op.i24 = opabc;
|
||||
return Code.Push(op);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: EmitParamInt
|
||||
//
|
||||
// Passes a constant integer parameter, using either PARAMI and an immediate
|
||||
// value or PARAM and a constant register, as appropriate.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
size_t VMFunctionBuilder::EmitParamInt(int value)
|
||||
{
|
||||
// Immediates for PARAMI must fit in 24 bits.
|
||||
if (((value << 8) >> 8) == value)
|
||||
{
|
||||
return Emit(OP_PARAMI, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Emit(OP_PARAM, 0, REGT_INT | REGT_KONST, GetConstantInt(value));
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: EmitLoadInt
|
||||
//
|
||||
// Loads an integer constant into a register, using either an immediate
|
||||
// value or a constant register, as appropriate.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
size_t VMFunctionBuilder::EmitLoadInt(int regnum, int value)
|
||||
{
|
||||
assert(regnum >= 0 && regnum < Registers[REGT_INT].MostUsed);
|
||||
if (value >= -32768 && value <= 32767)
|
||||
{
|
||||
return Emit(OP_LI, regnum, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Emit(OP_LK, regnum, GetConstantInt(value));
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: EmitRetInt
|
||||
//
|
||||
// Returns an integer, using either an immediate value or a constant
|
||||
// register, as appropriate.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
size_t VMFunctionBuilder::EmitRetInt(int retnum, bool final, int value)
|
||||
{
|
||||
assert(retnum >= 0 && retnum <= 127);
|
||||
if (value >= -32768 && value <= 32767)
|
||||
{
|
||||
return Emit(OP_RETI, retnum | (final << 7), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Emit(OP_RET, retnum | (final << 7), REGT_INT | REGT_KONST, GetConstantInt(value));
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: Backpatch
|
||||
//
|
||||
// Store a JMP instruction at <loc> that points at <target>.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::Backpatch(size_t loc, size_t target)
|
||||
{
|
||||
assert(loc < Code.Size());
|
||||
int offset = int(target - loc - 1);
|
||||
assert(((offset << 8) >> 8) == offset);
|
||||
Code[loc].op = OP_JMP;
|
||||
Code[loc].i24 = offset;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// VMFunctionBuilder :: BackpatchToHere
|
||||
//
|
||||
// Store a JMP instruction at <loc> that points to the current code gen
|
||||
// location.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void VMFunctionBuilder::BackpatchToHere(size_t loc)
|
||||
{
|
||||
Backpatch(loc, Code.Size());
|
||||
}
|
||||
90
src/scripting/vm/vmbuilder.h
Normal file
90
src/scripting/vm/vmbuilder.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#ifndef VMUTIL_H
|
||||
#define VMUTIL_H
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
class VMFunctionBuilder
|
||||
{
|
||||
public:
|
||||
// Keeps track of which registers are available by way of a bitmask table.
|
||||
class RegAvailability
|
||||
{
|
||||
public:
|
||||
RegAvailability();
|
||||
int GetMostUsed() { return MostUsed; }
|
||||
int Get(int count); // Returns the first register in the range
|
||||
void Return(int reg, int count);
|
||||
bool Reuse(int regnum);
|
||||
|
||||
private:
|
||||
VM_UWORD Used[256/32]; // Bitmap of used registers (bit set means reg is used)
|
||||
int MostUsed;
|
||||
|
||||
friend class VMFunctionBuilder;
|
||||
};
|
||||
|
||||
VMFunctionBuilder(bool checkself = false);
|
||||
~VMFunctionBuilder();
|
||||
|
||||
VMScriptFunction *MakeFunction();
|
||||
|
||||
// Returns the constant register holding the value.
|
||||
int GetConstantInt(int val);
|
||||
int GetConstantFloat(double val);
|
||||
int GetConstantAddress(void *ptr, VM_ATAG tag);
|
||||
int GetConstantString(FString str);
|
||||
|
||||
// Returns the address of the next instruction to be emitted.
|
||||
size_t GetAddress();
|
||||
|
||||
// Returns the address of the newly-emitted instruction.
|
||||
size_t Emit(int opcode, int opa, int opb, int opc);
|
||||
size_t Emit(int opcode, int opa, VM_SHALF opbc);
|
||||
size_t Emit(int opcode, int opabc);
|
||||
size_t EmitParamInt(int value);
|
||||
size_t EmitLoadInt(int regnum, int value);
|
||||
size_t EmitRetInt(int retnum, bool final, int value);
|
||||
|
||||
void Backpatch(size_t addr, size_t target);
|
||||
void BackpatchToHere(size_t addr);
|
||||
|
||||
// Write out complete constant tables.
|
||||
void FillIntConstants(int *konst);
|
||||
void FillFloatConstants(double *konst);
|
||||
void FillAddressConstants(FVoidObj *konst, VM_ATAG *tags);
|
||||
void FillStringConstants(FString *strings);
|
||||
|
||||
// PARAM increases ActiveParam; CALL decreases it.
|
||||
void ParamChange(int delta);
|
||||
|
||||
// Track available registers.
|
||||
RegAvailability Registers[4];
|
||||
|
||||
// For use by DECORATE's self/stateowner sanitizer.
|
||||
bool IsActionFunc;
|
||||
|
||||
private:
|
||||
struct AddrKonst
|
||||
{
|
||||
int KonstNum;
|
||||
VM_ATAG Tag;
|
||||
};
|
||||
// These map from the constant value to its position in the constant table.
|
||||
TMap<int, int> IntConstants;
|
||||
TMap<double, int> FloatConstants;
|
||||
TMap<void *, AddrKonst> AddressConstants;
|
||||
TMap<FString, int> StringConstants;
|
||||
|
||||
int NumIntConstants;
|
||||
int NumFloatConstants;
|
||||
int NumAddressConstants;
|
||||
int NumStringConstants;
|
||||
|
||||
int MaxParam;
|
||||
int ActiveParam;
|
||||
|
||||
TArray<VMOP> Code;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
600
src/scripting/vm/vmdisasm.cpp
Normal file
600
src/scripting/vm/vmdisasm.cpp
Normal file
|
|
@ -0,0 +1,600 @@
|
|||
/*
|
||||
** vmdisasm.cpp
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright -2016 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include "vm.h"
|
||||
#include "c_console.h"
|
||||
|
||||
#define NOP MODE_AUNUSED | MODE_BUNUSED | MODE_CUNUSED
|
||||
|
||||
#define LI MODE_AI | MODE_BCJOINT | MODE_BCIMMS
|
||||
#define LKI MODE_AI | MODE_BCJOINT | MODE_BCKI
|
||||
#define LKF MODE_AF | MODE_BCJOINT | MODE_BCKF
|
||||
#define LKS MODE_AS | MODE_BCJOINT | MODE_BCKS
|
||||
#define LKP MODE_AP | MODE_BCJOINT | MODE_BCKP
|
||||
#define LFP MODE_AP | MODE_BUNUSED | MODE_CUNUSED
|
||||
|
||||
#define RIRPKI MODE_AI | MODE_BP | MODE_CKI
|
||||
#define RIRPRI MODE_AI | MODE_BP | MODE_CI
|
||||
#define RFRPKI MODE_AF | MODE_BP | MODE_CKI
|
||||
#define RFRPRI MODE_AF | MODE_BP | MODE_CI
|
||||
#define RSRPKI MODE_AS | MODE_BP | MODE_CKI
|
||||
#define RSRPRI MODE_AS | MODE_BP | MODE_CI
|
||||
#define RPRPKI MODE_AP | MODE_BP | MODE_CKI
|
||||
#define RPRPRI MODE_AP | MODE_BP | MODE_CI
|
||||
#define RVRPKI MODE_AV | MODE_BP | MODE_CKI
|
||||
#define RVRPRI MODE_AV | MODE_BP | MODE_CI
|
||||
#define RIRPI8 MODE_AI | MODE_BP | MODE_CIMMZ
|
||||
|
||||
#define RPRIKI MODE_AP | MODE_BI | MODE_CKI
|
||||
#define RPRIRI MODE_AP | MODE_BI | MODE_CI
|
||||
#define RPRFKI MODE_AP | MODE_BF | MODE_CKI
|
||||
#define RPRFRI MODE_AP | MODE_BF | MODE_CI
|
||||
#define RPRSKI MODE_AP | MODE_BS | MODE_CKI
|
||||
#define RPRSRI MODE_AP | MODE_BS | MODE_CI
|
||||
#define RPRPKI MODE_AP | MODE_BP | MODE_CKI
|
||||
#define RPRPRI MODE_AP | MODE_BP | MODE_CI
|
||||
#define RPRVKI MODE_AP | MODE_BV | MODE_CKI
|
||||
#define RPRVRI MODE_AP | MODE_BV | MODE_CI
|
||||
#define RPRII8 MODE_AP | MODE_BI | MODE_CIMMZ
|
||||
|
||||
#define RIRI MODE_AI | MODE_BI | MODE_CUNUSED
|
||||
#define RFRF MODE_AF | MODE_BF | MODE_CUNUSED
|
||||
#define RSRS MODE_AS | MODE_BS | MODE_CUNUSED
|
||||
#define RPRP MODE_AP | MODE_BP | MODE_CUNUSED
|
||||
#define RXRXI8 MODE_AX | MODE_BX | MODE_CIMMZ
|
||||
#define RPRPRP MODE_AP | MODE_BP | MODE_CP
|
||||
#define RPRPKP MODE_AP | MODE_BP | MODE_CKP
|
||||
|
||||
#define RII16 MODE_AI | MODE_BCJOINT | MODE_BCIMMS
|
||||
#define I24 MODE_ABCJOINT
|
||||
#define I8 MODE_AIMMZ | MODE_BUNUSED | MODE_CUNUSED
|
||||
#define I8I16 MODE_AIMMZ | MODE_BCIMMZ
|
||||
#define __BCP MODE_AUNUSED | MODE_BCJOINT | MODE_BCPARAM
|
||||
#define RPI8 MODE_AP | MODE_BIMMZ | MODE_CUNUSED
|
||||
#define KPI8 MODE_AKP | MODE_BIMMZ | MODE_CUNUSED
|
||||
#define RPI8I8 MODE_AP | MODE_BIMMZ | MODE_CIMMZ
|
||||
#define KPI8I8 MODE_AKP | MODE_BIMMZ | MODE_CIMMZ
|
||||
#define I8BCP MODE_AIMMZ | MODE_BCJOINT | MODE_BCPARAM
|
||||
#define THROW MODE_AIMMZ | MODE_BCTHROW
|
||||
#define CATCH MODE_AIMMZ | MODE_BCCATCH
|
||||
#define CAST MODE_AX | MODE_BX | MODE_CIMMZ | MODE_BCCAST
|
||||
|
||||
#define RSRSRS MODE_AS | MODE_BS | MODE_CS
|
||||
#define RIRS MODE_AI | MODE_BS | MODE_CUNUSED
|
||||
#define I8RXRX MODE_AIMMZ | MODE_BX | MODE_CX
|
||||
|
||||
#define RIRIRI MODE_AI | MODE_BI | MODE_CI
|
||||
#define RIRII8 MODE_AI | MODE_BI | MODE_CIMMZ
|
||||
#define RIRIKI MODE_AI | MODE_BI | MODE_CKI
|
||||
#define RIKIRI MODE_AI | MODE_BKI | MODE_CI
|
||||
#define RIKII8 MODE_AI | MODE_BKI | MODE_CIMMZ
|
||||
#define RIRIIs MODE_AI | MODE_BI | MODE_CIMMS
|
||||
#define RIRI MODE_AI | MODE_BI | MODE_CUNUSED
|
||||
#define I8RIRI MODE_AIMMZ | MODE_BI | MODE_CI
|
||||
#define I8RIKI MODE_AIMMZ | MODE_BI | MODE_CKI
|
||||
#define I8KIRI MODE_AIMMZ | MODE_BKI | MODE_CI
|
||||
|
||||
#define RFRFRF MODE_AF | MODE_BF | MODE_CF
|
||||
#define RFRFKF MODE_AF | MODE_BF | MODE_CKF
|
||||
#define RFKFRF MODE_AF | MODE_BKF | MODE_CF
|
||||
#define I8RFRF MODE_AIMMZ | MODE_BF | MODE_CF
|
||||
#define I8RFKF MODE_AIMMZ | MODE_BF | MODE_CKF
|
||||
#define I8KFRF MODE_AIMMZ | MODE_BKF | MODE_CF
|
||||
#define RFRFI8 MODE_AF | MODE_BF | MODE_CIMMZ
|
||||
|
||||
#define RVRV MODE_AV | MODE_BV | MODE_CUNUSED
|
||||
#define RVRVRV MODE_AV | MODE_BV | MODE_CV
|
||||
#define RVRVKV MODE_AV | MODE_BV | MODE_CKV
|
||||
#define RVKVRV MODE_AV | MODE_BKV | MODE_CV
|
||||
#define RFRV MODE_AF | MODE_BV | MODE_CUNUSED
|
||||
#define I8RVRV MODE_AIMMZ | MODE_BV | MODE_CV
|
||||
#define I8RVKV MODE_AIMMZ | MODE_BV | MODE_CKV
|
||||
|
||||
#define RPRPRI MODE_AP | MODE_BP | MODE_CI
|
||||
#define RPRPKI MODE_AP | MODE_BP | MODE_CKI
|
||||
#define RIRPRP MODE_AI | MODE_BP | MODE_CP
|
||||
#define I8RPRP MODE_AIMMZ | MODE_BP | MODE_CP
|
||||
#define I8RPKP MODE_AIMMZ | MODE_BP | MODE_CKP
|
||||
|
||||
#define CIRR MODE_ACMP | MODE_BI | MODE_CI
|
||||
#define CIRK MODE_ACMP | MODE_BI | MODE_CKI
|
||||
#define CIKR MODE_ACMP | MODE_BKI | MODE_CI
|
||||
#define CFRR MODE_ACMP | MODE_BF | MODE_CF
|
||||
#define CFRK MODE_ACMP | MODE_BF | MODE_CKF
|
||||
#define CFKR MODE_ACMP | MODE_BKF | MODE_CF
|
||||
#define CVRR MODE_ACMP | MODE_BV | MODE_CV
|
||||
#define CVRK MODE_ACMP | MODE_BV | MODE_CKV
|
||||
#define CPRR MODE_ACMP | MODE_BP | MODE_CP
|
||||
#define CPRK MODE_ACMP | MODE_BP | MODE_CKP
|
||||
|
||||
const VMOpInfo OpInfo[NUM_OPS] =
|
||||
{
|
||||
#define xx(op, name, mode) { #name, mode }
|
||||
#include "vmops.h"
|
||||
};
|
||||
|
||||
static const char *const FlopNames[] =
|
||||
{
|
||||
"abs",
|
||||
"neg",
|
||||
"exp",
|
||||
"log",
|
||||
"log10",
|
||||
"sqrt",
|
||||
"ceil",
|
||||
"floor",
|
||||
|
||||
"acos rad",
|
||||
"asin rad",
|
||||
"atan rad",
|
||||
"cos rad",
|
||||
"sin rad",
|
||||
"tan rad",
|
||||
|
||||
"acos deg",
|
||||
"asin deg",
|
||||
"atan deg",
|
||||
"cos deg",
|
||||
"sin deg",
|
||||
"tan deg",
|
||||
|
||||
"cosh",
|
||||
"sinh",
|
||||
"tanh",
|
||||
};
|
||||
|
||||
static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func);
|
||||
|
||||
static int printf_wrapper(FILE *f, const char *fmt, ...)
|
||||
{
|
||||
va_list argptr;
|
||||
int count;
|
||||
|
||||
va_start(argptr, fmt);
|
||||
if (f == NULL)
|
||||
{
|
||||
count = VPrintf(PRINT_HIGH, fmt, argptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
count = vfprintf(f, fmt, argptr);
|
||||
}
|
||||
va_end(argptr);
|
||||
return count;
|
||||
}
|
||||
|
||||
void VMDumpConstants(FILE *out, const VMScriptFunction *func)
|
||||
{
|
||||
char tmp[21];
|
||||
int i, j, k, kk;
|
||||
|
||||
if (func->KonstD != NULL && func->NumKonstD != 0)
|
||||
{
|
||||
printf_wrapper(out, "\nConstant integers:\n");
|
||||
kk = (func->NumKonstD + 3) / 4;
|
||||
for (i = 0; i < kk; ++i)
|
||||
{
|
||||
for (j = 0, k = i; j < 4 && k < func->NumKonstD; j++, k += kk)
|
||||
{
|
||||
mysnprintf(tmp, countof(tmp), "%3d. %d", k, func->KonstD[k]);
|
||||
printf_wrapper(out, "%-20s", tmp);
|
||||
}
|
||||
printf_wrapper(out, "\n");
|
||||
}
|
||||
}
|
||||
if (func->KonstF != NULL && func->NumKonstF != 0)
|
||||
{
|
||||
printf_wrapper(out, "\nConstant floats:\n");
|
||||
kk = (func->NumKonstF + 3) / 4;
|
||||
for (i = 0; i < kk; ++i)
|
||||
{
|
||||
for (j = 0, k = i; j < 4 && k < func->NumKonstF; j++, k += kk)
|
||||
{
|
||||
mysnprintf(tmp, countof(tmp), "%3d. %.16f", k, func->KonstF[k]);
|
||||
printf_wrapper(out, "%-20s", tmp);
|
||||
}
|
||||
printf_wrapper(out, "\n");
|
||||
}
|
||||
}
|
||||
if (func->KonstA != NULL && func->NumKonstA != 0)
|
||||
{
|
||||
printf_wrapper(out, "\nConstant addresses:\n");
|
||||
kk = (func->NumKonstA + 3) / 4;
|
||||
for (i = 0; i < kk; ++i)
|
||||
{
|
||||
for (j = 0, k = i; j < 4 && k < func->NumKonstA; j++, k += kk)
|
||||
{
|
||||
mysnprintf(tmp, countof(tmp), "%3d. %p:%d", k, func->KonstA[k].v, func->KonstATags()[k]);
|
||||
printf_wrapper(out, "%-20s", tmp);
|
||||
}
|
||||
printf_wrapper(out, "\n");
|
||||
}
|
||||
}
|
||||
if (func->KonstS != NULL && func->NumKonstS != 0)
|
||||
{
|
||||
printf_wrapper(out, "\nConstant strings:\n");
|
||||
for (i = 0; i < func->NumKonstS; ++i)
|
||||
{
|
||||
printf_wrapper(out, "%3d. %s\n", i, func->KonstS[i].GetChars());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func)
|
||||
{
|
||||
VMFunction *callfunc;
|
||||
const char *callname;
|
||||
const char *name;
|
||||
int col;
|
||||
int mode;
|
||||
int a;
|
||||
bool cmp;
|
||||
char cmpname[8];
|
||||
|
||||
for (int i = 0; i < codesize; ++i)
|
||||
{
|
||||
name = OpInfo[code[i].op].Name;
|
||||
mode = OpInfo[code[i].op].Mode;
|
||||
a = code[i].a;
|
||||
cmp = (mode & MODE_ATYPE) == MODE_ACMP;
|
||||
|
||||
// String comparison encodes everything in a single instruction.
|
||||
if (code[i].op == OP_CMPS)
|
||||
{
|
||||
switch (a & CMP_METHOD_MASK)
|
||||
{
|
||||
case CMP_EQ: name = "beq"; break;
|
||||
case CMP_LT: name = "blt"; break;
|
||||
case CMP_LE: name = "ble"; break;
|
||||
}
|
||||
mode = MODE_AIMMZ;
|
||||
mode |= (a & CMP_BK) ? MODE_BKS : MODE_BS;
|
||||
mode |= (a & CMP_CK) ? MODE_CKS : MODE_CS;
|
||||
a &= CMP_CHECK | CMP_APPROX;
|
||||
cmp = true;
|
||||
}
|
||||
if (cmp)
|
||||
{ // Comparison instruction. Modify name for inverted test.
|
||||
if (!(a & CMP_CHECK))
|
||||
{
|
||||
strcpy(cmpname, name);
|
||||
if (name[1] == 'e')
|
||||
{ // eq -> ne
|
||||
cmpname[1] = 'n', cmpname[2] = 'e';
|
||||
}
|
||||
else if (name[2] == 't')
|
||||
{ // lt -> ge
|
||||
cmpname[1] = 'g', cmpname[2] = 'e';
|
||||
}
|
||||
else
|
||||
{ // le -> gt
|
||||
cmpname[1] = 'g', cmpname[2] = 't';
|
||||
}
|
||||
name = cmpname;
|
||||
}
|
||||
}
|
||||
printf_wrapper(out, "%08x: %02x%02x%02x%02x %-8s", i << 2, code[i].op, code[i].a, code[i].b, code[i].c, name);
|
||||
col = 0;
|
||||
switch (code[i].op)
|
||||
{
|
||||
case OP_JMP:
|
||||
case OP_TRY:
|
||||
col = printf_wrapper(out, "%08x", (i + 1 + code[i].i24) << 2);
|
||||
break;
|
||||
|
||||
case OP_PARAMI:
|
||||
col = printf_wrapper(out, "%d", code[i].i24);
|
||||
break;
|
||||
|
||||
case OP_CALL_K:
|
||||
case OP_TAIL_K:
|
||||
callfunc = (VMFunction *)func->KonstA[code[i].a].o;
|
||||
callname = callfunc->Name != NAME_None ? callfunc->Name : "[anonfunc]";
|
||||
col = printf_wrapper(out, "%.23s,%d", callname, code[i].b);
|
||||
if (code[i].op == OP_CALL_K)
|
||||
{
|
||||
col += printf_wrapper(out, ",%d", code[i].c);
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_RET:
|
||||
if (code[i].b != REGT_NIL)
|
||||
{
|
||||
if (a == RET_FINAL)
|
||||
{
|
||||
col = print_reg(out, 0, code[i].i16u, MODE_PARAM, 16, func);
|
||||
}
|
||||
else
|
||||
{
|
||||
col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
|
||||
col += print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func);
|
||||
if (a & RET_FINAL)
|
||||
{
|
||||
col += printf_wrapper(out, " [final]");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_RETI:
|
||||
if (a == RET_FINAL)
|
||||
{
|
||||
col = printf_wrapper(out, "%d", code[i].i16);
|
||||
}
|
||||
else
|
||||
{
|
||||
col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
|
||||
col += print_reg(out, col, code[i].i16, MODE_IMMS, 16, func);
|
||||
if (a & RET_FINAL)
|
||||
{
|
||||
col += printf_wrapper(out, " [final]");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OP_FLOP:
|
||||
col = printf_wrapper(out, "f%d,f%d,%d", code[i].a, code[i].b, code[i].c);
|
||||
if (code[i].c < countof(FlopNames))
|
||||
{
|
||||
col += printf_wrapper(out, " [%s]", FlopNames[code[i].c]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if ((mode & MODE_BCTYPE) == MODE_BCCAST)
|
||||
{
|
||||
switch (code[i].c)
|
||||
{
|
||||
case CAST_I2F:
|
||||
mode = MODE_AF | MODE_BI | MODE_CUNUSED;
|
||||
break;
|
||||
case CAST_I2S:
|
||||
mode = MODE_AS | MODE_BI | MODE_CUNUSED;
|
||||
break;
|
||||
case CAST_F2I:
|
||||
mode = MODE_AI | MODE_BF | MODE_CUNUSED;
|
||||
break;
|
||||
case CAST_F2S:
|
||||
mode = MODE_AS | MODE_BF | MODE_CUNUSED;
|
||||
break;
|
||||
case CAST_P2S:
|
||||
mode = MODE_AS | MODE_BP | MODE_CUNUSED;
|
||||
break;
|
||||
case CAST_S2I:
|
||||
mode = MODE_AI | MODE_BS | MODE_CUNUSED;
|
||||
break;
|
||||
case CAST_S2F:
|
||||
mode = MODE_AF | MODE_BS | MODE_CUNUSED;
|
||||
break;
|
||||
default:
|
||||
mode = MODE_AX | MODE_BX | MODE_CIMMZ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
col = print_reg(out, 0, a, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
|
||||
if ((mode & MODE_BCTYPE) == MODE_BCTHROW)
|
||||
{
|
||||
if (code[i].a == 0)
|
||||
{
|
||||
mode = (MODE_BP | MODE_CUNUSED);
|
||||
}
|
||||
else if (code[i].a == 1)
|
||||
{
|
||||
mode = (MODE_BKP | MODE_CUNUSED);
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = (MODE_BCJOINT | MODE_BCIMMS);
|
||||
}
|
||||
}
|
||||
else if ((mode & MODE_BCTYPE) == MODE_BCCATCH)
|
||||
{
|
||||
switch (code[i].a)
|
||||
{
|
||||
case 0:
|
||||
mode = MODE_BUNUSED | MODE_CUNUSED;
|
||||
break;
|
||||
case 1:
|
||||
mode = MODE_BUNUSED | MODE_CP;
|
||||
break;
|
||||
case 2:
|
||||
mode = MODE_BP | MODE_CP;
|
||||
break;
|
||||
case 3:
|
||||
mode = MODE_BKP | MODE_CP;
|
||||
break;
|
||||
default:
|
||||
mode = MODE_BIMMZ | MODE_CIMMZ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((mode & (MODE_BTYPE | MODE_CTYPE)) == MODE_BCJOINT)
|
||||
{
|
||||
col += print_reg(out, col, code[i].i16u, (mode & MODE_BCTYPE) >> MODE_BCSHIFT, 16, func);
|
||||
}
|
||||
else
|
||||
{
|
||||
col += print_reg(out, col, code[i].b, (mode & MODE_BTYPE) >> MODE_BSHIFT, 24, func);
|
||||
col += print_reg(out, col, code[i].c, (mode & MODE_CTYPE) >> MODE_CSHIFT, 24, func);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (cmp && i + 1 < codesize)
|
||||
{
|
||||
if (code[i+1].op != OP_JMP)
|
||||
{ // comparison instructions must be followed by jump
|
||||
col += printf_wrapper(out, " => *!*!*!*\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
col += printf_wrapper(out, " => %08x", (i + 2 + code[i+1].i24) << 2);
|
||||
}
|
||||
}
|
||||
if (col > 30)
|
||||
{
|
||||
col = 30;
|
||||
}
|
||||
printf_wrapper(out, "%*c", 30 - col, ';');
|
||||
if (!cmp && (code[i].op == OP_JMP || code[i].op == OP_TRY || code[i].op == OP_PARAMI))
|
||||
{
|
||||
printf_wrapper(out, "%d\n", code[i].i24);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf_wrapper(out, "%d,%d,%d", code[i].a, code[i].b, code[i].c);
|
||||
if (cmp && i + 1 < codesize && code[i+1].op == OP_JMP)
|
||||
{
|
||||
printf_wrapper(out, ",%d\n", code[++i].i24);
|
||||
}
|
||||
else if (code[i].op == OP_CALL_K || code[i].op == OP_TAIL_K)
|
||||
{
|
||||
printf_wrapper(out, " [%p]\n", callfunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf_wrapper(out, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func)
|
||||
{
|
||||
if (mode == MODE_UNUSED || mode == MODE_CMP)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (col > 0)
|
||||
{
|
||||
col = printf_wrapper(out, ",");
|
||||
}
|
||||
switch(mode)
|
||||
{
|
||||
case MODE_I:
|
||||
return col+printf_wrapper(out, "d%d", arg);
|
||||
case MODE_F:
|
||||
return col+printf_wrapper(out, "f%d", arg);
|
||||
case MODE_S:
|
||||
return col+printf_wrapper(out, "s%d", arg);
|
||||
case MODE_P:
|
||||
return col+printf_wrapper(out, "a%d", arg);
|
||||
case MODE_V:
|
||||
return col+printf_wrapper(out, "v%d", arg);
|
||||
|
||||
case MODE_KI:
|
||||
if (func != NULL)
|
||||
{
|
||||
return col+printf_wrapper(out, "%d", func->KonstD[arg]);
|
||||
}
|
||||
return printf_wrapper(out, "kd%d", arg);
|
||||
case MODE_KF:
|
||||
if (func != NULL)
|
||||
{
|
||||
return col+printf_wrapper(out, "%#g", func->KonstF[arg]);
|
||||
}
|
||||
return col+printf_wrapper(out, "kf%d", arg);
|
||||
case MODE_KS:
|
||||
if (func != NULL)
|
||||
{
|
||||
return col+printf_wrapper(out, "\"%.27s\"", func->KonstS[arg].GetChars());
|
||||
}
|
||||
return col+printf_wrapper(out, "ks%d", arg);
|
||||
case MODE_KP:
|
||||
if (func != NULL)
|
||||
{
|
||||
return col+printf_wrapper(out, "%p", func->KonstA[arg]);
|
||||
}
|
||||
return col+printf_wrapper(out, "ka%d", arg);
|
||||
case MODE_KV:
|
||||
if (func != NULL)
|
||||
{
|
||||
return col+printf_wrapper(out, "(%f,%f,%f)", func->KonstF[arg], func->KonstF[arg+1], func->KonstF[arg+2]);
|
||||
}
|
||||
return col+printf_wrapper(out, "kv%d", arg);
|
||||
|
||||
case MODE_IMMS:
|
||||
return col+printf_wrapper(out, "%d", (arg << immshift) >> immshift);
|
||||
|
||||
case MODE_IMMZ:
|
||||
return col+printf_wrapper(out, "%d", arg);
|
||||
|
||||
case MODE_PARAM:
|
||||
{
|
||||
int regtype, regnum;
|
||||
#ifdef __BIG_ENDIAN__
|
||||
regtype = (arg >> 8) & 255;
|
||||
regnum = arg & 255;
|
||||
#else
|
||||
regtype = arg & 255;
|
||||
regnum = (arg >> 8) & 255;
|
||||
#endif
|
||||
switch (regtype & (REGT_TYPE | REGT_KONST | REGT_MULTIREG))
|
||||
{
|
||||
case REGT_INT:
|
||||
return col+printf_wrapper(out, "d%d", regnum);
|
||||
case REGT_FLOAT:
|
||||
return col+printf_wrapper(out, "f%d", regnum);
|
||||
case REGT_STRING:
|
||||
return col+printf_wrapper(out, "s%d", regnum);
|
||||
case REGT_POINTER:
|
||||
return col+printf_wrapper(out, "a%d", regnum);
|
||||
case REGT_FLOAT | REGT_MULTIREG:
|
||||
return col+printf_wrapper(out, "v%d", regnum);
|
||||
case REGT_INT | REGT_KONST:
|
||||
return col+print_reg(out, 0, regnum, MODE_KI, 0, func);
|
||||
case REGT_FLOAT | REGT_KONST:
|
||||
return col+print_reg(out, 0, regnum, MODE_KF, 0, func);
|
||||
case REGT_STRING | REGT_KONST:
|
||||
return col+print_reg(out, 0, regnum, MODE_KS, 0, func);
|
||||
case REGT_POINTER | REGT_KONST:
|
||||
return col+print_reg(out, 0, regnum, MODE_KP, 0, func);
|
||||
case REGT_FLOAT | REGT_MULTIREG | REGT_KONST:
|
||||
return col+print_reg(out, 0, regnum, MODE_KV, 0, func);
|
||||
default:
|
||||
if (regtype == REGT_NIL)
|
||||
{
|
||||
return col+printf_wrapper(out, "nil");
|
||||
}
|
||||
return col+printf_wrapper(out, "param[t=%d,%c,%c,n=%d]",
|
||||
regtype & REGT_TYPE,
|
||||
regtype & REGT_KONST ? 'k' : 'r',
|
||||
regtype & REGT_MULTIREG ? 'm' : 's',
|
||||
regnum);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return col+printf_wrapper(out, "$%d", arg);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
218
src/scripting/vm/vmexec.cpp
Normal file
218
src/scripting/vm/vmexec.cpp
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
** 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 "vm.h"
|
||||
#include "xs_Float.h"
|
||||
#include "math/cmath.h"
|
||||
|
||||
#define IMPLEMENT_VMEXEC
|
||||
|
||||
#if !defined(COMPGOTO) && defined(__GNUC__)
|
||||
#define COMPGOTO 1
|
||||
#endif
|
||||
|
||||
#if COMPGOTO
|
||||
#define OP(x) x
|
||||
#define NEXTOP do { unsigned op = pc->op; a = pc->a; pc++; goto *ops[op]; } while(0)
|
||||
#else
|
||||
#define OP(x) case OP_##x
|
||||
#define NEXTOP break
|
||||
#endif
|
||||
|
||||
#define luai_nummod(a,b) ((a) - floor((a)/(b))*(b))
|
||||
|
||||
#define A (pc[-1].a)
|
||||
#define B (pc[-1].b)
|
||||
#define C (pc[-1].c)
|
||||
#define Cs (pc[-1].cs)
|
||||
#define BC (pc[-1].i16u)
|
||||
#define BCs (pc[-1].i16)
|
||||
#define ABCs (pc[-1].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 THROW(x) throw(EVMAbortException(x))
|
||||
|
||||
#define CMPJMP(test) \
|
||||
if ((test) == (a & CMP_CHECK)) { \
|
||||
assert(pc->op == OP_JMP); \
|
||||
pc += 1 + JMPOFS(pc); \
|
||||
} else { \
|
||||
pc += 1; \
|
||||
}
|
||||
|
||||
#define GETADDR(a,o,x) \
|
||||
if (a == NULL) { THROW(x); } \
|
||||
ptr = (VM_SBYTE *)a + o
|
||||
|
||||
static const VM_UWORD ZapTable[16] =
|
||||
{
|
||||
0x00000000, 0x000000FF, 0x0000FF00, 0x0000FFFF,
|
||||
0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00FFFFFF,
|
||||
0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF,
|
||||
0xFFFF0000, 0xFFFF00FF, 0xFFFFFF00, 0xFFFFFFFF
|
||||
};
|
||||
|
||||
#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)(VMFrameStack *stack, const VMOP *pc, VMReturn *ret, int numret) =
|
||||
#ifdef NDEBUG
|
||||
VMExec_Unchecked::Exec
|
||||
#else
|
||||
VMExec_Checked::Exec
|
||||
#endif
|
||||
;
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// 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->Native);
|
||||
assert(numparam == calleefunc->NumArgs);
|
||||
assert(REGT_INT == 0 && REGT_FLOAT == 1 && REGT_STRING == 2 && REGT_POINTER == 3);
|
||||
|
||||
regd = regf = regs = rega = 0;
|
||||
for (int i = 0; i < numparam; ++i)
|
||||
{
|
||||
VMValue &p = params[i];
|
||||
if (p.Type < REGT_STRING)
|
||||
{
|
||||
if (p.Type == REGT_INT)
|
||||
{
|
||||
calleereg.d[regd++] = p.i;
|
||||
}
|
||||
else // p.Type == REGT_FLOAT
|
||||
{
|
||||
calleereg.f[regf++] = p.f;
|
||||
}
|
||||
}
|
||||
else if (p.Type == REGT_STRING)
|
||||
{
|
||||
calleereg.s[regs++] = p.s();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(p.Type == REGT_POINTER);
|
||||
calleereg.a[rega] = p.a;
|
||||
calleereg.atag[rega++] = p.atag;
|
||||
}
|
||||
}
|
||||
}
|
||||
1608
src/scripting/vm/vmexec.h
Normal file
1608
src/scripting/vm/vmexec.h
Normal file
File diff suppressed because it is too large
Load diff
493
src/scripting/vm/vmframe.cpp
Normal file
493
src/scripting/vm/vmframe.cpp
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
/*
|
||||
** vmframe.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 <new>
|
||||
#include "vm.h"
|
||||
|
||||
IMPLEMENT_CLASS(VMException)
|
||||
IMPLEMENT_ABSTRACT_POINTY_CLASS(VMFunction)
|
||||
DECLARE_POINTER(Proto)
|
||||
END_POINTERS
|
||||
IMPLEMENT_CLASS(VMScriptFunction)
|
||||
IMPLEMENT_CLASS(VMNativeFunction)
|
||||
|
||||
VMScriptFunction::VMScriptFunction(FName name)
|
||||
{
|
||||
Native = false;
|
||||
Name = name;
|
||||
Code = NULL;
|
||||
KonstD = NULL;
|
||||
KonstF = NULL;
|
||||
KonstS = NULL;
|
||||
KonstA = NULL;
|
||||
ExtraSpace = 0;
|
||||
CodeSize = 0;
|
||||
NumRegD = 0;
|
||||
NumRegF = 0;
|
||||
NumRegS = 0;
|
||||
NumRegA = 0;
|
||||
NumKonstD = 0;
|
||||
NumKonstF = 0;
|
||||
NumKonstS = 0;
|
||||
NumKonstA = 0;
|
||||
MaxParam = 0;
|
||||
NumArgs = 0;
|
||||
}
|
||||
|
||||
VMScriptFunction::~VMScriptFunction()
|
||||
{
|
||||
if (Code != NULL)
|
||||
{
|
||||
if (KonstS != NULL)
|
||||
{
|
||||
for (int i = 0; i < NumKonstS; ++i)
|
||||
{
|
||||
KonstS[i].~FString();
|
||||
}
|
||||
}
|
||||
M_Free(Code);
|
||||
}
|
||||
}
|
||||
|
||||
void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta)
|
||||
{
|
||||
assert(Code == NULL);
|
||||
assert(numops > 0);
|
||||
assert(numkonstd >= 0 && numkonstd <= 255);
|
||||
assert(numkonstf >= 0 && numkonstf <= 255);
|
||||
assert(numkonsts >= 0 && numkonsts <= 255);
|
||||
assert(numkonsta >= 0 && numkonsta <= 255);
|
||||
void *mem = M_Malloc(numops * sizeof(VMOP) +
|
||||
numkonstd * sizeof(int) +
|
||||
numkonstf * sizeof(double) +
|
||||
numkonsts * sizeof(FString) +
|
||||
numkonsta * (sizeof(FVoidObj) + 1));
|
||||
Code = (VMOP *)mem;
|
||||
mem = (void *)((VMOP *)mem + numops);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t VMScriptFunction::PropagateMark()
|
||||
{
|
||||
if (KonstA != NULL)
|
||||
{
|
||||
FVoidObj *konsta = KonstA;
|
||||
VM_UBYTE *atag = KonstATags();
|
||||
for (int count = NumKonstA; count > 0; --count)
|
||||
{
|
||||
if (*atag++ == ATAG_OBJECT)
|
||||
{
|
||||
GC::Mark(konsta->o);
|
||||
}
|
||||
konsta++;
|
||||
}
|
||||
}
|
||||
return NumKonstA * sizeof(void *) + Super::PropagateMark();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
if (UnusedBlocks != NULL)
|
||||
{
|
||||
BlockHeader *block, *next;
|
||||
for (block = UnusedBlocks; block != NULL; block = next)
|
||||
{
|
||||
next = block->NextBlock;
|
||||
delete[] (VM_UBYTE *)block;
|
||||
}
|
||||
}
|
||||
Blocks = NULL;
|
||||
UnusedBlocks = NULL;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: AllocFrame
|
||||
//
|
||||
// Allocates a frame from the stack with the desired number of registers.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::AllocFrame(int numregd, int numregf, int numregs, int numrega)
|
||||
{
|
||||
assert((unsigned)numregd < 255);
|
||||
assert((unsigned)numregf < 255);
|
||||
assert((unsigned)numregs < 255);
|
||||
assert((unsigned)numrega < 255);
|
||||
// To keep the arguments to this function simpler, it assumes that every
|
||||
// register might be used as a parameter for a single call.
|
||||
int numparam = numregd + numregf + numregs + numrega;
|
||||
int size = VMFrame::FrameSize(numregd, numregf, numregs, numrega, numparam, 0);
|
||||
VMFrame *frame = Alloc(size);
|
||||
frame->NumRegD = numregd;
|
||||
frame->NumRegF = numregf;
|
||||
frame->NumRegS = numregs;
|
||||
frame->NumRegA = numrega;
|
||||
frame->MaxParam = numparam;
|
||||
frame->InitRegS();
|
||||
return frame;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// VMFrameStack :: AllocFrame
|
||||
//
|
||||
// Allocates a frame from the stack suitable for calling a particular
|
||||
// function.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
VMFrame *VMFrameStack::AllocFrame(VMScriptFunction *func)
|
||||
{
|
||||
int size = VMFrame::FrameSize(func->NumRegD, func->NumRegF, func->NumRegS, func->NumRegA,
|
||||
func->MaxParam, func->ExtraSpace);
|
||||
VMFrame *frame = Alloc(size);
|
||||
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();
|
||||
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;
|
||||
}
|
||||
// Free any string registers this frame had.
|
||||
FString *regs = frame->GetRegS();
|
||||
for (int i = frame->NumRegS; i != 0; --i)
|
||||
{
|
||||
(regs++)->~FString();
|
||||
}
|
||||
// Free any parameters this frame left behind.
|
||||
VMValue *param = frame->GetParam();
|
||||
for (int i = frame->NumParam; i != 0; --i)
|
||||
{
|
||||
(param++)->~VMValue();
|
||||
}
|
||||
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 VMFrameStack::Call(VMFunction *func, VMValue *params, int numparams, VMReturn *results, int numresults, VMException **trap)
|
||||
{
|
||||
bool allocated = false;
|
||||
try
|
||||
{
|
||||
if (func->Native)
|
||||
{
|
||||
return static_cast<VMNativeFunction *>(func)->NativeCall(this, params, numparams, results, numresults);
|
||||
}
|
||||
else
|
||||
{
|
||||
AllocFrame(static_cast<VMScriptFunction *>(func));
|
||||
allocated = true;
|
||||
VMFillParams(params, TopFrame(), numparams);
|
||||
int numret = VMExec(this, static_cast<VMScriptFunction *>(func)->Code, results, numresults);
|
||||
PopFrame();
|
||||
return numret;
|
||||
}
|
||||
}
|
||||
catch (VMException *exception)
|
||||
{
|
||||
if (allocated)
|
||||
{
|
||||
PopFrame();
|
||||
}
|
||||
if (trap != NULL)
|
||||
{
|
||||
*trap = exception;
|
||||
return -1;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
catch (EVMAbortException exception)
|
||||
{
|
||||
if (allocated)
|
||||
{
|
||||
PopFrame();
|
||||
}
|
||||
if (trap != nullptr)
|
||||
{
|
||||
*trap = nullptr;
|
||||
}
|
||||
|
||||
Printf("VM execution aborted: ");
|
||||
switch (exception)
|
||||
{
|
||||
case X_READ_NIL:
|
||||
Printf("tried to read from address zero.");
|
||||
break;
|
||||
|
||||
case X_WRITE_NIL:
|
||||
Printf("tried to write to address zero.");
|
||||
break;
|
||||
|
||||
case X_TOO_MANY_TRIES:
|
||||
Printf("too many try-catch blocks.");
|
||||
break;
|
||||
|
||||
case X_ARRAY_OUT_OF_BOUNDS:
|
||||
Printf("array access out of bounds.");
|
||||
break;
|
||||
|
||||
case X_DIVISION_BY_ZERO:
|
||||
Printf("division by zero.");
|
||||
break;
|
||||
|
||||
case X_BAD_SELF:
|
||||
Printf("invalid self pointer.");
|
||||
break;
|
||||
}
|
||||
Printf("\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (allocated)
|
||||
{
|
||||
PopFrame();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
215
src/scripting/vm/vmops.h
Normal file
215
src/scripting/vm/vmops.h
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#ifndef xx
|
||||
#define xx(op, name, mode) OP_##op
|
||||
#endif
|
||||
|
||||
xx(NOP, nop, NOP), // no operation
|
||||
|
||||
// Load constants.
|
||||
xx(LI, li, LI), // load immediate signed 16-bit constant
|
||||
xx(LK, lk, LKI), // load integer constant
|
||||
xx(LKF, lk, LKF), // load float constant
|
||||
xx(LKS, lk, LKS), // load string constant
|
||||
xx(LKP, lk, LKP), // load pointer constant
|
||||
xx(LFP, lf, LFP), // load frame pointer
|
||||
|
||||
// Load from memory. rA = *(rB + rkC)
|
||||
xx(LB, lb, RIRPKI), // load byte
|
||||
xx(LB_R, lb, RIRPRI),
|
||||
xx(LH, lh, RIRPKI), // load halfword
|
||||
xx(LH_R, lh, RIRPRI),
|
||||
xx(LW, lw, RIRPKI), // load word
|
||||
xx(LW_R, lw, RIRPRI),
|
||||
xx(LBU, lbu, RIRPKI), // load byte unsigned
|
||||
xx(LBU_R, lbu, RIRPRI),
|
||||
xx(LHU, lhu, RIRPKI), // load halfword unsigned
|
||||
xx(LHU_R, lhu, RIRPRI),
|
||||
xx(LSP, lsp, RFRPKI), // load single-precision fp
|
||||
xx(LSP_R, lsp, RFRPRI),
|
||||
xx(LDP, ldp, RFRPKI), // load double-precision fp
|
||||
xx(LDP_R, ldp, RFRPRI),
|
||||
xx(LS, ls, RSRPKI), // load string
|
||||
xx(LS_R, ls, RSRPRI),
|
||||
xx(LO, lo, RPRPKI), // load object
|
||||
xx(LO_R, lo, RPRPRI),
|
||||
xx(LP, lp, RPRPKI), // load pointer
|
||||
xx(LP_R, lp, RPRPRI),
|
||||
xx(LV, lv, RVRPKI), // load vector
|
||||
xx(LV_R, lv, RVRPRI),
|
||||
|
||||
xx(LBIT, lbit, RIRPI8), // rA = !!(*rB & C) -- *rB is a byte
|
||||
|
||||
// Store instructions. *(rA + rkC) = rB
|
||||
xx(SB, sb, RPRIKI), // store byte
|
||||
xx(SB_R, sb, RPRIRI),
|
||||
xx(SH, sh, RPRIKI), // store halfword
|
||||
xx(SH_R, sh, RPRIRI),
|
||||
xx(SW, sw, RPRIKI), // store word
|
||||
xx(SW_R, sw, RPRIRI),
|
||||
xx(SSP, ssp, RPRFKI), // store single-precision fp
|
||||
xx(SSP_R, ssp, RPRFRI),
|
||||
xx(SDP, sdp, RPRFKI), // store double-precision fp
|
||||
xx(SDP_R, sdp, RPRFRI),
|
||||
xx(SS, ss, RPRSKI), // store string
|
||||
xx(SS_R, ss, RPRSRI),
|
||||
xx(SP, sp, RPRPKI), // store pointer
|
||||
xx(SP_R, sp, RPRPRI),
|
||||
xx(SV, sv, RPRVKI), // store vector
|
||||
xx(SV_R, sv, RPRVRI),
|
||||
|
||||
xx(SBIT, sbit, RPRII8), // *rA |= C if rB is true, *rA &= ~C otherwise
|
||||
|
||||
// Move instructions.
|
||||
xx(MOVE, mov, RIRI), // dA = dB
|
||||
xx(MOVEF, mov, RFRF), // fA = fB
|
||||
xx(MOVES, mov, RSRS), // sA = sB
|
||||
xx(MOVEA, mov, RPRP), // aA = aB
|
||||
xx(CAST, cast, CAST), // xA = xB, conversion specified by C
|
||||
xx(DYNCAST_R, dyncast,RPRPRP), // aA = aB after casting to rkC (specifying a class)
|
||||
xx(DYNCAST_K, dyncast,RPRPKP),
|
||||
|
||||
// Control flow.
|
||||
xx(TEST, test, RII16), // if (dA != BC) then pc++
|
||||
xx(JMP, jmp, I24), // pc += ABC -- The ABC fields contain a signed 24-bit offset.
|
||||
xx(IJMP, ijmp, RII16), // pc += dA + BC -- BC is a signed offset. The target instruction must be a JMP.
|
||||
xx(PARAM, param, __BCP), // push parameter encoded in BC for function call (B=regtype, C=regnum)
|
||||
xx(PARAMI, parami, I24), // push immediate, signed integer for function call
|
||||
xx(CALL, call, RPI8I8), // Call function pkA with parameter count B and expected result count C
|
||||
xx(CALL_K, call, KPI8I8),
|
||||
xx(TAIL, tail, RPI8), // Call+Ret in a single instruction
|
||||
xx(TAIL_K, tail, KPI8),
|
||||
xx(RESULT, result, __BCP), // Result should go in register encoded in BC (in caller, after CALL)
|
||||
xx(RET, ret, I8BCP), // Copy value from register encoded in BC to return value A, possibly returning
|
||||
xx(RETI, reti, I8I16), // Copy immediate from BC to return value A, possibly returning
|
||||
xx(TRY, try, I24), // When an exception is thrown, start searching for a handler at pc + ABC
|
||||
xx(UNTRY, untry, I8), // Pop A entries off the exception stack
|
||||
xx(THROW, throw, THROW), // A == 0: Throw exception object pB
|
||||
// A == 1: Throw exception object pkB
|
||||
// A >= 2: Throw VM exception of type BC
|
||||
xx(CATCH, catch, CATCH), // 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), // if rA >= BC, throw exception
|
||||
|
||||
// String instructions.
|
||||
xx(CONCAT, concat, RSRSRS), // sA = sB.. ... ..sC
|
||||
xx(LENS, lens, RIRS), // dA = sB.Length
|
||||
xx(CMPS, cmps, I8RXRX), // if ((skB op skC) != (A & 1)) then pc++
|
||||
|
||||
// Integer math.
|
||||
xx(SLL_RR, sll, RIRIRI), // dA = dkB << diC
|
||||
xx(SLL_RI, sll, RIRII8),
|
||||
xx(SLL_KR, sll, RIKIRI),
|
||||
xx(SRL_RR, srl, RIRIRI), // dA = dkB >> diC -- unsigned
|
||||
xx(SRL_RI, srl, RIRII8),
|
||||
xx(SRL_KR, srl, RIKIRI),
|
||||
xx(SRA_RR, sra, RIRIRI), // dA = dkB >> diC -- signed
|
||||
xx(SRA_RI, sra, RIRII8),
|
||||
xx(SRA_KR, sra, RIKIRI),
|
||||
xx(ADD_RR, add, RIRIRI), // dA = dB + dkC
|
||||
xx(ADD_RK, add, RIRIKI),
|
||||
xx(ADDI, addi, RIRIIs), // dA = dB + C -- C is a signed 8-bit constant
|
||||
xx(SUB_RR, sub, RIRIRI), // dA = dkB - dkC
|
||||
xx(SUB_RK, sub, RIRIKI),
|
||||
xx(SUB_KR, sub, RIKIRI),
|
||||
xx(MUL_RR, mul, RIRIRI), // dA = dB * dkC
|
||||
xx(MUL_RK, mul, RIRIKI),
|
||||
xx(DIV_RR, div, RIRIRI), // dA = dkB / dkC
|
||||
xx(DIV_RK, div, RIRIKI),
|
||||
xx(DIV_KR, div, RIKIRI),
|
||||
xx(MOD_RR, mod, RIRIRI), // dA = dkB % dkC
|
||||
xx(MOD_RK, mod, RIRIKI),
|
||||
xx(MOD_KR, mod, RIKIRI),
|
||||
xx(AND_RR, and, RIRIRI), // dA = dB & dkC
|
||||
xx(AND_RK, and, RIRIKI),
|
||||
xx(OR_RR, or, RIRIRI), // dA = dB | dkC
|
||||
xx(OR_RK, or, RIRIKI),
|
||||
xx(XOR_RR, xor, RIRIRI), // dA = dB ^ dkC
|
||||
xx(XOR_RK, xor, RIRIKI),
|
||||
xx(MIN_RR, min, RIRIRI), // dA = min(dB,dkC)
|
||||
xx(MIN_RK, min, RIRIKI),
|
||||
xx(MAX_RR, max, RIRIRI), // dA = max(dB,dkC)
|
||||
xx(MAX_RK, max, RIRIKI),
|
||||
xx(ABS, abs, RIRI), // dA = abs(dB)
|
||||
xx(NEG, neg, RIRI), // dA = -dB
|
||||
xx(NOT, not, RIRI), // dA = ~dB
|
||||
xx(SEXT, sext, RIRII8), // dA = dB, sign extended by shifting left then right by C
|
||||
xx(ZAP_R, zap, RIRIRI), // dA = dB, with bytes zeroed where bits in C/dC are one
|
||||
xx(ZAP_I, zap, RIRII8),
|
||||
xx(ZAPNOT_R, zapnot, RIRIRI), // dA = dB, with bytes zeroed where bits in C/dC are zero
|
||||
xx(ZAPNOT_I, zapnot, RIRII8),
|
||||
xx(EQ_R, beq, CIRR), // if ((dB == dkC) != A) then pc++
|
||||
xx(EQ_K, beq, CIRK),
|
||||
xx(LT_RR, blt, CIRR), // if ((dkB < dkC) != A) then pc++
|
||||
xx(LT_RK, blt, CIRK),
|
||||
xx(LT_KR, blt, CIKR),
|
||||
xx(LE_RR, ble, CIRR), // if ((dkB <= dkC) != A) then pc++
|
||||
xx(LE_RK, ble, CIRK),
|
||||
xx(LE_KR, ble, CIKR),
|
||||
xx(LTU_RR, bltu, CIRR), // if ((dkB < dkC) != A) then pc++ -- unsigned
|
||||
xx(LTU_RK, bltu, CIRK),
|
||||
xx(LTU_KR, bltu, CIKR),
|
||||
xx(LEU_RR, bleu, CIRR), // if ((dkB <= dkC) != A) then pc++ -- unsigned
|
||||
xx(LEU_RK, bleu, CIRK),
|
||||
xx(LEU_KR, bleu, CIKR),
|
||||
|
||||
// Double-precision floating point math.
|
||||
xx(ADDF_RR, add, RFRFRF), // fA = fB + fkC
|
||||
xx(ADDF_RK, add, RFRFKF),
|
||||
xx(SUBF_RR, sub, RFRFRF), // fA = fkB - fkC
|
||||
xx(SUBF_RK, sub, RFRFKF),
|
||||
xx(SUBF_KR, sub, RFKFRF),
|
||||
xx(MULF_RR, mul, RFRFRF), // fA = fB * fkC
|
||||
xx(MULF_RK, mul, RFRFKF),
|
||||
xx(DIVF_RR, div, RFRFRF), // fA = fkB / fkC
|
||||
xx(DIVF_RK, div, RFRFKF),
|
||||
xx(DIVF_KR, div, RFKFRF),
|
||||
xx(MODF_RR, mod, RFRFRF), // fA = fkB % fkC
|
||||
xx(MODF_RK, mod, RFRFKF),
|
||||
xx(MODF_KR, mod, RFKFRF),
|
||||
xx(POWF_RR, pow, RFRFRF), // fA = fkB ** fkC
|
||||
xx(POWF_RK, pow, RFRFKF),
|
||||
xx(POWF_KR, pow, RFKFRF),
|
||||
xx(MINF_RR, min, RFRFRF), // fA = min(fB),fkC)
|
||||
xx(MINF_RK, min, RFRFKF),
|
||||
xx(MAXF_RR, max, RFRFRF), // fA = max(fB),fkC)
|
||||
xx(MAXF_RK, max, RFRFKF),
|
||||
xx(ATAN2, atan2, RFRFRF), // fA = atan2(fB,fC), result is in degrees
|
||||
xx(FLOP, flop, RFRFI8), // fA = f(fB), where function is selected by C
|
||||
xx(EQF_R, beq, CFRR), // if ((fB == fkC) != (A & 1)) then pc++
|
||||
xx(EQF_K, beq, CFRK),
|
||||
xx(LTF_RR, blt, CFRR), // if ((fkB < fkC) != (A & 1)) then pc++
|
||||
xx(LTF_RK, blt, CFRK),
|
||||
xx(LTF_KR, blt, CFKR),
|
||||
xx(LEF_RR, ble, CFRR), // if ((fkb <= fkC) != (A & 1)) then pc++
|
||||
xx(LEF_RK, ble, CFRK),
|
||||
xx(LEF_KR, ble, CFKR),
|
||||
|
||||
// Vector math.
|
||||
xx(NEGV, negv, RVRV), // vA = -vB
|
||||
xx(ADDV_RR, addv, RVRVRV), // vA = vB + vkC
|
||||
xx(ADDV_RK, addv, RVRVKV),
|
||||
xx(SUBV_RR, subv, RVRVRV), // vA = vkB - vkC
|
||||
xx(SUBV_RK, subv, RVRVKV),
|
||||
xx(SUBV_KR, subv, RVKVRV),
|
||||
xx(DOTV_RR, dotv, RVRVRV), // va = vB dot vkC
|
||||
xx(DOTV_RK, dotv, RVRVKV),
|
||||
xx(CROSSV_RR, crossv, RVRVRV), // vA = vkB cross vkC
|
||||
xx(CROSSV_RK, crossv, RVRVKV),
|
||||
xx(CROSSV_KR, crossv, RVKVRV),
|
||||
xx(MULVF_RR, mulv, RVRVRV), // vA = vkB * fkC
|
||||
xx(MULVF_RK, mulv, RVRVKV),
|
||||
xx(MULVF_KR, mulv, RVKVRV),
|
||||
xx(LENV, lenv, RFRV), // fA = vB.Length
|
||||
xx(EQV_R, beqv, CVRR), // if ((vB == vkC) != A) then pc++ (inexact if A & 32)
|
||||
xx(EQV_K, beqv, CVRK),
|
||||
|
||||
// Pointer math.
|
||||
xx(ADDA_RR, add, RPRPRI), // pA = pB + dkC
|
||||
xx(ADDA_RK, add, RPRPKI),
|
||||
xx(SUBA, sub, RIRPRP), // dA = pB - pC
|
||||
xx(EQA_R, beq, CPRR), // if ((pB == pkC) != A) then pc++
|
||||
xx(EQA_K, beq, CPRK),
|
||||
|
||||
#undef xx
|
||||
Loading…
Add table
Add a link
Reference in a new issue