1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 01:49:54 +00:00
openmw-tes3mp/monster/compiler/assembler.d
nkorslund 3f1aeb3aef Adding in Monster
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@61 ea6a568a-9f4f-0410-981a-c910a81bb256
2008-11-07 08:45:18 +00:00

1063 lines
25 KiB
D

/*
Monster - an advanced game scripting language
Copyright (C) 2007, 2008 Nicolay Korslund
Email: <korslund@gmail.com>
WWW: http://monster.snaptoad.com/
This file (assembler.d) is part of the Monster script language
package.
Monster is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module monster.compiler.assembler;
import monster.minibos.string;
import monster.minibos.stdio;
import monster.util.list;
import monster.compiler.bytecode;
import monster.compiler.linespec;
import monster.compiler.types;
import monster.vm.error;
// Print the final output from assemble to stdout
//debug=printOutput;
// Element type
enum ET
{
None, // Undefined
OpCode, // Instruction, variable number of bytes
Data1, // One byte of data
Data4, // One int of data
Data8, // Two ints of data
Ptr, // Data pointer
Label, // Jump label
JumpPtr, // Appears after a jump instruction, referes
// to a label.
JumpIndex, // Refers to a label by index, not by pointer
}
// Represents one element in the code list.
struct CodeElement
{
ET type;
union
{
ubyte bytes[8];
ubyte bdata;
int idata;
long ldata;
}
CodeElement *label; // Destination for jumps
// Cumulative offset into the final code buffer
int offset = -1;
// Source code line corresponding to this instruction
int line = -1;
// Label number. This is different from the label / jump index,
// which is a low-level reference. One label number is given to each
// named label in state code. These are listed in a state label
// export table in the compiled class file.
int lnumber = -1;
// Set the offset to val, return the offset of the next element (val
// + size of this)
uint setOffset(uint val)
{
offset = val;
return val + getSize();
}
// Insert ourselves into the code block at the right offset.
void insertCode(ubyte[] code)
{
// Zero-size elements don't have any code
if(getSize() == 0) return;
// Jumps are handled in a special way
assert(type != ET.JumpIndex);
if(type == ET.JumpPtr)
{
// Convert it back to a number, but this time the label offset
type = ET.JumpIndex;
idata = label.offset;
}
// Write the data
int s = getSize();
code[offset..offset+s] = bytes[0..s];
}
void print(LineSpec[] lines)
{
int index;
PT ptype;
// These start a new line
if(type == ET.Label || type == ET.OpCode)
{
int fline = findLine(lines, offset);
if(fline != line)
writef("\n%s(%s): ", line, fline);
else
writef("\n%s: ", line);
}
switch(type)
{
case ET.Label:
writef("label %s @ %s", idata, offset);
break;
case ET.OpCode:
writef("%s", bcToString[idata]);
break;
case ET.Data1:
writef(", byte ", idata);
break;
case ET.JumpPtr:
writef(", ptr -> ", label.offset);
break;
case ET.JumpIndex:
writef(", idx -> ", idata);
break;
case ET.Ptr:
decodePtr(idata, ptype, index);
writef(", ptr(%s, %s)", ptype, index);
break;
case ET.Data4:
writef(", int %s", idata);
break;
case ET.Data8:
case ET.None:
default:
assert(0);
}
}
void setLabel(CodeElement *lab)
{
// Check that we only get called on jumps
assert(type == ET.JumpIndex);
// And check that the label actually matches this jump
assert(lab.type == ET.Label);
assert(lab.idata == idata);
label = lab;
type = ET.JumpPtr;
}
// Get the size of this element in the generated code. It does not
// determine how much data is stored by this struct, or which
// variable above it uses, only how big the result is in the
// finished code buffer.
int getSize()
{
switch(type)
{
case ET.Label:
return 0;
case ET.OpCode:
assert(idata > 0);
// Instructions are either one or two bytes. Single byte
// instructions have values <= 255, everything else is coded
// with two bytes.
if(idata < 256) return 1;
return 2;
case ET.Data1:
return 1;
case ET.JumpPtr:
case ET.JumpIndex:
case ET.Ptr:
case ET.Data4:
return 4;
case ET.Data8:
return 8;
case ET.None:
default:
assert(0);
}
}
}
Assembler tasm;
// A structure that takes a series of pseudo-assembler commands as
// input and stores them in a list of byte code instructions and data.
// We can perform various optimizations on this command list (not
// implemented yet) before storing it to file.
struct Assembler
{
private:
LinkedList!(CodeElement) codeList;
int toInt(float f) { return *(cast(int*)&f); }
// Add a new code element to the list
void add(ET type, long data)
{
if(type == ET.OpCode && data > 255)
{
// Instruction values over 255 are stored as two bytes
data -= 256;
assert(data < 255);
data <<= 8; // Move the instruction into the next byte
data += BC.MultiByte; // Set the multi-byte indicator
}
CodeElement ce;
ce.ldata = data;
ce.type = type;
ce.line = line;
codeList ~= ce;
}
// Low level opcode functions
void cmd(BC code) { add(ET.OpCode, code); }
void addb(int i)
{
assert(i >= 0 && i < 256);
add(ET.Data1, i);
}
void addib(int i)
{
assert(i >= -128 && i < 127);
add(ET.Data1, i);
}
void addi(int i) { add(ET.Data4, i); }
void addl(long l) { add(ET.Data8, l); }
void label_add(int i) { add(ET.Label, i); }
void jump_add(int i) { add(ET.JumpIndex, i); }
// Add a pointer
void addPtr(PT type, int index)
{ add(ET.Ptr, codePtr(type, index)); }
// Current index in the table of jump points
int jumpIndex;
// Highest inserted label number
int maxNumber;
// Current line number
int line;
// Add a command that varies depending on a type size. If size is >
// 1, it is added as an integer parameter to the instruction.
void cmdmult(BC codeOne, BC codeMult, int size)
{
assert(size >= 1);
if(size == 1) cmd(codeOne);
else
{
cmd(codeMult);
addi(size);
}
}
// Like cmdmult, but size can only be 1 or 2, and the size isn't
// added.
void cmd2(BC codeOne, BC codeTwo, int size)
{
assert(size == 1 || size == 2);
if(size == 1) cmd(codeOne);
else cmd(codeTwo);
}
public:
// Start a new function
void newFunc()
{
codeList.reset();
jumpIndex = 0;
maxNumber = -1;
line = -1;
}
// Set the current line. A line table is stored in the file, so
// specific instructions can be translated to a line in the source
// file. Mainly used for debugging.
void setLine(int ln)
{
// Never overwrite a valid line with -1
if(ln == -1) return;
line = ln;
}
int getLine() { return line; }
// Higher level opcode commands
// Exit a function. params is the number of function parameters
// and/or local variables to pop, ret is the return value size
void exit(int params = 0, int ret = 0)
{
if(params)
{
if(ret == 1)
{
// Function has parameters and a return value of size 1
cmd(BC.ReturnVal);
addi(params);
}
else if(ret > 1)
{
// Return value larger than 1
cmd(BC.ReturnValN);
addi(params);
addi(ret);
}
else
{
// Parameters, but no return value
cmd(BC.Return);
addi(params);
}
return;
}
// No parameters / local variables. The return value (if any) as
// already on the top of the stack, so we can just exit quietly
// and pass it along.
cmd(BC.Exit);
}
void error(Err code)
{
cmd(BC.Error);
addb(code);
assert(code != 0);
}
void callFunc(int func, int cls)
{
cmd(BC.Call);
addi(cls);
addi(func);
}
void callIdle(int func, int cls)
{
cmd(BC.CallIdle);
addi(cls);
addi(func);
}
void callFarFunc(int func, int cls)
{
cmd(BC.CallFar);
addi(cls);
addi(func);
}
void halt() { cmd(BC.Halt); }
void newObj(int i)
{
cmd(BC.New);
addi(i);
}
// Copy the topmost int on the stack
void dup() { cmd(BC.Dup); }
/* All jump and label commands come in two versions, one for
jump-before-label, and one for label-before-jump. Eg.:
Label-before-jump:
int l1 = tasm.label(); // Jump back here
...
tasm.jumpz(l1);
Jump-before-label:
int l2 = tasm.jump();
...
tasm.label(l2); // Jump forwards to here
The following other cases are also currently possible:
- Orphaned (backward) label:
tasm.label() with no corresponding jump
- Multiple backward jumps:
a=tasm.label(); tasm.jump(a); tasm.jump(a); ...
Other combinations are NOT allowed! However, these should be
enough to implement more complex jump patterns like
break/continue. The more advanced combinations are handled in
LabelStatement and GotoStatement in statement.d
*/
// Generic jump command to jump to a labed that is not yet
// defined. Returns a jump index.
int genjump(BC code)
{
cmd(code);
// Add and return the current jump index. Then increase it for the
// next jump.
jump_add(jumpIndex);
return jumpIndex++;
}
// Generic version to jump to an existing label index
void genjump(BC code, int label)
{
cmd(code);
jump_add(label);
}
// Jump if zero
int jumpz() { return genjump(BC.JumpZ); }
void jumpz(int i) { genjump(BC.JumpZ, i); }
// Jump if non-zero
int jumpnz() { return genjump(BC.JumpNZ); }
void jumpnz(int i) { genjump(BC.JumpNZ, i); }
// Just jump dammit!
int jump() { return genjump(BC.Jump); }
void jump(int i) { genjump(BC.Jump, i); }
// Used for label before jump
int label()
{
label_add(jumpIndex);
return jumpIndex++;
}
// Label after jump
void label(int i) { label_add(i); }
// Assign a lnumber to the last label element (used by the functions
// below)
private void setLNumber(int number)
{
with(codeList.getTail().value)
{
// Some sanity checks
assert(type == ET.Label);
assert(lnumber == -1);
// Set the label number
lnumber = number;
}
// Remember the max number given
if(number > maxNumber) maxNumber = number;
}
// Assign a number to a label (used for named labels, to get their
// offset)
int labelNum(int number)
{
int l = label();
if(number != -1)
setLNumber(number);
return l;
}
void labelNum(int i, int number)
{
label(i);
if(number != -1)
setLNumber(number);
}
// Push the current stack position
void getStack() { cmd(BC.GetStack); }
// Variable and stack management:
void push(int i)
{
cmd(BC.PushData);
addi(i);
}
void push(float f) { push(*(cast(int*)&f)); }
void push(uint u) { push(*(cast(int*)&u)); }
union Share
{
double d;
long l;
ulong u;
int ints[2];
}
void push8(long l)
{
Share s;
s.l = l;
pushArray(s.ints);
}
void push8(ulong u)
{
Share s;
s.u = u;
pushArray(s.ints);
}
void push8(double d)
{
Share s;
s.d = d;
pushArray(s.ints);
}
void pushArray(int[] data ...)
{
// TODO: We should use a push8 instruction or something for larger
// data types, but that's optimization.
foreach(int i; data)
push(i);
}
//void pushArray(float[] data...) { pushArray(cast(int[])data); }
// Push a value from the stack
void pushLocal(int index, int siz)
{
assert(siz >= 1);
assert(index >= 0 || index <= -siz);
// Local variables are counted forwards from the current stack
// frame pointer. The frame pointer is set whenever a new function
// starts, that is AFTER the parameters have been pushed. Thus we
// count parameters backwards from the stack pointer, and
// variables forwards.
// For multi-int variables, just push each int as a separate
// variable. We can optimize this later.
while(siz-- > 0)
{
cmd(BC.PushLocal);
addi(index++);
}
}
// Push a value from a class variable. The second parameter is the
// number of ints to push.
void pushClass(int index, int siz)
{
assert(siz >= 1);
assert(index >= 0);
while(siz-- > 0)
{
cmd(BC.PushClassVar);
addi(index++);
}
}
void pushParentVar(int index, int classIndex, int siz)
{
assert(index >= 0);
assert(classIndex >= 0);
assert(siz >= 1);
while(siz-- > 0)
{
cmd(BC.PushParentVar);
addi(classIndex);
addi(index++);
}
}
// Push a variable from another object
void pushFarClass(int index, int classIndex, int siz)
{
assert(index >= 0);
assert(classIndex >= 0);
assert(siz >= 1);
// The FAR versions of variables, assumes that the object
// reference has already been pushed.
cmdmult(BC.PushFarClassVar, BC.PushFarClassMulti, siz);
addi(classIndex);
addi(index++);
}
// Push 'this', the current object reference
void pushThis() { cmd(BC.PushThis); }
private void pushPtr(PT pt, int index)
{
cmd(BC.PushData);
addPtr(pt, index);
}
// Push addresses instead of values
// Pointer to data segment of this object
void pushClassAddr(int i) { pushPtr(PT.DataOffs, i); }
// Imported local variable
void pushParentVarAddr(int i, int classIndex)
{
assert(classIndex >= 0);
// We have to push the class index FIRST. It will be popped
// immediately after the pointer is decoded.
push(classIndex);
pushPtr(PT.DataOffsCls, i);
}
// Data segment of another object, but this class (use offset.)
void pushFarClassAddr(int i, int classIndex)
{
assert(classIndex >= 0);
push(classIndex);
pushPtr(PT.FarDataOffs, i);
}
// Local stack variable
void pushLocalAddr(int i) { pushPtr(PT.Stack, i); }
// Push the address of an array element. The index and array are
// already on the stack.
void elementAddr() { pushPtr(PT.ArrayIndex,0); }
// Create an instruction that reads data from the data segment and
// creates an array from it at runtime. TODO: This might be
// decommissioned soon.
void makeArrayLit(int offset, int len, int elemSize)
{
cmd(BC.MakeArray);
addi(offset);
addi(elemSize);
addi(len*elemSize);
}
// Convert an array index to a const reference
void makeArrayConst() { cmd(BC.MakeConstArray); }
// Check if an array is const
void isArrayConst() { cmd(BC.IsConstArray); }
void makeSlice() { cmd(BC.Slice); }
// Pops the last 'len' elements off the stack and creates an array
// from them. The element size (in ints) is given in the second
// parameter.
void popToArray(int len, int elemSize)
{
cmd(BC.PopToArray);
addi(len*elemSize);
addi(elemSize);
}
// Create a new array, initialized with the given value and with the
// given dimension number. The lengths are popped of the stack.
void newArray(int ini[], int rank)
{
cmd(BC.NewArray);
addi(rank);
addi(ini.length);
foreach(i; ini) addi(i);
}
// Copies two array references from the stack, copies the data in
// the last into the first. The (raw) lengths must match.
void copyArray() { cmd(BC.CopyArray); }
// Fills an array with a specific value.
void fillArray(int size)
{
cmd(BC.FillArray);
addi(size);
}
// Creates a copy of the array whose index is on the stack
void dupArray() { cmd(BC.DupArray); }
// Just remove a value from the stack (will later be optimized away
// in most cases.)
void pop(int i=1)
{
if(i == 0) return;
if(i == 1) cmd(BC.Pop);
else
{
cmd(BC.PopN);
addb(i);
}
}
void mov(int s) { cmd2(BC.StoreRet, BC.StoreRet8, s); }
// Set object state to the given index
void setState(int st, int label, int cls)
{
cmd(BC.State);
addi(st);
addi(label);
addi(cls);
}
// Fetch an element from an array
void fetchElement() { cmd(BC.FetchElem); }
// Array concatination. The first concatinates two arrays, the
// others adds a single element to the array from either side.
void catArray() { cmd(BC.CatArray); }
void catArrayLeft(int s) { cmd(BC.CatLeft); addi(s); }
void catArrayRight(int s) { cmd(BC.CatRight); addi(s); }
// Get the length of an array. The parameter gives element size. For
// example, an array of six ints with an element size of 2 (eg. a
// double) has length 3.
void getArrayLength() { cmd(BC.GetArrLen); }
// Reverse an array in place
void reverseArray() { cmd(BC.ReverseArray); }
// Create an array iterator
void createArrayIterator(bool isRev, bool isRef)
{
cmd(BC.CreateArrayIter);
addb(isRev);
addb(isRef);
}
// Create a class iterator
void createClassIterator(int classNum)
{
cmd(BC.CreateClassIter);
addi(classNum);
}
// Do the next iteration
void iterateNext() { cmd(BC.IterNext); }
// Break off iteration
void iterateBreak() { cmd(BC.IterBreak); }
void iterateUpdate(int pos)
{
if(pos < 0)
fail("iterUpdate stack position must be positive");
cmd(BC.IterUpdate);
addi(pos);
}
// Value modifiers
void add(Type t)
{
if(t.isInt || t.isUint) cmd(BC.IAdd);
else if(t.isLong || t.isUlong) cmd(BC.LAdd);
else if(t.isFloat) cmd(BC.FAdd);
else if(t.isDouble) cmd(BC.DAdd);
else assert(0);
}
void sub(Type t)
{
if(t.isInt || t.isUint) cmd(BC.ISub);
else if(t.isLong || t.isUlong) cmd(BC.LSub);
else if(t.isFloat) cmd(BC.FSub);
else if(t.isDouble) cmd(BC.DSub);
else assert(0);
}
void mul(Type t)
{
if(t.isInt || t.isUint) cmd(BC.IMul);
else if(t.isLong || t.isUlong) cmd(BC.LMul);
else if(t.isFloat) cmd(BC.FMul);
else if(t.isDouble) cmd(BC.DMul);
else assert(0);
}
void div(Type t)
{
if(t.isInt) cmd(BC.IDiv);
else if(t.isUint) cmd(BC.UDiv);
else if(t.isLong) cmd(BC.LDiv);
else if(t.isUlong) cmd(BC.ULDiv);
else if(t.isFloat) cmd(BC.FDiv);
else if(t.isDouble) cmd(BC.DDiv);
else assert(0);
}
void idiv(Type t)
{
if(t.isIntegral) div(t);
else if(t.isFloat) cmd(BC.FIDiv);
else if(t.isDouble) cmd(BC.DIDiv);
else assert(0);
}
void divrem(Type t)
{
if(t.isInt) cmd(BC.IDivRem);
else if(t.isUint) cmd(BC.UDivRem);
else if(t.isLong) cmd(BC.LDivRem);
else if(t.isUlong) cmd(BC.ULDivRem);
else if(t.isFloat) cmd(BC.FDivRem);
else if(t.isDouble) cmd(BC.DDivRem);
else assert(0);
}
void neg(Type t)
{
if(t.isInt) cmd(BC.INeg);
else if(t.isLong) cmd(BC.LNeg);
else if(t.isFloat) cmd(BC.FNeg);
else if(t.isDouble) cmd(BC.DNeg);
else assert(0);
}
void preInc(int s) { cmd2(BC.PreInc, BC.PreInc8, s); }
void preDec(int s) { cmd2(BC.PreDec, BC.PreDec8, s); }
void postInc(int s) { cmd2(BC.PostInc, BC.PostInc8, s); }
void postDec(int s) { cmd2(BC.PostDec, BC.PostDec8, s); }
// Type casting
void castIntToLong(bool fromSign)
{
if(fromSign) cmd(BC.CastI2L);
// Converting to unsigned long is as simple as setting zero as the
// upper int. We don't need a separate instruction to do this.
else push(0);
}
void castLongToInt() { pop(); }
void castIntToFloat(Type fr, Type to)
{
assert(fr.isIntegral);
assert(to.isFloating);
if(to.isFloat)
{
if(fr.isInt) cmd(BC.CastI2F);
else if(fr.isUint) cmd(BC.CastU2F);
else if(fr.isLong) cmd(BC.CastL2F);
else if(fr.isUlong) cmd(BC.CastUL2F);
else assert(0);
}
else
{
assert(to.isDouble);
if(fr.isInt) cmd(BC.CastI2D);
else if(fr.isUint) cmd(BC.CastU2D);
else if(fr.isLong) cmd(BC.CastL2D);
else if(fr.isUlong) cmd(BC.CastUL2D);
else assert(0);
}
}
void castFloatToFloat(int fs, int ts)
{
if(fs == 1 && ts == 2) cmd(BC.CastF2D);
else if(fs == 2 && ts == 1) cmd(BC.CastD2F);
else assert(0);
}
// Hmm, could as well merge the int and float versions. Later on
// we'll convert to a generic type instead of directly to string
// though.
void castIntToString(Type t)
{
cmd(BC.CastI2S);
if(t.isInt) addb(1);
else if(t.isUint) addb(2);
else if(t.isLong) addb(3);
else if(t.isUlong) addb(4);
else assert(0);
}
void castFloatToString(Type t)
{
assert(t.isFloating);
cmd(BC.CastF2S);
addb(t.getSize);
}
void castBoolToString() { cmd(BC.CastB2S); }
void castObjToString() { cmd(BC.CastO2S); }
// Cast an object to a parent class
void upcast(int cindex)
{
cmd(BC.Upcast);
addi(cindex);
}
// Boolean operators
void isEqual(int s) { cmdmult(BC.IsEqual, BC.IsEqualMulti, s); }
void isCaseEqual() { cmd(BC.IsCaseEqual); }
void less(Type t)
{
if(t.isUint || t.isChar) cmd(BC.ULess);
else if(t.isInt) cmd(BC.ILess);
else if(t.isLong) cmd(BC.LLess);
else if(t.isFloat) cmd(BC.FLess);
else if(t.isUlong) cmd(BC.ULLess);
else if(t.isDouble) cmd(BC.DLess);
}
void not() { cmd(BC.Not); }
void cmpArray() { cmd(BC.CmpArray); }
void icmpString() { cmd(BC.ICmpStr); }
// Convert the current code list to byte code, and return it. The
// lines parameter is filled with an array of line specifiers that
// can be used to translate instruction position to a source code
// line number. The offsets parameter, when not null, must be of
// length maxNumber+1. It will be filled with the offsets of
// numbered labels.
ubyte[] assemble(ref LineSpec lines[], uint offsets[] = null)
in
{
if(offsets.length != 0)
assert(offsets.length == 1+maxNumber,
"offset table must match number of named labels");
else
assert(maxNumber == -1,
"assemble() called with named labels but nowhere to store offsets");
}
body
{
lines = null;
// List of jumps, used for converting jump numbers to specific
// labels.
CodeElement* jumpList[];
CodeElement* labelList[];
jumpList.length = jumpIndex;
labelList.length = jumpIndex;
// Go through the code list and resolve jumps and labels
foreach(ref CodeElement ce; codeList)
{
assert(ce.type != ET.JumpPtr);
if(ce.type == ET.JumpIndex)
{
// Has the label been looked up?
if(labelList[ce.idata] != null)
// Set the jump pointer to replace the index
ce.setLabel(labelList[ce.idata]);
else
{
// Store the jump until the label is found. There can
// only be one jump stored in each jumplist element.
assert(jumpList[ce.idata] == null);
jumpList[ce.idata] = &ce;
}
continue;
}
if(ce.type == ET.Label)
{
// Is there a jump that refers to this label?
if(jumpList[ce.idata] != null)
{
// Make the jump point to this label.
jumpList[ce.idata].setLabel(&ce);
jumpList[ce.idata] = null;
}
else
// Set the label up for later referal
labelList[ce.idata] = &ce;
}
}
// Do all low-level optimalizations here.
// Finishing up. Go through the list and set the offsets and the
// line specifications.
uint offset = 0;
LineSpec ls;
ls.line = -2;
ls.pos = -1;
foreach(ref CodeElement ce; codeList)
{
// Only store line specs for opcodes
if(ce.type == ET.OpCode &&
ls.line != ce.line && offset != ls.pos)
{
// New line!
ls.pos = offset;
ls.line = ce.line;
lines ~= ls;
}
offset = ce.setOffset(offset);
}
// Output the current code list
debug(printOutput)
{
writefln("Compiled function:");
foreach(CodeElement ce; codeList)
ce.print(lines);
writefln();
}
// Store the offsets of named labels in the 'offsets' table
if(offsets.length > 0)
{
foreach(CodeElement ce; codeList)
if(ce.lnumber != -1)
offsets[ce.lnumber] = ce.offset;
}
// Set up the code list, fill it, and return it.
ubyte[] code;
code.length = offset;
foreach(ref CodeElement ce; codeList)
ce.insertCode(code);
return code;
}
}