diff --git a/Makefile b/Makefile index 4ce19fe83b..5f5077b9dc 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,8 @@ src := $(src) $(wildcard mscripts/*.d) src := $(src) monster/monster.d \ $(wildcard monster/vm/*.d) \ $(wildcard monster/compiler/*.d) \ -$(wildcard monster/util/*.d) +$(wildcard monster/util/*.d) \ +$(wildcard monster/modules/*.d) obj := $(src:%.d=objs/%.o) # The NIF object files for niftool and bsatool are put in a separate diff --git a/build_openmw.bat b/build_openmw.bat index 05634fee91..6612a98fdf 100755 --- a/build_openmw.bat +++ b/build_openmw.bat @@ -8,4 +8,4 @@ g++ -c ogre\cpp_ogre.cpp -I.\includes\ogre\ g++ -c bullet\cpp_bullet.cpp -I.\includes\bullet\ echo Compiling main program (openmw.exe) -gdc -g openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d bullet\*.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o libbulletdynamics.a libbulletcollision.a libbulletmath.a mscripts\object.d monster\monster.d monster\compiler\*.d monster\vm\*.d monster\util\*.d avcodec-51.dll avformat-52.dll avdevice-52.dll avutil-49.dll openal32.dll ogremain_d.dll OIS_d.dll -lstdc++ -o openmw.exe +gdc -g openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d bullet\*.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o libbulletdynamics.a libbulletcollision.a libbulletmath.a mscripts\object.d monster\monster.d monster\compiler\*.d monster\vm\*.d monster\util\*.d monster\modules\*.d avcodec-51.dll avformat-52.dll avdevice-52.dll avutil-49.dll openal32.dll ogremain_d.dll OIS_d.dll -lstdc++ -o openmw.exe diff --git a/input/events.d b/input/events.d index 59aeceb326..7357ff2856 100644 --- a/input/events.d +++ b/input/events.d @@ -224,10 +224,15 @@ void initializeInput() // put another import in core.config. I should probably check the // bug list and report it. updateMouseSensitivity(); + + // Set up the FPS ticker + auto mo = (new MonsterClass("FPSTicker")).getSing(); + frameCount = mo.getIntPtr("frameCount"); + mo.setState("tick"); } -float tmpTime = 0; -int cnt; +// Points directly to FPSTicker.frameCounter in Monster +int *frameCount; extern(C) int ois_isPressed(int keysym); @@ -242,19 +247,12 @@ bool isPressed(Keys key) extern(C) int d_frameStarted(float time) { - tmpTime += time; - cnt++; - if(tmpTime >= 1.5) - { - writefln("fps: ", cnt/tmpTime); - cnt = 0; - tmpTime = 0; - } + (*frameCount)++; if(doExit) return 0; // Run the Monster scheduler - scheduler.doFrame(); + vm.frame(time); musCumTime += time; if(musCumTime > musRefresh) diff --git a/monster/compiler/assembler.d b/monster/compiler/assembler.d index dfac9c2f3f..0673da9e46 100644 --- a/monster/compiler/assembler.d +++ b/monster/compiler/assembler.d @@ -32,6 +32,7 @@ import monster.util.list; import monster.compiler.bytecode; import monster.compiler.linespec; import monster.compiler.types; +import monster.compiler.tokenizer; import monster.vm.error; @@ -364,23 +365,18 @@ struct Assembler assert(code != 0); } - void callFunc(int func, int cls) + void callFunc(int func, int cls, bool isFar) { - cmd(BC.Call); + if(isFar) cmd(BC.CallFar); + else cmd(BC.Call); addi(cls); addi(func); } - void callIdle(int func, int cls) + void callIdle(int func, int cls, bool isFar) { - cmd(BC.CallIdle); - addi(cls); - addi(func); - } - - void callFarFunc(int func, int cls) - { - cmd(BC.CallFar); + if(isFar) cmd(BC.CallIdleFar); + else cmd(BC.CallIdle); addi(cls); addi(func); } @@ -670,17 +666,6 @@ struct Assembler // 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); } @@ -864,10 +849,17 @@ struct Assembler 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); } + void incDec(TT op, bool postfix, int s) + { + if(op == TT.PlusPlus) + if(postfix) cmd2(BC.PostInc, BC.PostInc8, s); + else cmd2(BC.PreInc, BC.PreInc8, s); + else if(op == TT.MinusMinus) + if(postfix) cmd2(BC.PostDec, BC.PostDec8, s); + else cmd2(BC.PreDec, BC.PreDec8, s); + else + assert(0, "Illegal op type"); + } // Type casting void castIntToLong(bool fromSign) @@ -911,36 +903,13 @@ struct Assembler 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) + // Cast a generic type to string + void castToString(int tindex) { - 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); + cmd(BC.CastT2S); + addi(tindex); } - 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); } diff --git a/monster/compiler/block.d b/monster/compiler/block.d index 1b44573b29..c876cd568a 100644 --- a/monster/compiler/block.d +++ b/monster/compiler/block.d @@ -79,7 +79,7 @@ abstract class Block static void reqNext(ref TokenArray toks, TT type, out Token tok) { if(!isNext(toks, type, tok)) - fail("Expected " ~ tokenList[type], toks); + fail("Expected " ~ tokenList[type], toks); } static void reqNext(ref TokenArray toks, TT type, out Floc loc) diff --git a/monster/compiler/bytecode.d b/monster/compiler/bytecode.d index a0f42a4cee..f305383772 100644 --- a/monster/compiler/bytecode.d +++ b/monster/compiler/bytecode.d @@ -30,14 +30,19 @@ enum BC Exit = 1, // Exit function. Call, // Call function in this object. Takes a class - // index and a function index, both ints + // tree index and a function index, both ints. CallFar, // Call function in another object. Takes a - // class index and a function index. The + // class index tree and a function index. The // object must be pushed on the stack. - CallIdle, // Calls an idle function (in this object.) - // Takes a class index and a function index. + CallIdle, // Calls an idle function in this object. + // Takes a class tree index and a function + // index. + + CallIdleFar, // Calls an idle function in another + // object. Also takes an object index from the + // stack. Return, // Takes a parameter nr (int). Equivalent to: // POPN nr (remove nr values of the stack) @@ -95,11 +100,11 @@ enum BC // object). Parameter is an int offset. PushParentVar, // Push value in data segment of parent - // object. int class index, int offset + // object. int class tree index, int offset PushFarClassVar, // Push value from the data segment in another // object. The object reference is already on - // the stack. int class index, int offset + // the stack. int class tree index, int offset PushFarClassMulti, // Pushes multiple ints from the data // segment. Takes the variable size (int) as @@ -243,27 +248,8 @@ enum BC CastUL2D, // ulong to double CastF2D, // float to double - CastI2S, // int to char[]. Takes one byte giving the - // int type: 1=int,2=uint,3=long,4=ulong - CastF2S, // float to char[]. Takes one byte giving the - // float size (1 or 2). - CastB2S, // bool to char[] - CastO2S, // an object to char[] - - // The "Cast Type Array to String" instructions below are used to - // convert arrays of the form [1,2,3] and [[1,2,3],[4,5]] to a - // string. They all take a byte as parameter, specifying the array - // depth. They are NOT IMPLEMENTED YET! - - CastIA2S, // Cast int arrays to char[] - CastFA2S, // Cast float arrays to char[] - CastBA2S, // Cast bool arrays to char[] - CastCA2S, // Cast char arrays to char[] - CastOA2S, // Cast object arrays to char[] - - Upcast, // Implicitly cast an object to a parent class - // type. Takes an int class index. - + CastT2S, // cast any type to string. Takes the type + // index (int) as a parameter FetchElem, // Get an element from an array. Pops the // index, then the array reference, then @@ -273,13 +259,6 @@ enum BC GetArrLen, // Get the length of an array. Pops the array, // pushes the length. - MakeArray, // Takes an offset (int), the element size - // (int) and the total raw length (number of - // elements * elem size). Reads data from the - // data segment at offset and creates an array - // of the given length (times the element - // size). Pushes the array index. - PopToArray, // Takes a raw length n (int) and an element // size (int). Creates an array from the last // n values on the stack and pops them off. @@ -433,8 +412,8 @@ enum PT DataOffsCls = 4, // Variable is in this object, but in another // class. The class MUST be a parent class of the - // current object. A class index follows this - // pointer on the stack. + // current object. A class tree index follows + // this pointer on the stack. FarDataOffs = 5, // Another class, another object. The index is a // data offset. Pop the class index off the @@ -616,17 +595,7 @@ char[][] bcToString = BC.CastL2D: "CastL2D", BC.CastUL2D: "CastUL2D", BC.CastF2D: "CastF2D", - BC.CastI2S: "CastI2S", - BC.CastF2S: "CastF2S", - BC.CastB2S: "CastB2S", - BC.CastO2S: "CastO2S", - BC.CastIA2S: "CastIA2S", - BC.CastFA2S: "CastFA2S", - BC.CastBA2S: "CastBA2S", - BC.CastCA2S: "CastCA2S", - BC.CastOA2S: "CastOA2S", - BC.Upcast: "Upcast", - BC.MakeArray: "MakeArray", + BC.CastT2S: "CastT2S", BC.PopToArray: "PopToArray", BC.NewArray: "NewArray", BC.CopyArray: "CopyArray", diff --git a/monster/compiler/expression.d b/monster/compiler/expression.d index 6a1e56dbeb..a918e4507c 100644 --- a/monster/compiler/expression.d +++ b/monster/compiler/expression.d @@ -64,7 +64,7 @@ abstract class Expression : Block Expression b; Floc ln; - // These are allowed for members (eg. a.hello().you()) + // These are allowed for members (eg. hello().to.you()) if(FunctionCallExpr.canParse(toks)) b = new FunctionCallExpr; else if(VariableExpr.canParse(toks)) b = new VariableExpr; else if(isMember) fail(toks[0].str ~ " can not be a member", toks[0].loc); @@ -381,26 +381,12 @@ abstract class Expression : Block // messages. Floc getLoc() { return loc; } - // Special version of resolve that is called for members, - // ie. objname.member;. Most expressions cannot be members, - // but those that can must override this. - void resolveMember(Scope sc, Type ownerType) - { fail(toString() ~ " cannot be a member of " ~ ownerType.toString, loc); } - // Used for error messages char[] typeString() { return type.toString(); } // Can this expression be assigned to? (most types can not) bool isLValue() { return false; } - // If true, assignments to this expression (for lvalues) are handled - // through writeProperty rather than with evalDest. - bool isProperty() { return false; } - - // Static expressions. These can be evaluated as members without - // needing the owner pushed onto the stack. - bool isStatic() { return false; } - // Evaluate this using run-time instructions. This is only used when // evalCTime can not be used (ie. when isCTime is false.) void evalAsm() { fail(format("expression ", this, " not implemented")); } @@ -431,18 +417,24 @@ abstract class Expression : Block } // Evaluate this expression as a destination (ie. push a pointer - // instead of a value). Only valid for LValues. TODO: This - // completely replaces the need for a separate isLValue member, but - // I think I will keep it. It belongs in the semantic level, while - // this is in the code generation level. + // instead of a value). Only valid for LValues. void evalDest() { assert(0, "evalDest() called for non-lvalue " ~ this.toString); } - // Called on all lvalue expressions after they have been - // modified, and must be stack-neutral. - void postWrite() { assert(isLValue()); } + // Pop values of the stack and store it in this expression. Only + // valid for lvalues. This is now used in place of evalDest in most + // places (although it may use evalDest internally.) + void store() + { assert(0, "store not implemented for " ~ toString()); } - // Assign to this property. Only called if isProperty returns true. - void writeProperty() { assert(0); } + // Handles ++ and -- + void incDec(TT op, bool post) + { + assert(isLValue); + assert(type.isInt || type.isUint || + type.isLong || type.isUlong); + evalDest(); + tasm.incDec(op, post, type.getSize()); + } // Can this expression be evaluated at compile time? bool isCTime() { return false; } @@ -702,15 +694,7 @@ class ArrayLiteralExpr : Expression int elem = type.getBase().getSize(); int num = params.length; - // Create a temporary array containing the data - const int LEN = 32; - int buf[LEN]; - int data[]; - - if(num*elem <= LEN) - data = buf[0..num*elem]; - else - data = new int[num*elem]; + int data[] = new int[num*elem]; // Set up the data foreach(i, par; params) @@ -719,10 +703,6 @@ class ArrayLiteralExpr : Expression // Create the array and get the index arrind = arrays.createConst(data, elem).getIndex; - // Delete the array, if necessary - if(data.length > LEN) - delete data; - // Create an array from the index and return it as the compile // time value return (cast(int*)&arrind)[0..1]; @@ -1001,6 +981,10 @@ abstract class MemberExpression : Expression resolve(sc); } + + // Static members. These can be evaluated without needing the owner + // pushed onto the stack. + bool isStatic() { return false; } } // Expression that handles conversion from one type to another. This @@ -1057,3 +1041,50 @@ class CastExpression : Expression return "cast(" ~ type.toString ~ ")(" ~ orig.toString ~ ")"; } } + +// Used as the surrogate owner expression for imported +// members. Example: +// import x; +// y = 3; // refers to x.y +// y is resolved as x.y, where the owner (x) is an import holder. +class ImportHolder : Expression +{ + MonsterClass mc; + + this(MonsterClass pmc) + { + mc = pmc; + + // Importing singletons and modules is like importing + // the object itself + if(mc.isSingleton) + type = mc.objType; + else + type = mc.classType; + } + + // All lookups in this import is done through this function. Can be + // used to filter lookups later on. + ScopeLookup lookup(Token name) + { + assert(mc !is null); + mc.requireScope(); + return mc.sc.lookup(name); + } + + override: + + void parse(ref TokenArray) { assert(0); } + void resolve(Scope sc) {} + void evalDest() { assert(0); } + char[] toString() { return "imported class " ~ mc.name.str ~ ""; } + + void evalAsm() + { + if(mc.isSingleton) + { + assert(type.isObject); + tasm.pushSingleton(mc.getIndex()); + } + } +} diff --git a/monster/compiler/functions.d b/monster/compiler/functions.d index c1ad6e01a1..9a7842daa6 100644 --- a/monster/compiler/functions.d +++ b/monster/compiler/functions.d @@ -36,6 +36,7 @@ enum FuncType } import monster.compiler.types; +import monster.compiler.operators; import monster.compiler.assembler; import monster.compiler.bytecode; import monster.compiler.scopes; @@ -50,6 +51,7 @@ import monster.vm.idlefunction; import monster.vm.mclass; import monster.vm.error; import monster.vm.fstack; +import monster.vm.thread; import monster.vm.vm; import std.stdio; @@ -90,6 +92,9 @@ struct Function // Is this function final? (can not be overridden in child classes) bool isFinal; + // If true, this function can be executed without an object + bool isStatic; + // What function we override (if any) Function *overrides; @@ -135,8 +140,13 @@ struct Function { assert(obj !is null); - // Cast the object to the correct type for this function. - obj = obj.Cast(owner); + // Make sure there's a thread to use + bool wasNew; + if(cthread is null) + { + wasNew = true; + cthread = Thread.getNew(); + } // Push the function on the stack fstack.push(this, obj); @@ -153,7 +163,7 @@ struct Function natFunc_c(); break; case FuncType.Normal: - obj.thread.execute(); + cthread.execute(); break; case FuncType.Native: fail("Called unimplemented native function " ~ toString); @@ -167,6 +177,19 @@ struct Function // Remove ourselves from the function stack fstack.pop(); + + assert(cthread !is null); + + // Reset cthread, if we created it. + if(wasNew) + { + // If the thread is still in the unused list, we can safely + // kill it. + if(cthread.isUnused) + cthread.kill(); + + cthread = null; + } } // Call without an object. TODO: Only allowed for functions compiled @@ -180,6 +203,8 @@ struct Function call(int_mo); } + // This allows you to compile a function file by writing fn = + // Function("filename"). static Function opCall(char[] file, MonsterClass mc = null) { Function fn; @@ -191,13 +216,35 @@ struct Function // 'mc'. If no class is given, use an empty internal class. void compile(char[] file, MonsterClass mc = null) { - assert(name.str == "", - "Function " ~ name.str ~ " has already been set up"); - // Check if the file exists if(!vm.findFile(file)) fail("File not found: " ~ file); + // Create the stream and pass it on + auto bf = new BufferedFile(file); + compile(file, bf, mc); + delete bf; + } + + void compile(char[] file, Stream str, MonsterClass mc = null) + { + // Get the BOM and tokenize the stream + auto ef = new EndianStream(str); + int bom = ef.readBOM(); + TokenArray tokens = tokenizeStream(file, ef, bom); + compile(file, tokens, mc); + //delete ef; + } + + void compile(char[] file, ref TokenArray tokens, MonsterClass mc = null) + { + assert(name.str == "", + "Function " ~ name.str ~ " has already been set up"); + + // Check if this is a class or a module file first + if(MonsterClass.canParse(tokens)) + fail("Cannot run " ~ file ~ " - it is a class or module."); + // Set mc to an empty class if no class is given if(mc is null) { @@ -213,21 +260,6 @@ struct Function mc = int_mc; } - // TODO: Maybe we should centralize this stuff somewhere. - // Especially since we have to reinvent loading from string or - // other custom streams, and so on. It's easier to put the tools - // for this here than to have them in MonsterClass. Fix this later - // when you see how things turn out. - auto bf = new BufferedFile(file); - auto ef = new EndianStream(bf); - int bom = ef.readBOM(); - TokenArray tokens = tokenizeStream(file, ef, bom); - delete bf; - - // Check if this is a class or a module file first - if(MonsterClass.canParse(tokens)) - fail("Cannot run " ~ file ~ " - it is a class or module."); - auto fd = new FuncDeclaration; // Parse and comile the function fd.parseFile(tokens, this); @@ -679,6 +711,10 @@ class FunctionCallExpr : MemberExpression bool isVararg; + // Used to simulate a member for imported variables + DotOperator dotImport; + bool recurse = true; + static bool canParse(TokenArray toks) { return isNext(toks, TT.Identifier) && isNext(toks, TT.LeftParen); @@ -728,28 +764,36 @@ class FunctionCallExpr : MemberExpression { if(isMember) // Are we called as a member? { - assert(leftScope !is null); - fd = leftScope.findFunc(name.str); - if(fd is null) fail(name.str ~ " is not a member function of " - ~ leftScope.toString, loc); + assert(leftScope !is null); + auto l = leftScope.lookup(name); + fd = l.func; + if(!l.isFunc) + fail(name.str ~ " is not a member function of " + ~ leftScope.toString, loc); } else { assert(leftScope is null); - fd = sc.findFunc(name.str); - if(fd is null) fail("Undefined function "~name.str, name.loc); + auto l = sc.lookupImport(name); + + // For imported functions, we have to do some funky magic + if(l.isImport) + { + assert(l.imphold !is null); + dotImport = new DotOperator(l.imphold, this, loc); + dotImport.resolve(sc); + return; + } + + fd = l.func; + if(!l.isFunc) + fail("Undefined function "~name.str, name.loc); } // Is this an idle function? - if(fd.isIdle) - { - if(!sc.isStateCode) - fail("Idle functions can only be called from state code", - name.loc); - - if(isMember) - fail("Idle functions cannot be called as members", name.loc); - } + if(fd.isIdle && !sc.isStateCode) + fail("Idle functions can only be called from state code", + name.loc); type = fd.type; assert(type !is null); @@ -870,15 +914,20 @@ class FunctionCallExpr : MemberExpression void evalAsm() { + if(dotImport !is null && recurse) + { + recurse = false; + dotImport.evalAsm(); + return; + } + if(!pdone) evalParams(); setLine(); assert(fd.owner !is null); - if(isMember) - tasm.callFarFunc(fd.index, fd.owner.getTreeIndex()); - else if(fd.isIdle) - tasm.callIdle(fd.index, fd.owner.getIndex()); + if(fd.isIdle) + tasm.callIdle(fd.index, fd.owner.getTreeIndex(), isMember); else - tasm.callFunc(fd.index, fd.owner.getTreeIndex()); + tasm.callFunc(fd.index, fd.owner.getTreeIndex(), isMember); } } diff --git a/monster/compiler/operators.d b/monster/compiler/operators.d index 31e80648a4..0257d19536 100644 --- a/monster/compiler/operators.d +++ b/monster/compiler/operators.d @@ -87,10 +87,6 @@ class UnaryOperator : Expression if(opType == TT.PlusPlus || opType == TT.MinusMinus) { - if(exp.isProperty) - fail("Operators ++ and -- not implemented for properties yet", - loc); - if(!type.isIntegral) fail("Operator " ~ tokenList[opType] ~ " not allowed for " ~ exp.toString() ~ " of type " ~ exp.typeString(), loc); @@ -162,26 +158,8 @@ class UnaryOperator : Expression { if(opType == TT.PlusPlus || opType == TT.MinusMinus) { - exp.evalDest(); - - assert(exp.type.isInt || exp.type.isUint || - exp.type.isLong || exp.type.isUlong); - setLine(); - if(postfix) - { - if(opType == TT.PlusPlus) tasm.postInc(exp.type.getSize()); - else tasm.postDec(exp.type.getSize()); - } - else - { - if(opType == TT.PlusPlus) tasm.preInc(exp.type.getSize()); - else tasm.preDec(exp.type.getSize()); - } - - // The expression has been modified. - exp.postWrite(); - + exp.incDec(opType, postfix); return; } @@ -217,7 +195,9 @@ abstract class OperatorExpr : Expression // getList()[14], and array[2..$]; class ArrayOperator : OperatorExpr { - bool isSlice; + bool isSlice; // Set if this is a slice + bool isFill; // Set during assignment if we're filling the array + // with one single value // name[], name[index], name[index..index2] Expression name, index, index2; @@ -242,11 +222,8 @@ class ArrayOperator : OperatorExpr return res ~= "])"; } - // We can ALWAYS assign to elements of an array. There is no such - // thing as a constant array in Monster, only constant array - // references. Exceptions (such as strings in the static data area) - // must be dealt with at runtime. We might moderate this rule later - // though. + // We can ALWAYS assign to elements of an array. Const arrays are + // handled at runtime only at the moment. bool isLValue() { return true; } void resolve(Scope sc) @@ -376,7 +353,7 @@ class ArrayOperator : OperatorExpr setLine(); if(isDest) tasm.elementAddr(); - else tasm.fetchElement(/*type.getSize*/); + else tasm.fetchElement(); assert(!isSlice); } @@ -390,30 +367,53 @@ class ArrayOperator : OperatorExpr setLine(); tasm.makeSlice(); + + assert(isSlice); } } void evalDest() { + assert(isLValue); isDest = true; eval(); } + + void store() + { + evalDest(); + + if(isSlice) + { + // Special case for left hand slices, of the type a[] or + // a[i..j]. In this case we use special commands to fill or + // copy the entire array data. + + if(isFill) + tasm.fillArray(type.getBase().getSize()); + else tasm.copyArray(); + } + else + tasm.mov(type.getSize()); + } } // Handles the DOT operator, eg. someObj.someFunc(); class DotOperator : OperatorExpr { // owner.member - Expression owner, member; + Expression owner; + MemberExpression member; this(Expression own, Expression memb, Floc loc) { - this.owner = own; + owner = own; assert(own !is null, "owner cannot be null"); assert(memb !is null); - this.member = memb; + member = cast(MemberExpression)memb; + assert(member !is null); this.loc = loc; } @@ -431,10 +431,6 @@ class DotOperator : OperatorExpr return member.isLValue; } - // We are a property if the member is a property - bool isProperty() { return member.isProperty; } - void writeProperty() { member.writeProperty(); } - void resolve(Scope sc) { // In order to find the correct scope for the right side, we @@ -451,6 +447,12 @@ class DotOperator : OperatorExpr member.resolveMember(sc, ot); type = member.type; + + // Make sure we only call static members when the owner is a + // type. + if(ot.isMeta && !member.isStatic) + fail("Can only access static members for " ~ owner.toString, + loc); } // TODO: An evalMemberCTime() function could be used here @@ -467,7 +469,17 @@ class DotOperator : OperatorExpr member.evalDest(); } - void postWrite() { member.postWrite(); } + void store() + { + evalCommon(); + member.store(); + } + + void incDec(TT o, bool b) + { + evalCommon(); + member.incDec(o,b); + } void evalCommon() { @@ -491,13 +503,6 @@ class DotOperator : OperatorExpr // Assignment operators, =, +=, *=, /=, %=, ~= class AssignOperator : BinaryOperator { - // Set to true if we are assigning to an array slice, eg. a[1..3] = - // b[] or a[] = 3; This changes some rules, eg array data must be - // copied instead or just their reference, and we are allowed to - // assign a single value to a slice and fill the array that way. - bool isSlice; - bool isFill; // Used for filling elements with one value. - bool catElem; // For concatinations (~=), true when the right hand // side is a single element rather than an array. @@ -527,7 +532,6 @@ class AssignOperator : BinaryOperator if(arr !is null && arr.isSlice) { assert(type.isArray); - isSlice = true; if(opType == TT.CatEq) fail("Cannot use ~= on array slice " ~ left.toString, loc); @@ -555,8 +559,8 @@ class AssignOperator : BinaryOperator ~ " to slice " ~ left.toString ~ " of type " ~ left.typeString, loc); - // Inform eval() what to do - isFill = true; + // Inform arr.store() that we're filling in a single element + arr.isFill = true; return; } @@ -644,43 +648,18 @@ class AssignOperator : BinaryOperator } else right.eval(); - // Special case for properties and other cases where the - // assignment is actually a function call. We don't call - // evalDest or mov, just let writeProperty handle everything. - if(left.isProperty) - { - left.writeProperty(); - return; - } - + // Store the value on the stack into the left expression. + setLine(); + left.store(); + /* left.evalDest(); setLine(); - // Special case for left hand slices, of the type a[] or - // a[i..j]. - if(isSlice) - { - assert(type.isArray); - - if(isFill) - { - // Fill the array with the result of the right-hand - // expression - tasm.fillArray(s); - } - else - // Copy array contents from the right array to the left - // array - tasm.copyArray(); - - return; - } - - tasm.mov(right.type.getSize()); // Move the data // Left hand value has been modified, notify it. left.postWrite(); + */ } } diff --git a/monster/compiler/properties.d b/monster/compiler/properties.d index bf32630da3..d240c2ed6c 100644 --- a/monster/compiler/properties.d +++ b/monster/compiler/properties.d @@ -48,12 +48,8 @@ class NumericProperties(T) : SimplePropertyScope { this() { - super(T.stringof ~ "Properties"); - - // Argh, this fails of course because we're trying to push a - // long value as an int. We can use a static if here to check - // the size, and use different instructions. Eg push8 that takes - // long and double. + super(T.stringof ~ "Properties", + GenericProperties.singleton); // Static properties of all numeric types static if(T.sizeof == 4) @@ -67,8 +63,6 @@ class NumericProperties(T) : SimplePropertyScope inserts("max", T.stringof, { tasm.push8(T.max); }); } else static assert(0); - - nextProp = GenericProperties.singleton; } } @@ -136,7 +130,7 @@ class ArrayProperties: SimplePropertyScope this() { - super("ArrayProperties"); + super("ArrayProperties", GenericProperties.singleton); insert("length", "int", { tasm.getArrayLength(); }, @@ -146,8 +140,6 @@ class ArrayProperties: SimplePropertyScope insert("sort", "owner", { assert(0, "sort not implemented"); }); insert("const", "owner", { tasm.makeArrayConst(); }); insert("isConst", "bool", { tasm.isArrayConst(); }); - - nextProp = GenericProperties.singleton; } } @@ -158,23 +150,11 @@ class ClassProperties : SimplePropertyScope this() { - super("ClassProperties"); + super("ClassProperties", + GenericProperties.singleton); insert("clone", "owner", { tasm.cloneObj(); }); - // For testing purposes. Makes 'singleton' an alias for the - // first variable in the data segment. This might actually not - // be far from how the end result would work - the singleton - // would just be a hidden variable, but in the variable list - // belonging to the class. We don't have to handle it using the - // property system at all, really, we only need to allow the - // special name 'singleton' as a variable. - /* - insert("singleton", "int", - { tasm.pushClass(0, 1); }, - { tasm.pushClassAddr(0); tasm.movRet(); }); - */ - // We should move handling of states here. This will mean // removing StateStatement and making states a propert type. We // can't leave statestatement in as a special syntax for setting @@ -191,8 +171,6 @@ class ClassProperties : SimplePropertyScope // differentiate between near and far properties too. Think more // about it. //insert("state", "int", { tasm.push(6); }); - - nextProp = GenericProperties.singleton; } } @@ -238,7 +216,7 @@ class GenericProperties : SimplePropertyScope abstract class SimplePropertyScope : PropertyScope { - this(char[] n) { super(n); } + this(char[] n, PropertyScope ps = null) { super(n, ps); } private SP[char[]] propList; @@ -277,7 +255,7 @@ abstract class SimplePropertyScope : PropertyScope // Return the stored type. If it is null, return the owner type // instead. - Type getPropType(char[] name, Type oType) + Type getType(char[] name, Type oType) { assert(hasProperty(name)); Type tp = propList[name].type; diff --git a/monster/compiler/scopes.d b/monster/compiler/scopes.d index c46fdf84df..339eafed50 100644 --- a/monster/compiler/scopes.d +++ b/monster/compiler/scopes.d @@ -52,6 +52,111 @@ void initScope() global = new PackageScope(null, "global"); } +// List all identifier types +enum LType + { + None, + Class, + Type, + Variable, + Function, + State, + StateLabel, + LoopLabel, + Property, + Import, + } + +const char[][] LTypeName = + [ + LType.None: "", + LType.Class: "class", + LType.Type: "type", + LType.Variable: "variable", + LType.Function: "function", + LType.State: "state", + LType.StateLabel: "state label", + LType.LoopLabel: "loop label", + LType.Property: "property", + ]; + +struct ScopeLookup +{ + Token name; + LType ltype; + + Scope sc; + Type type; + union + { + MonsterClass mc; + Variable* var; + Function* func; + State* state; + StateLabel *slabel; + ImportHolder imphold; + + void *ptr; + Object ob; + } + + bool isClass() { return ltype == LType.Class; } + bool isType() { return ltype == LType.Type; } + bool isVar() { return ltype == LType.Variable; } + bool isFunc() { return ltype == LType.Function; } + bool isState() { return ltype == LType.State; } + bool isNone() { return ltype == LType.None; } + bool isImport() { return ltype == LType.Import; } + bool isProperty() + { + bool ret = (ltype == LType.Property); + assert( ret == (cast(PropertyScope)sc !is null) ); + return ret; + } + + // For properties only + Type getPropType(Type owner) + { + assert(type is null); + assert(owner !is null); + assert(name.str != ""); + + type = owner; + return ps().getType(name.str, owner); + } + private PropertyScope ps() + { + assert(isProperty); + assert(type !is null); + return cast(PropertyScope)sc; + } + bool isPropLValue() { return ps().isLValue(name.str, type); } + bool isPropStatic() { return ps().isStatic(name.str, type); } + void getPropValue() { ps().getValue(name.str, type); } + void setPropValue() { ps().setValue(name.str, type); } + + static ScopeLookup opCall(Token nm, LType lt, Type tp, Scope sc, Object ob) + { + auto sl = ScopeLookup(nm, lt, tp, sc); + sl.ob = ob; + return sl; + } + + static ScopeLookup opCall(Token nm, LType lt, Type tp, Scope sc, void*p = null) + { + assert(nm.str != ""); + + ScopeLookup sl; + sl.name = nm; + sl.ltype = lt; + sl.type = tp; + sl.sc = sc; + sl.ptr = p; + return sl; + } +} + + // TODO: Write here which of these should be kept around at runtime, // and which of them can be discarded after compilation. @@ -65,16 +170,26 @@ abstract class Scope // global scope. Scope parent; - // Properties assigned to this scope (if any). - PropertyScope nextProp; - // Verify that an identifier is not declared in this scope. If the // identifier is found, give a duplicate identifier compiler // error. Recurses through parent scopes. - void clearId(Token name) + final void clearId(Token name) { - assert(!isRoot()); - parent.clearId(name); + // Lookup checks all parent scopes so we only have to call it + // once. + auto sl = lookup(name); + assert(sl.name.str == name.str); + + if(!sl.isNone) + { + if(sl.isProperty) + fail(name.str ~ " is a property and cannot be redeclared", + name.loc); + + fail(format("%s is already declared (at %s) as a %s", + name.str, name.loc, LTypeName[sl.ltype]), + name.loc); + } } // Made protected since it is so easy to confuse with isStateCode(), @@ -87,6 +202,8 @@ abstract class Scope // mostly used for debugging. char[] scopeName; + ImportHolder importList[]; + public: this(Scope last, char[] name) @@ -95,8 +212,11 @@ abstract class Scope parent = last; assert(last !is this, "scope cannot be it's own parent"); - assert(name != ""); + + // Copy the import list from our parent + if(!isRoot) + importList = parent.importList; } // Is this the root scope? @@ -120,12 +240,6 @@ abstract class Scope // Is this scope allowed to be a root scope (without parent?) bool allowRoot() { return false; } - // Get a property from this scope - void getProperty(Token name, Type ownerType, ref Property p) - { - assert(0); - } - // Get the function definition belonging to this scope. Function *getFunction() { @@ -164,76 +278,73 @@ abstract class Scope LabelStatement getBreak(char[] name = "") { return null; } LabelStatement getContinue(char[] name = "") { return null; } - // Does this scope have a property with the given name? - bool hasProperty(Token name) - { - assert(!isProperty); - return false; - } + final ScopeLookup lookupName(char[] name) + { return lookup(Token(name, Floc.init)); } - final bool findProperty(Token name, Type ownerType, ref Property result) + ScopeLookup lookup(Token name) { - if(hasProperty(name)) - { - getProperty(name, ownerType, result); - return true; - } - - if(nextProp !is null) - return nextProp.findProperty(name, ownerType, result); - - // No property in this scope. Check the parent. - if(!isRoot) return parent.findProperty(name, ownerType, result); - - // No parent, property not found. - return false; + if(isRoot()) return ScopeLookup(name, LType.None, null, null); + else return parent.lookup(name); } - Variable* findVar(char[] name) + // Look up an identifier, and check imported scopes as well. + ScopeLookup lookupImport(Token name) { - if(isRoot()) return null; - return parent.findVar(name); - } + auto l = lookup(name); + if(!l.isNone) return l; - State* findState(char[] name) - { - if(isRoot()) return null; - return parent.findState(name); - } + // Nuttin' was found, try the imports + bool found = false; + auto old = l; + foreach(imp; importList) + { + l = imp.lookup(name); - Function* findFunc(char[] name) - { - if(isRoot()) return null; - return parent.findFunc(name); - } + // Only accept types, classes, variables and functions + if(l.isType || l.isClass || l.isVar || l.isFunc) + { + // Duplicate matches aren't allowed + if(found && l.sc !is old.sc) + fail(format( + "%s matches both %s.%s (at %s) and %s.%s (at %s)", name.str, + old.imphold.mc.name.str, old.name.str, old.name.loc, + imp.mc.name.str, l.name.str, l.name.loc), + name.loc); + + // First match + found = true; + old = l; + old.imphold = imp; + } + } - StructType findStruct(char[] name) - { - if(isRoot()) return null; - return parent.findStruct(name); - } + if(!found) + return ScopeLookup(name, LType.None, null, null); - EnumType findEnum(char[] name) - { - if(isRoot()) return null; - return parent.findEnum(name); + // Tell the caller that this is an import. We override the + // lookup struct, but it doesn't matter since the lookup will + // have to be performed again anyway. + old.ltype = LType.Import; + assert(old.imphold !is null); + return old; } - void insertLabel(StateLabel *lb) - { - assert(!isRoot); - parent.insertLabel(lb); - } + // Add an import to this scope + void registerImport(ImportHolder s) { importList ~= s; } - void insertVar(Variable* dec) { assert(0); } + // More user-friendly version for API-defined + // imports. Eg. global.registerImport(myclass) -> makes myclass + // available in ALL classes. + void registerImport(MonsterClass mc) + { registerImport(new ImportHolder(mc)); } // Used for summing up stack level. Redeclared in StackScope. int getTotLocals() { return 0; } int getLocals() { assert(0); } - // These must be overridden in their respective scopes - int addNewDataVar(int varSize) { assert(0); } - int addNewLocalVar(int varSize) { assert(0); } + // This must be overridden in all scopes that allow variable + // declarations. + int addNewVar(int varSize) { assert(0); } final: @@ -292,21 +403,6 @@ final class StateScope : Scope super(last, st.name.str); } - override: - void clearId(Token name) - { - assert(name.str != ""); - - // Check against labels. We are not allowed to shadow labels at - // any point. - StateLabel *lb; - if(st.labels.inList(name.str, lb)) - fail(format("Identifier '%s' conflicts with label on line %s", name.str, - lb.name.loc), name.loc); - - super.clearId(name); - } - // Insert a label, check for name collisions. void insertLabel(StateLabel *lb) { @@ -316,6 +412,19 @@ final class StateScope : Scope st.labels[lb.name.str] = lb; } + override: + ScopeLookup lookup(Token name) + { + assert(name.str != ""); + + // Check against state labels + StateLabel *lb; + if(st.labels.inList(name.str, lb)) + return ScopeLookup(lb.name, LType.StateLabel, null, this, lb); + + return super.lookup(name); + } + State* getState() { return st; } bool isState() { return true; } @@ -414,13 +523,6 @@ final class PackageScope : Scope bool csInList(char[] name, ref MonsterClass cb) { return ciInList(name, cb) && cb.name.str == name; - //fail(format("Class name mismatch: wanted %s but found %s", - // name, cb.name.str)); - } - bool csInList(char[] name) - { - MonsterClass mc; - return csInList(name, mc); } // Get the class. It must exist and the case must match. getClass @@ -448,18 +550,21 @@ final class PackageScope : Scope return mc; } - override void clearId(Token name) + override ScopeLookup lookup(Token name) { - assert(name.str != ""); - // Type names can never be overwritten, so we check findClass // and the built-in types. We might move the builtin type check // to a "global" scope at some point. - if(BasicType.isBasic(name.str) || csInList(name.str)) - fail("Identifier '"~ name.str~ "' is a type and cannot be redeclared", - name.loc); + if(BasicType.isBasic(name.str)) + return ScopeLookup(name, LType.Type, BasicType.get(name.str), this); + + MonsterClass mc; + if(csInList(name.str, mc)) + return ScopeLookup(mc.name, LType.Class, null, this, mc); + // No parents to check assert(isRoot()); + return super.lookup(name); } // Find a parsed class of the given name. Looks in the list of @@ -549,42 +654,29 @@ abstract class VarScope : Scope this(Scope last, char[] name) { super(last, name); } - override: - - // Find a variable, returns the declaration. - Variable* findVar(char[] name) + // Insert a variable, checks if it already exists. + void insertVar(Variable* dec) { - Variable* vd; + assert(!isStateCode, "called insertVar in state code"); - if(variables.inList(name, vd)) - return vd; + // Check for name collisions. + clearId(dec.name); - return super.findVar(name); + variables[dec.name.str] = dec; } - void clearId(Token name) + override: + + ScopeLookup lookup(Token name) { assert(name.str != ""); Variable *vd; if(variables.inList(name.str, vd)) - fail(format("Identifier '%s' already declared on line %s (as a variable)", - name.str, vd.name.loc), - name.loc); + return ScopeLookup(vd.name, LType.Variable, vd.type, this, vd); - super.clearId(name); + return super.lookup(name); } - - // Insert a variable, checks if it already exists. - void insertVar(Variable* dec) - { - assert(!isStateCode, "called insertVar in state code"); - - // Check for name collisions. - clearId(dec.name); - - variables[dec.name.str] = dec; - } } // A scope that can contain functions and variables @@ -603,15 +695,14 @@ class FVScope : VarScope if(isClass) { // Are we overriding a function? - auto old = findFunc(fd.name.str); - - // If there is no existing function, call clearId - if(old is null) - clearId(fd.name); - else + auto look = lookup(fd.name); + if(look.isFunc) // We're overriding. Let fd know, and let it handle the // details when it resolves. - fd.overrides = old; + fd.overrides = look.func; + else + // No matching function. Check that the name is available. + clearId(fd.name); } else // Non-class functions can never override anything @@ -624,33 +715,18 @@ class FVScope : VarScope } override: - void clearId(Token name) + ScopeLookup lookup(Token name) { assert(name.str != ""); Function* fd; if(functions.inList(name.str, fd)) - { - fail(format("Identifier '%s' already declared on line %s (as a function)", - name.str, fd.name.loc), - name.loc); - } + return ScopeLookup(fd.name, LType.Function, fd.type, this, fd); // Let VarScope handle variables - super.clearId(name); + return super.lookup(name); } - - Function* findFunc(char[] name) - { - Function* fd; - - if(functions.inList(name, fd)) - return fd; - - assert(!isRoot()); - return parent.findFunc(name); - } } // Can contain types, functions and variables. 'Types' means structs, @@ -683,62 +759,33 @@ class TFVScope : FVScope } override: - void clearId(Token name) + ScopeLookup lookup(Token name) { assert(name.str != ""); - StructType fd; + StructType sd; EnumType ed; + Type tp; - if(structs.inList(name.str, fd)) - { - fail(format("Identifier '%s' already declared on line %s (as a struct)", - name.str, fd.loc), - name.loc); - } + if(structs.inList(name.str, sd)) tp = sd; + else if(enums.inList(name.str, ed)) tp = ed; - if(enums.inList(name.str, ed)) - { - fail(format("Identifier '%s' already declared on line %s (as an enum)", - name.str, ed.loc), - name.loc); - } + if(tp !is null) + return ScopeLookup(Token(tp.name, tp.loc), LType.Type, tp, this); - // Let VarScope handle variables - super.clearId(name); + // Pass it on to the parent + return super.lookup(name); } - - StructType findStruct(char[] name) - { - StructType fd; - - if(structs.inList(name, fd)) - return fd; - - assert(!isRoot()); - return parent.findStruct(name); - } - - EnumType findEnum(char[] name) - { - EnumType ed; - - if(enums.inList(name, ed)) - return ed; - - assert(!isRoot()); - return parent.findEnum(name); - } } // Lookup scope for enums. For simplicity we use the property system // to handle enum members. final class EnumScope : SimplePropertyScope { - this() { super("EnumScope"); } + this() { super("EnumScope", GenericProperties.singleton); } - int index; // Index in a global enum index list. Do whatever you do - // with global class indices here. + int index; // Index in a global enum index list. Make a static list + // here or something. void setup() { @@ -772,7 +819,7 @@ final class StructScope : VarScope bool isStruct() { return true; } - int addNewLocalVar(int varSize) + int addNewVar(int varSize) { return (offset+=varSize) - varSize; } // Define it only here since we don't need it anywhere else. @@ -788,7 +835,6 @@ final class ClassScope : TFVScope MonsterClass cls; HashTable!(char[], State*) states; - int dataSize; // Data segment size for this class public: @@ -797,16 +843,13 @@ final class ClassScope : TFVScope { cls = cl; super(last, cls.name.str); - - // Connect a class property scope with this scope. - nextProp = ClassProperties.singleton; } bool isClass() { return true; } MonsterClass getClass() { return cls; } // Add a variable to the data segment, returns the offset. - int addNewDataVar(int varSize) + int addNewVar(int varSize) { int tmp = dataSize; @@ -815,19 +858,22 @@ final class ClassScope : TFVScope return tmp; } - override void clearId(Token name) + override ScopeLookup lookup(Token name) { assert(name.str != ""); State* sd; if(states.inList(name.str, sd)) - fail(format("Identifier '%s' already declared on line %s (as a state)", - name.str, sd.name.loc), - name.loc); + return ScopeLookup(sd.name, LType.State, null, this, sd); + + // Check the property list + auto sl = ClassProperties.singleton.lookup(name); + if(sl.isProperty) + return sl; // Let the parent handle everything else - super.clearId(name); + return super.lookup(name); } // Get total data segment size @@ -841,17 +887,6 @@ final class ClassScope : TFVScope st.index = states.length; states[st.name.str] = st; } - - State* findState(char[] name) - { - State* st; - - if(states.inList(name, st)) - return st; - - assert(!isRoot()); - return parent.findState(name); - } } // A scope that keeps track of the stack @@ -885,7 +920,7 @@ abstract class StackScope : VarScope // Allocate a local variable on the stack, and return the offset. // The parameter gives the size of the requested variable in ints (4 // bytes.) - int addNewLocalVar(int varSize) + int addNewVar(int varSize) { assert(expStack == 0); @@ -935,8 +970,6 @@ class FuncScope : StackScope } override: - void insertLabel(StateLabel *lb) - { assert(0, "cannot insert labels in function scopes"); } bool isFunc() { return true; } Function *getFunction() { return fnc; } @@ -947,7 +980,10 @@ class CodeScope : StackScope this(Scope last, CodeBlock cb) { char[] name = "codeblock"; - if(cb.isState) name = "stateblock"; + + assert(cb !is null); + if(cb.isState) + name = "stateblock"; super(last, name); } @@ -980,11 +1016,11 @@ class ArrayScope : StackScope // scope are defined in properties.d abstract class PropertyScope : Scope { - this(char[] n) { super(null, n); } + this(char[] n, PropertyScope last = null) { super(last, n); } // Override these in base classes. - Type getPropType(char[] name, Type oType); + Type getType(char[] name, Type oType); void getValue(char[] name, Type oType); bool hasProperty(char[] name); bool isStatic(char[] name, Type oType); @@ -996,51 +1032,17 @@ abstract class PropertyScope : Scope override: final: bool isProperty() { return true; } - - // No need for collision checks - void clearId(Token name) - { assert(isRoot()); } - bool allowRoot() { return true; } - void getProperty(Token name, Type ownerType, ref Property p) + ScopeLookup lookup(Token name) { - assert(hasProperty(name)); + // Does this scope contain the property? + if(hasProperty(name.str)) + return ScopeLookup(name, LType.Property, null, this); - p.scp = this; - p.name = name.str; - p.oType = ownerType; + // Check the parent scope + return super.lookup(name); } - - // Check if we have a given property. - bool hasProperty(Token name) { return hasProperty(name.str); } -} - -// A reference to a property, used in VariableExpr. -struct Property -{ - PropertyScope scp; - char[] name; // Name of the property - Type oType; // Type of the owner - - // Get the type of this property - Type getType() { return scp.getPropType(name, oType); } - - // Push the value (of type getType) onto the stack. Assumes the - // owner (of type oType) has already been pushed onto the stack, - // unless the property is static. - void getValue() { scp.getValue(name, oType); } - - // Pops a value (of type getType) off the stack and sets the - // property. Can only be called for lvalues. - void setValue() { assert(isLValue); scp.setValue(name, oType); } - - // Can we write to this property? - bool isLValue() { return scp.isLValue(name, oType); } - - // Is this property static? If it is, we do not have to push the - // owner onto the stack before using the property. - bool isStatic() { return scp.isStatic(name, oType); } } // Scope inside of loops. Handles break and continue, loop labels, and @@ -1108,21 +1110,21 @@ class LoopScope : CodeScope continueLabel = new LabelStatement(getTotLocals()); } - override void clearId(Token name) + override ScopeLookup lookup(Token name) { assert(name.str != ""); - // Check for loop labels as well + // Check for loop labels if(loopName.str == name.str) - fail(format("Identifier %s is a loop label on line %s and cannot be redefined.", - name.str, loopName.loc), - name.loc); + return ScopeLookup(loopName, LType.LoopLabel, null, this); - super.clearId(name); + return super.lookup(name); } // Get the break or continue label for the given named loop, or the - // innermost loop if name is empty or omitted. + // innermost loop if name is empty or omitted. TODO: Might fold + // these into lookup as well. For non-named labels we could use + // __closest_loop__ or something like that internally. LabelStatement getBreak(char[] name = "") { if(name == "" || name == loopName.str) diff --git a/monster/compiler/statement.d b/monster/compiler/statement.d index eb0fc177c3..1aee0d95fc 100644 --- a/monster/compiler/statement.d +++ b/monster/compiler/statement.d @@ -66,6 +66,103 @@ class ExprStatement : Statement void compile() { exp.evalPop(); } } +/* Handles: + import type1, type2, ...; // module scope + import exp1, exp2, ...; // local scope (not implemented yet) + + Also handles: + with(exp1, etc) statement; + which is equivalent to: + { + import exp1, etc; + statement; + } +*/ +class ImportStatement : Statement +{ + Type typeList[]; + + MonsterClass mc; + + bool isLocal; + Statement stm; + + this(bool local = false) { isLocal = local; } + + // TODO: Things like this should be completely unnecessary - we'll + // fix a simpler and more concise parser-system later. + static bool canParse(TokenArray toks) + { return + isNext(toks, TT.Import) || + isNext(toks, TT.With); + } + + void parse(ref TokenArray toks) + { + void getList() + { + typeList = [Type.identify(toks)]; + while(isNext(toks, TT.Comma)) + typeList ~= Type.identify(toks); + } + + if(isNext(toks, TT.Import, loc)) + { + // form: import ImportList; + getList(); + reqNext(toks, TT.Semicolon); + } + else if(isNext(toks, TT.With, loc)) + { + // form: with(ImportList) statement + if(!isLocal) + fail("with(...) not allowed in class scopes", loc); + + reqNext(toks, TT.LeftParen); + getList(); + reqNext(toks, TT.RightParen); + stm = CodeBlock.identify(toks, false); + } + else assert(0); + } + + void resolve(Scope sc) + { + // If a statement is present (ie. if this is a with-statement), + // wrap the imports in a code scope so they become temporary. + if(stm !is null) + sc = new CodeScope(sc, "with-scope"); + + // Add the imports to the scope + foreach(type; typeList) + { + type.resolve(sc); + + if(type.isReplacer) + type = type.getBase(); + + if(!type.isObject) + fail("Can only import from classes", type.loc); + + auto t = cast(ObjectType)type; + assert(t !is null); + mc = t.getClass(type.loc); + + sc.registerImport(new ImportHolder(mc)); + } + + // Resolve the statement if present + if(stm !is null) + stm.resolve(sc); + } + + void compile() + { + if(stm !is null) + stm.compile(); + } +} + // Destination for a goto statement, on the form 'label:'. This // statement is actually a bit complicated, since it must handle jumps // occuring both above and below the label. Seeing as the assembler @@ -157,7 +254,9 @@ class LabelStatement : Statement // Do the magic. Just tell the scope that we exist and let it // handle the rest. - sc.insertLabel(lb); + auto scp = sc.getState().sc; + assert(scp !is null); + scp.insertLabel(lb); stacklevel = sc.getTotLocals(); assert(stacklevel == 0); @@ -750,7 +849,7 @@ class ForeachStatement : Statement index.var.type.toString, index.var.name.loc); } // If not, allocate a stack value for it anyway. - else sc.addNewLocalVar(1); + else sc.addNewVar(1); // Check that the array is in fact an array arrayExp.resolve(sc); @@ -773,7 +872,7 @@ class ForeachStatement : Statement // a local variable. This is not the same as the index variable // above - the iterator index is an internal variable used by // the system for storing the iterator state. - sc.addNewLocalVar(1); + sc.addNewVar(1); // This defines the stack level of the loop internals. Break and // continue labels are set up so that they return to this level @@ -1126,10 +1225,12 @@ class StateStatement : Statement, LabelUser // Check the state name. if(stateName.type == TT.Identifier) { - stt = sc.findState(stateName.str); - if(stt is null) + auto sl = sc.lookup(stateName); + if(!sl.isState) fail("Undefined state " ~ stateName.str, stateName.loc); + stt = sl.state; + // If a label is specified, tell the state that we are // asking for a label. The function will set label for us, // either now or when the label is resolved. The label will @@ -1159,7 +1260,7 @@ class StateStatement : Statement, LabelUser return; } - int cindex = stt.sc.getClass().getIndex(); + int cindex = stt.owner.getTreeIndex(); if(label is null) tasm.setState(stt.index, -1, cindex); @@ -1387,6 +1488,7 @@ class CodeBlock : Statement else if(GotoStatement.canParse(toks)) b = new GotoStatement; else if(ContinueBreakStatement.canParse(toks)) b = new ContinueBreakStatement; else if(ForeachStatement.canParse(toks)) b = new ForeachStatement; + else if(ImportStatement.canParse(toks)) b = new ImportStatement(true); // switch / select // case // assert ? diff --git a/monster/compiler/states.d b/monster/compiler/states.d index 74cacf6c62..9810d722cb 100644 --- a/monster/compiler/states.d +++ b/monster/compiler/states.d @@ -46,6 +46,7 @@ struct State StateLabel* labelList[]; StateScope sc; // Scope for this state + MonsterClass owner; // Class where this state was defined // State declaration - used to resolve forward references. Should // not be kept around when compilation is finished. @@ -159,6 +160,7 @@ class StateDeclaration : Statement // rules, such as allowing idle functions and disallowing // variable declarations. st.sc = new StateScope(last, st); + st.owner = st.sc.getClass(); // Resolve the interior of the code block assert(code !is null); diff --git a/monster/compiler/tokenizer.d b/monster/compiler/tokenizer.d index 2e4bababcd..b798eaee61 100644 --- a/monster/compiler/tokenizer.d +++ b/monster/compiler/tokenizer.d @@ -91,24 +91,17 @@ enum TT // Keywords. Note that we use Class as a separator below, so it // must be first in this list. All operator tokens must occur // before Class, and all keywords must come after Class. - Class, Module, - For, - If, - Else, - Foreach, - ForeachRev, - Do, - While, - Until, + Class, Module, Singleton, + If, Else, + For, Foreach, ForeachRev, + Do, While, Until, + Continue, Break, Typeof, Return, - Continue, - Break, - Switch, - Select, + Switch, Select, State, Struct, Enum, Thread, - Singleton, Clone, Override, Final, Function, + Import, Clone, Override, Final, Function, With, This, New, Static, Const, Out, Ref, Abstract, Idle, Public, Private, Protected, True, False, Native, Null, Goto, Halt, Var, In, @@ -131,6 +124,17 @@ struct Token Floc loc; char[] toString() { return str; } + + static Token opCall(char[] name, Floc loc) + { return Token(TT.Identifier, name, loc); } + static Token opCall(TT tt, char[] name, Floc loc) + { + Token t; + t.type = tt; + t.str = name; + t.loc = loc; + return t; + } } // Used to look up keywords. @@ -221,6 +225,7 @@ const char[][] tokenList = TT.Struct : "struct", TT.Enum : "enum", TT.Thread : "thread", + TT.Import : "import", TT.Typeof : "typeof", TT.Singleton : "singleton", TT.Clone : "clone", @@ -230,6 +235,7 @@ const char[][] tokenList = TT.Override : "override", TT.Final : "final", TT.Function : "function", + TT.With : "with", TT.Idle : "idle", TT.Out : "out", TT.Ref : "ref", @@ -244,6 +250,13 @@ const char[][] tokenList = TT.Halt : "halt", TT.Var : "var", TT.In : "in", + + // These are only used in error messages + TT.StringLiteral : "string literal", + TT.NumberLiteral : "number literal", + TT.CharLiteral : "character literal", + TT.Identifier : "identifier", + TT.EOF : "end of file" ]; class StreamTokenizer diff --git a/monster/compiler/types.d b/monster/compiler/types.d index e4a8ddfbf1..9513193ff0 100644 --- a/monster/compiler/types.d +++ b/monster/compiler/types.d @@ -39,6 +39,7 @@ import monster.vm.mclass; import monster.vm.error; import std.stdio; +import std.utf; import std.string; /* @@ -125,7 +126,8 @@ abstract class Type : Block Type t = null; // Find what kind of type this is and create an instance of the - // corresponding class. + // corresponding class. TODO: Lots of redundant objects created + // here. if(BasicType.canParse(toks)) t = new BasicType(); else if(UserType.canParse(toks)) t = new UserType(); else if(GenericType.canParse(toks)) t = new GenericType(); @@ -156,6 +158,10 @@ abstract class Type : Block return identify(toks, false, exp); } + // Lookup table of all types + static Type[int] typeList; + int tIndex; // Index of this type + // The complete type name including specifiers, eg. "int[]". char[] name; @@ -167,6 +173,12 @@ abstract class Type : Block return meta; } + this() + { + tIndex = typeList.length; + typeList[tIndex] = this; + } + // Used for easy checking bool isInt() { return false; } bool isUint() { return false; } @@ -371,6 +383,18 @@ abstract class Type : Block assert(0, "doCastCTime not implemented for type " ~ toString); } + // Cast variable of this type to string + char[] valToString(int[] data) + { + assert(0, "valToString not implemented for " ~ toString); + } + + final AIndex valToStringIndex(int[] data) + { + assert(data.length == getSize); + return monster.vm.arrays.arrays.create(valToString(data)).getIndex; + } + void parse(ref TokenArray toks) {assert(0, name);} void resolve(Scope sc) {assert(0, name);} @@ -445,7 +469,7 @@ abstract class InternalType : Type final: bool isLegal() { return false; } int[] defaultInit() {assert(0, name);} - int getSize() {assert(0, name);} + int getSize() { return 0; } } // Handles the 'null' literal. This type is only used for @@ -476,6 +500,29 @@ class NullType : InternalType } } +// Helper template for BasicType +abstract class TypeHolderBase +{ + char[] getString(int[] data); +} +class TypeHolder(T) : TypeHolderBase +{ + T toValue(int[] data) + { + static if(!is(T==bool)) + assert(data.length*4 == T.sizeof); + return *(cast(T*)data.ptr); + } + + char[] getString(int [] data) + { + static if(is(T == dchar)) + return toUTF8([toValue(data)]); + else + return .toString(toValue(data)); + } +} + // Handles all the built-in types. These are: int, uint, long, ulong, // float, double, bool, char and the "void" type, which is represented // by an empty string. The void type is only allowed in special cases @@ -500,6 +547,25 @@ class BasicType : Type store[tn] = this; } + TypeHolderBase tph; + TypeHolderBase getHolder() + { + if(tph is null) + { + if(isInt) tph = new TypeHolder!(int); + if(isUint) tph = new TypeHolder!(uint); + if(isLong) tph = new TypeHolder!(long); + if(isUlong) tph = new TypeHolder!(ulong); + if(isFloat) tph = new TypeHolder!(float); + if(isDouble) tph = new TypeHolder!(double); + if(isChar) tph = new TypeHolder!(dchar); + if(isBool) tph = new TypeHolder!(bool); + } + assert(!isVoid); + + return tph; + } + private static BasicType[char[]] store; // Get a basic type of the given name. This will not allocate a new @@ -508,6 +574,7 @@ class BasicType : Type { if(tn in store) return store[tn]; + // Automatically adds itself to the store return new BasicType(tn); } @@ -591,13 +658,6 @@ class BasicType : Type return false; } - bool canCastCTime(Type to) - { - // We haven't implemented compile time string casting yet - if(to.isString) return false; - return canCastTo(to); - } - void evalCastTo(Type to) { assert(this != to); @@ -630,31 +690,38 @@ class BasicType : Type { assert(!isVoid); - if(isIntegral) - tasm.castIntToString(this); - else if(isFloating) - tasm.castFloatToString(this); - else if(isBool) tasm.castBoolToString(); - // Create an array from one element on the stack - else if(isChar) tasm.popToArray(1, 1); - else assert(0, name ~ " not done yet"); + if(isChar) tasm.popToArray(1, 1); + else + tasm.castToString(tIndex); } else fail("Conversion " ~ toString ~ " to " ~ to.toString ~ " not implemented."); } + char[] valToString(int[] data) + { + assert(data.length == getSize); + assert(!isVoid); + + return getHolder().getString(data); + } + int[] doCastCTime(int[] data, Type to) { assert(this != to); assert(!isVoid); + assert(!to.isVoid); + + if(to.isString) + return [valToStringIndex(data)]; int fromSize = getSize(); int toSize = to.getSize(); - bool fromSign = isInt || isLong || isFloat || isBool; assert(data.length == fromSize); + bool fromSign = isInt || isLong || isFloat || isBool; if(to.isInt || to.isUint) { @@ -799,10 +866,18 @@ class ObjectType : Type fail("Cannot use module " ~ name ~ " as a type", loc); } + char[] valToString(int[] data) + { + assert(data.length == 1); + return format("%s#%s", name, data[0]); + } + int getSize() { return 1; } bool isObject() { return true; } int[] defaultInit() { return makeData!(int)(0); } + bool canCastCTime(Type to) { return false; } + bool canCastTo(Type type) { assert(clsIndex != 0); @@ -836,14 +911,11 @@ class ObjectType : Type auto tt = cast(ObjectType)to; assert(tt !is null); assert(clsIndex !is tt.clsIndex); - int cnum = tt.clsIndex; - - tasm.upcast(cnum); return; } assert(to.isString); - tasm.castObjToString(); + tasm.castToString(tIndex); } // Members of objects are resolved in the class scope. @@ -902,6 +974,50 @@ class ArrayType : Type return ArrayProperties.singleton; } + // All arrays can be cast to string + bool canCastTo(Type to) + { return to.isString; } + + void evalCastTo(Type to) + { + assert(to.isString); + tasm.castToString(tIndex); + } + + int[] doCastCTime(int[] data, Type to) + { + assert(to.isString); + + return [valToStringIndex(data)]; + } + + char[] valToString(int[] data) + { + assert(data.length == 1); + + // Get the array reference + ArrayRef *arf = monster.vm.arrays.arrays.getRef(cast(AIndex)data[0]); + + // Empty array? + if(arf.iarr.length == 0) return "[]"; + + assert(arf.elemSize == base.getSize); + assert(arf.iarr.length == arf.length * arf.elemSize); + + if(isString) return format("\"%s\"", arf.carr); + + char[] res = "["; + + // For element of the array, run valToString on the appropriate + // data + for(int i; i= cn.idleData.l; + } +} + +// This implementation uses a user-driven timer instead of the system +// clock. It's more efficient, but requires the user to update the +// given timer manuall each frame. The default sleep (timer.sleep) is +// bound to the default timer, but it's possible to create multiple +// independent timers. +class IdleSleep_Timer : IdleFunction +{ + override: + bool initiate(Thread* cn) + { + // The timer is stored in the object's 'extra' pointer + auto t = cast(SleepManager)cn.extraData.obj; + assert(t !is null); + + // Calculate the return time + cn.idleData.l = t.current + cast(long)(t.tickSize*stack.popFloat); + + // Schedule us + return true; + } + + bool hasFinished(Thread* cn) + { + // Get the timer + auto t = cast(SleepManager)cn.extraData.obj; + assert(t !is null); + + // Is it time? + return t.current >= cn.idleData.l; + } +} + +// A manually updated timer. This can be improved quite a lot: Most +// sleep operations (depending on application of course) will skip +// many frames before they return. For example, for sleep(0.5) at 100 +// fps, hasFinished will return false approximately 50 times before +// returning true. For bigger sleep values and a large number of +// objects, the impact of this is significant. A good solution would +// be to pool scheduled objects together and only perform one check on +// the entire pool. If the pool is due, all the nodes within it are +// inserted into the scheduler for detailed checking. We could have a +// series of such pools, ordered by expiration time, so that we only +// ever need to check the first pool in the list. The optimal pool +// interval, number of pools etc depends on the application and the +// fps - but it should be possible to find some reasonable defaults. A +// more generalized priority queue implementation is also possible. +class SleepManager +{ + private: + // Instance of the timer class that is associated with this timer + MonsterObject *tobj; + + // Current tick count + long current; + + public: + + // Specify a Monster object to associate with this timer. Use 'null' + // if you don't need an object. + this(MonsterObject *obj) + { + if(obj is null) return; + + tobj = obj; + tobj.getExtra(_timerClass).obj = this; + } + + // By default, create a new object + this() + { this(_timerClass.createObject); } + + // Number of 'ticks' per second + static const long tickSize = 1000; + + // Reset the timer to zero + void reset() { current = 0; } + + // Return the total number of elapsed seconds since start (or last + // reset) + double read() { return current/cast(double)tickSize; } + + // Add time to the timer. + void add(double d) { current += cast(long)(tickSize*d); } + void addl(long l) { current += l; } + + MonsterObject *getObj() { return tobj; } +} + +SleepManager idleTime; + +MonsterClass _timerClass; + +void initTimerModule() +{ + if(_timerClass !is null) + { + assert(idleTime !is null); + return; + } + + _timerClass = new MonsterClass(MC.String, moduleDef, "timer"); + + assert(idleTime is null); + idleTime = new SleepManager(_timerClass.getSing()); + + setSleepMethod(SleepMethod.Clock); +} + +enum SleepMethod + { Clock, Timer } + +void setSleepMethod(SleepMethod meth) +{ + initTimerModule(); + + if(meth == SleepMethod.Clock) + _timerClass.bind("sleep", new IdleSleep_SystemClock); + else if(meth == SleepMethod.Timer) + _timerClass.bind("sleep", new IdleSleep_Timer); + else assert(0, "unknown timer method"); +} diff --git a/monster/modules/timer.mn b/monster/modules/timer.mn new file mode 100644 index 0000000000..f613e08838 --- /dev/null +++ b/monster/modules/timer.mn @@ -0,0 +1,7 @@ +/* + NOTE: This file is not used - it is here just for reference. The + real module is defined internally in timer.d. + */ + +singleton timer; +idle sleep(float secs); diff --git a/monster/monster.d b/monster/monster.d index 1e0d7a39fd..f69e98ac71 100644 --- a/monster/monster.d +++ b/monster/monster.d @@ -27,15 +27,17 @@ module monster.monster; public { // These should contain all you need for normal usage. - import monster.vm.mclass; import monster.vm.mobject; + import monster.vm.mclass; import monster.vm.stack; import monster.vm.vm; - import monster.vm.scheduler; + import monster.vm.thread; import monster.vm.idlefunction; import monster.vm.arrays; import monster.vm.params; import monster.vm.error; + + import monster.modules.all; } private import monster.compiler.tokenizer; @@ -54,6 +56,6 @@ static this() // Initialize VM scheduler.init(); - stack.init(); + initStack(); arrays.initialize(); } diff --git a/monster/util/freelist.d b/monster/util/freelist.d index 226d8afe2e..10cacff9ff 100644 --- a/monster/util/freelist.d +++ b/monster/util/freelist.d @@ -24,10 +24,269 @@ module monster.util.freelist; -// TODO: Create unittests - import monster.util.list; import monster.util.growarray; +import std.c.stdlib : malloc, free; + +// A freelist for buffers of a given size. The 'size' template +// parameter gives the total requested size of the entire struct, +// including overhead. +struct BufferList(int size) +{ + // Calculate the 'overhead' size of the structs + alias void* vp; + static const junk = 2*vp.sizeof + int.sizeof; + + static union BuffData(int size) + { + static assert(size >= 2, "size must be at least 2"); + + int[size/int.sizeof] ints; + ubyte[size] bytes; + } + + // Get the data sizes + static const bytes = size - junk; + static const ints = bytes / int.sizeof; + + static assert(bytes > 0, "size is too small"); + + alias BuffData!(bytes) Value; + alias Value* ValuePtr; + static assert(Value.sizeof == bytes); + + alias LinkedList!(Value, NoAlloc) List; + alias List.Node LNode; + + static struct BufferNode(int size) + { + LNode data; + int index; + } + + alias BufferNode!(bytes) Node; + alias Node* NodePtr; + static assert(Node.sizeof == size); + + private: + + // This is the array that does all the actual allocations. It is + // used for quickly looking up indices. GrowArrays are designed to + // grow dynamically without reallocation, while still being easily + // indexed like a normal array. + static GrowArray!(Node) array; + + // The freelist. This is shared between all template instances of + // the same size. + static List freeList; + + // The nodes belonging to THIS list instance + List nodes; + + // Get a new node (move from freelist to node list) + ValuePtr getNew() + { + // Is the freelist empty? + if(freeList.length == 0) + { + // Create a bunch of nodes and shove them into the freelist. + const makeSize = 50; + + // Grow the growarray + uint len = array.length; + array.length = len + makeSize; + + // Loop through the new nodes, number them, and insert them + // into freeList + for(int i=0; i < makeSize; i++) + { + NodePtr fn = array.getPtr(i+len); + fn.index = i + len; + freeList.insertNode(&fn.data); + } + } + + // Move the first element from the freelist into the node list. + auto node = freeList.getHead; + freeList.removeNode(node); + nodes.insertNodeFirst(node); + + // Return the value pointer. Since the value is always at the + // begining of the Node struct, this is the same + // pointer. + return &node.value; + } + + // Move a node back to the freelist ("delete" it) + void remove(ValuePtr node) + { + nodes.removeNode(node); + freeList.insertNodeFirst(node); + } + + public: + + // Get the node corresponding to an index + static ValuePtr getNode(int index) + { + return &array.getPtr(index).data.value; + } + + // Get the index from a node + static int getIndex(ValuePtr node) + { + return ( cast(Node*)node ).index; + } + + uint length() { return nodes.length; } + static uint totLength() { return array.length; } + + // Move the given node to another list + ValuePtr moveTo(ref BufferList fl, ValuePtr node) + { + nodes.removeNode(node); + fl.nodes.insertNodeFirst(node); + return node; + } + + // Get the first element in the list + ValuePtr getHead() { return &nodes.getHead().value; } + + // Loop through the structs in this list + int opApply(int delegate(ref Value) dg) + { + return nodes.opApply(dg); + } + + int[] getInt(int isize) + { + assert(isize <= ints); + + return getNew().ints[0..isize]; + } + + void freeInt(int[] buf) + { + assert(buf.length <= ints); + remove(cast(ValuePtr)buf.ptr); + } + + void* get() { return getNew(); } + void free(void* p) { remove(cast(ValuePtr)p); } +} + +import std.stdio; + +struct Buffers +{ + static: + BufferList!(64) b64; + BufferList!(128) b128; + BufferList!(256) b256; + BufferList!(768) b768; + + /* + static this() + { + writefln("64: ints=%s bytes=%s", b64.ints, b64.bytes); + writefln("128: ints=%s bytes=%s", b128.ints, b128.bytes); + writefln("256: ints=%s bytes=%s", b256.ints, b256.bytes); + writefln("768: ints=%s bytes=%s", b768.ints, b768.bytes); + } + */ + + int[] getInt(uint size) + { + if(size <= b64.ints) return b64.getInt(size); + else if(size <= b128.ints) return b128.getInt(size); + else if(size <= b256.ints) return b256.getInt(size); + else if(size <= b768.ints) return b768.getInt(size); + // Too large for our lists - just use malloc + else + { + writefln("WARNING: using malloc for %s ints (%s bytes)", + size, size*int.sizeof); + return ( cast(int*)malloc(size*int.sizeof) )[0..size]; + } + } + + void free(int[] buf) + { + uint size = buf.length; + if(size <= b64.ints) b64.freeInt(buf); + else if(size <= b128.ints) b128.freeInt(buf); + else if(size <= b256.ints) b256.freeInt(buf); + else if(size <= b768.ints) b768.freeInt(buf); + else .free(buf.ptr); + } +} + +/* THIS DOESN'T WORK - because DMD is still stubborn with those + template forwarding issues. Instead we'll just reuse the old + freelist implementation below. + +// A list that uses a freelist for allocation. It is built on top of +// BufferList. +struct FreeList(T) +{ + private: + // For small sizes, pool together with existing lists. + static if(T.sizeof <= 64) static const size = 64; + else static if(T.sizeof <= 128) static const size = 128; + else static if(T.sizeof <= 256) static const size = 256; + // Just use the actual size, rounded up to the nearest 16 + else static if(T.sizeof % 16 == 0) + const size = T.sizeof; + else + const size = T.sizeof + 16 - (T.sizeof%16); + + alias BufferList!(size) BuffList; + BuffList buffer; + + alias BuffList.Value Value; + alias BuffList.ValuePtr ValuePtr; + + static assert(T.sizeof <= BuffList.bytes); + + public: + + // Get a new node (move from freelist to node list) + T* getNew() + { return cast(T*) buffer.get(); } + + // Move a node back to the freelist ("delete" it) + void remove(T* node) + { buffer.free(node); } + + // Get the node corresponding to an index + static T* getNode(int index) + { return cast(T*) buffer.getNode(index); } + + // Get the index from a node + static int getIndex(T *node) + { return buffer.getIndex(cast(ValuePtr)node); } + + uint length() { return buffer.length(); } + static uint totLength() { return buffer.totLength(); } + + // Move the given node to another list + T* moveTo(ref FreeList fl, T* node) + { + auto vp = cast(ValuePtr) node; + return cast(T*) buffer.moveTo(fl.buffer, vp); + } + + // Get the first element in the list + T* getHead() { return cast(T*) buffer.getHead(); } + + // Loop through the structs in this list + int opApply(int delegate(ref T) dg) + { + auto dgc = cast(int delegate(ref Value)) dg; + return nodes.opApply(dgc); + } +} +*/ // This had to be moved outside FreeList to work around some // irritating DMD template problems. (Can you say Aaargh!) @@ -47,13 +306,6 @@ struct FreeList(T) private: - /* - static struct _FreeNode - { - TNode data; - int index; - } - */ alias __FreeNode!(T) _FreeNode; // This is the array that does all the actual allocations. It is diff --git a/monster/util/list.d b/monster/util/list.d index 0e0e9ae009..a9cd393f2b 100644 --- a/monster/util/list.d +++ b/monster/util/list.d @@ -27,8 +27,7 @@ module monster.util.list; // Set this to enable some more extensive list checks. These will loop // through the entire list on every insert and remove, so they are // very slow for large lists. But they are very handy bug catchers -// when doing a little dirty list hacking, and they have saved me in -// the past. +// when doing a little dirty list hacking. // debug=slowcheck; @@ -583,6 +582,10 @@ struct LinkedList(Value, alias Alloc = GCAlloc) } } +alias LinkedList!(void*, NoAlloc) PointerList; +alias PointerList.Node vpNode; +alias PointerList.Iterator vpIter; + /* This test is NOT very complete */ unittest { diff --git a/monster/vm/fstack.d b/monster/vm/fstack.d index db38ad47b4..681e29ad78 100644 --- a/monster/vm/fstack.d +++ b/monster/vm/fstack.d @@ -39,7 +39,8 @@ enum SPType NConst, // Native constructor // The idle function callbacks are split because they handle the - // stack differently. + // stack differently. We probably don't need to have one type for + // each though. Idle_Initiate, // IdleFunction.initiate() Idle_Reentry, // IdleFunction.reentry() Idle_Abort, // IdleFunction.abort() @@ -65,6 +66,11 @@ struct StackPoint int afterStack; // Where the stack should be when this function // returns int *frame; // Stack frame, stored when entering the function + + bool isStatic() + { + return (ftype == SPType.Function) && func.isStatic; + } } FunctionStack fstack; @@ -129,12 +135,15 @@ struct FunctionStack // Set the stack point up as a state void push(State *st, MonsterObject *obj) { + assert(st !is null); + push(obj); cur.ftype = SPType.State; cur.state = st; assert(obj !is null); - cur.cls = obj.cls; + assert(st.owner.parentOf(obj.cls)); + cur.cls = st.owner; // Set up the byte code cur.code.setData(st.bcode, st.lines); @@ -156,11 +165,13 @@ struct FunctionStack push(obj); cur.func = fn; + cur.cls = fn.owner; assert(fn.isIdle, fn.name.str ~ "() is not an idle function"); cur.ftype = tp; } - // These are used for the various idle callbacks + // These are used for the various idle callbacks. TODO: Probably + // overkill to have one for each, but leave it until you're sure. void pushIdleInit(Function *fn, MonsterObject *obj) { pushIdleCommon(fn, obj, SPType.Idle_Initiate); } diff --git a/monster/vm/gc.d b/monster/vm/gc.d new file mode 100644 index 0000000000..8c0429fc41 --- /dev/null +++ b/monster/vm/gc.d @@ -0,0 +1,26 @@ +/* + Monster - an advanced game scripting language + Copyright (C) 2007, 2008 Nicolay Korslund + Email: + WWW: http://monster.snaptoad.com/ + + This file (gc.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/ . + + */ + +// This file will eventually contain the garbage collector. +module monster.vm.gc; diff --git a/monster/vm/idlefunction.d b/monster/vm/idlefunction.d index c9ee300398..aa80227ad1 100644 --- a/monster/vm/idlefunction.d +++ b/monster/vm/idlefunction.d @@ -24,15 +24,15 @@ module monster.vm.idlefunction; -import monster.vm.mobject; +import monster.vm.thread; // A callback class for idle functions. A child object of this class // is what you "bind" to idle functions (rather than just a delegate, // like for native functions.) Note that instances are not bound to -// specific script objects; one idle function instance may be called -// for many objects simultaneously. Any data specific to the monster -// object (such as parameters) must be stored elsewhere, usually -// through the 'extra' pointer in MonsterObject. +// specific script objects or threads; one idle function instance may +// be called for many objects / threads simultaneously. Any data +// specific to this call (such as parameters) must be stored +// elsewhere, usually within the Thread. abstract class IdleFunction { // This is called immediately after the idle function is "called" @@ -40,21 +40,21 @@ abstract class IdleFunction // from the stack), but otherwise does not have to do // anything. Return true if the scheduler should put this idle // function into the condition list, which is usually a good - // idea. For functions which never "return", or event driven idle - // functions (which handle their own scheduling), we should return - // false. - bool initiate(MonsterObject*) { return true; } + // idea. For functions which never "return", and for event driven + // idle functions (which handle their own scheduling), you should + // return false. + bool initiate(Thread*) { return true; } // This is called whenever the idle function is about to "return" to // state code. It has to push the return value, if any, but // otherwise it can be empty. Note that if the idle function is - // aborted (eg. state is changed), this function is never called, - // and abort() is called instead. - void reentry(MonsterObject*) {} + // aborted (eg. the state is changed), this function is never + // called, and abort() is called instead. + void reentry(Thread*) {} // Called whenever an idle function is aborted, for example by a // state change. No action is usually required. - void abort(MonsterObject*) {} + void abort(Thread*) {} // The condition that determines if this function has finished. This // is the main method by which the scheduler determines when to @@ -65,5 +65,5 @@ abstract class IdleFunction // should return false in initiate and instead reschedule the object // manually when the event occurs. (A nice interface for this has // not been created yet, though.) - abstract bool hasFinished(MonsterObject*); + abstract bool hasFinished(Thread*); } diff --git a/monster/vm/iterators.d b/monster/vm/iterators.d index 493c785ffd..344747ce93 100644 --- a/monster/vm/iterators.d +++ b/monster/vm/iterators.d @@ -58,6 +58,7 @@ struct IteratorRef bool isClass; MonsterObject *mo; + MonsterClass mc; // Array iterators bool firstArray(bool irev, bool iref, int *stk) @@ -112,6 +113,7 @@ struct IteratorRef stk[1] = cast(int) getIndex(); mo = mc.getFirst(); + this.mc = mc; // Are there any objects? if(mo == null) return false; @@ -136,7 +138,7 @@ struct IteratorRef // Handle class iterations seperately if(isClass) { - mo = mo.getNext(); + mo = mc.getNext(mo); if(mo == null) return false; *sindex = cast(int)mo.getIndex(); diff --git a/monster/vm/mclass.d b/monster/vm/mclass.d index d5e8f8c63f..4c20cdfd98 100644 --- a/monster/vm/mclass.d +++ b/monster/vm/mclass.d @@ -34,9 +34,7 @@ import monster.compiler.structs; import monster.compiler.block; import monster.compiler.enums; -import monster.vm.thread; import monster.vm.codestream; -import monster.vm.scheduler; import monster.vm.idlefunction; import monster.vm.fstack; import monster.vm.arrays; @@ -45,38 +43,27 @@ import monster.vm.vm; import monster.vm.mobject; import monster.util.flags; -import monster.util.freelist; import monster.util.string; +import monster.util.list; +import monster.util.freelist; import std.string; import std.stdio; import std.file; import std.stream; -// TODO: Needed to fix DMD/GDC template problems. Remove if this bug -// is fixed. -import monster.util.list; -alias _lstNode!(CodeThread) _tmp1; -alias __FreeNode!(CodeThread) _tmp2; - -alias _lstNode!(MonsterObject) _tmp3; -alias __FreeNode!(MonsterObject) _tmp4; - typedef void *MClass; // Pointer to C++ equivalent of MonsterClass. typedef int CIndex; -alias FreeList!(CodeThread) ThreadList; - -// Parameter to the constructor. Decides how the class is created. +// Parameter to the constructor. Decides how the class is +// created. TODO: This system will be removed again, because we'll +// stop using constructors. enum MC { - None = 0, // Initial value - File = 1, // Load class from file (default) - NoCase = 2, // Load class from file, case insensitive name match - String = 3, // Load class from string - Stream = 4, // Load class from stream - Manual = 5, // Manually create class + File, // Load class from file (default) + NoCase, // Load class from file, case insensitive name match + String, // Load class from string } enum CFlags @@ -116,9 +103,12 @@ final class MonsterClass { return Block.isNext(tokens, TT.Class) || + Block.isNext(tokens, TT.Singleton) || Block.isNext(tokens, TT.Module); } + static uint getTotalObjects() { return allObjects.length; } + final: /******************************************************* @@ -127,11 +117,6 @@ final class MonsterClass * * *******************************************************/ - alias FreeList!(MonsterObject) ObjectList; - - // TODO: Put as many of these as possible in the private - // section. Ie. move all of them and see what errors you get. - // Index within the parent tree. This might become a list at some // point. int treeIndex; @@ -146,8 +131,15 @@ final class MonsterClass Type classType; // Type for class references to this class (not // implemented yet) + private: + // List of objects of this class. Includes objects of all subclasses + // as well. + PointerList objects; + Flags!(CFlags) flags; + public: + bool isParsed() { return flags.has(CFlags.Parsed); } bool isScoped() { return flags.has(CFlags.Scoped); } bool isResolved() { return flags.has(CFlags.Resolved); } @@ -183,19 +175,12 @@ final class MonsterClass * * *******************************************************/ - // By default we leave the loadType at None. This leaves us open to - // define the class later. Calling eg. setName will define the class - // as a manual class. This isn't supported yet though. this() {} this(MC type, char[] name1, char[] name2 = "", bool usePath = true) { - loadType = type; - if(type == MC.File || type == MC.NoCase) { - loadType = MC.File; - if(type == MC.NoCase) loadCI(name1, name2, usePath); else @@ -211,25 +196,18 @@ final class MonsterClass return; } - if(type == MC.Manual) - { - assert(name2 == "", "MC.Manual only takes one parameter"); - setName(name1); - return; - } - assert(0, "encountered unknown MC type"); } this(MC type, Stream str, char[] nam = "") { - assert(type == MC.Stream); - loadType = type; - loadStream(str, nam); } + this(ref TokenArray toks, char[] nam="") + { loadTokens(toks, nam); } + this(Stream str, char[] nam="") - { this(MC.Stream, str, nam); } + { loadStream(str, nam); } this(char[] nam1, char[] nam2 = "", bool usePath=true) { this(MC.File, nam1, nam2, usePath); } @@ -265,6 +243,9 @@ final class MonsterClass void load(Stream str, char[] fname="(stream)") { loadStream(str, fname); } + void loadTokens(ref TokenArray toks, char[] name) + { parse(toks, name); } + void loadStream(Stream str, char[] fname="(stream)", int bom = -1) { assert(str !is null); @@ -326,11 +307,13 @@ final class MonsterClass requireScope(); // Get the function from the scope - auto fn = sc.findFunc(name); + auto ln = sc.lookupName(name); - if(fn is null) + if(!ln.isFunc) fail("Function '" ~ name ~ "' not found."); + auto fn = ln.func; + if(!fn.isNormal && !fn.isNative) { // Being here is always bad. Now we just need to find @@ -388,11 +371,13 @@ final class MonsterClass { requireScope(); - Variable *vb = sc.findVar(name); + auto ln = sc.lookupName(name); - if(vb is null) + if(!ln.isVar) fail("Variable " ~ name ~ " not found"); + Variable *vb = ln.var; + assert(vb.vtype == VarType.Class); return vb; @@ -409,11 +394,12 @@ final class MonsterClass { requireScope(); - State *st = sc.findState(name); - - if(st is null) + auto ln = sc.lookupName(name); + if(!ln.isState) fail("State " ~ name ~ " not found"); + State *st = ln.state; + return st; } @@ -467,13 +453,25 @@ final class MonsterClass // Loop through all objects of this type int opApply(int delegate(ref MonsterObject v) del) - { return objects.opApply(del); } + { + int dg(ref void *vp) + { + auto mop = cast(MonsterObject*)vp; + return del(*mop); + } + return objects.opApply(&dg); + } - // Get the first object in the 'objects' list. Used for - // iterator-like looping through objects, together with getNext in - // MonsterObject. Returns null if no objects exist. + // Get the first object in the list for this class MonsterObject* getFirst() - { return objects.getHead(); } + { return cast(MonsterObject*)objects.getHead().value; } + + MonsterObject* getNext(MonsterObject *ob) + { + auto iter = (*getListPtr(ob, treeIndex)).getNext(); + if(iter is null) return null; + return cast(MonsterObject*)iter.value; + } // Get the singleton object MonsterObject* getSing() @@ -485,106 +483,119 @@ final class MonsterClass return singObj; } - // Create a new object, and assign a thread to it. MonsterObject* createObject() + { return createClone(null); } + + // Get the whole allocated buffer belonging to this object + private int[] getDataBlock(MonsterObject *obj) { - requireCompile(); + assert(obj !is null); + assert(obj.cls is this); + return (cast(int*)obj.data.ptr)[0..totalData.length]; + } - if(isAbstract) - fail("Cannot create objects from abstract class " ~ name.str); + private vpIter getListPtr(MonsterObject *obj, int i) + { + auto ep = cast(ExtraData*) &obj.data[i][$-MonsterObject.exSize]; + return &ep.node; + } + + // Create a new object based on an existing object + MonsterObject* createClone(MonsterObject *source) + { + requireCompile(); if(isModule && singObj !is null) fail("Cannot create instances of module " ~ name.str); - // Create the thread - CodeThread *trd = threads.getNew(); + MonsterObject *obj = allObjects.getNew(); - // Create an object tree equivalent of the class tree - MonsterObject* otree[]; - otree.length = tree.length; - - assert(otree.length > 0); + obj.state = null; + obj.cls = this; - // Create one buffer big enough for all the data segments here, - // and let getObject slice it. TODO: This can be optimized even - // further, by using a freelist or other preallocation, and by - // precalculating the result and the slicing. Not important at - // the moment. - int[] totalData = new int[totalDataSize]; + // Allocate the object data segment from a freelist + int[] odata = Buffers.getInt(totalData.length); - // Fill the list with objects, and assign the thread. - foreach(i, ref obj; otree) + // Copy the data, either from the class (in case of new objects) + // or from the source (when cloning.) + if(source !is null) { - obj = tree[i].getObject(totalData); - obj.thread = trd; - } - - // Make sure we used the entire buffer - assert(totalData.length == 0); - - // Pick out the top object - MonsterObject* top = otree[$-1]; - - assert(tree[$-1] is this); - assert(top !is null); + assert(!isAbstract); + assert(source.cls is this); - // Initialize the thread - trd.initialize(top); + assert(source.data.length == tree.length); - // For each object we assign a slice of the object list. TODO: - // In the future it's likely that these lists might have - // different contents from each other (eg. in the case of - // multiple inheritance), and simple slices will not be good - // enough. This is the main reason why we give each object its - // own tree, instead of using one shared list in the thread. - foreach(i, ref obj; otree) - obj.tree = otree[0..i+1]; - - assert(top.tree == otree); - - return top; - } - - // Create a new object based on an existing object - MonsterObject* createClone(MonsterObject *source) - { - requireCompile(); + // Copy data from the object + odata[] = getDataBlock(source); + } + else + { + if(isAbstract) + fail("Cannot create objects from abstract class " ~ name.str); - assert(source.tree.length == tree.length); - assert(source.thread.topObj == source, - "createClone can only clone the topmost object"); + // Copy init values from the class + odata[] = totalData[]; + } - // Create a new thread - CodeThread *trd = threads.getNew(); + // Use this to get subslices of the data segment + int[] slice = odata; + int[] get(int ints) + { + assert(ints <= slice.length); + int[] res = slice[0..ints]; + slice = slice[ints..$]; + return res; + } - // Create one buffer big enough for all the data segments here, - // and let getClone slice it. - int[] totalData = new int[totalDataSize]; + // The beginning of the block is used for the int data[][] + // array. + obj.data = cast(int[][]) get(iasize*tree.length); - // Loop through the objects in the source tree, and clone each - // of them - MonsterObject* otree[] = source.tree.dup; - foreach(i, ref obj; otree) + // Set up the a slice for the data segment of each class + foreach(i, c; tree) { - obj = obj.cls.getClone(obj, totalData); - obj.tree = otree[0..i+1]; - obj.thread = trd; + // Just get the slice - the actual data is already set up. + obj.data[i] = get(c.data.length + MonsterObject.exSize); + + // Insert ourselves into the per-class list. We've already + // allocated size for a node, we just have to add it to the + // list. + auto node = getListPtr(obj, i); + node.value = obj; // Store the object pointer + c.objects.insertNode(node); } - // Make sure we used the entire buffer - assert(totalData.length == 0); + // At this point we should have used up the entire slice + assert(slice.length == 0); + + // Call constructors + foreach(c; tree) + { + // Custom native constructor + if(c.constType != FuncType.Native) + { + fstack.pushNConst(obj); + if(c.constType == FuncType.NativeDDel) + c.dg_const(); + else if(c.constType == FuncType.NativeDFunc) + c.fn_const(); + else if(c.constType == FuncType.NativeCFunc) + c.c_const(); + fstack.pop(); + } - // Pick out the top object - MonsterObject* top = otree[$-1]; - assert(top !is null); + // TODO: Call script-constructor here + } - // Initialize the thread - trd.initialize(top); + // Set the same state as the source + if(source !is null) + obj.setState(source.state, null); - // Set the same state - trd.setState(source.thread.getState(), null); + // Make sure that getDataBlock works + assert(getDataBlock(obj).ptr == odata.ptr && + getDataBlock(obj).length == odata.length); - return top; + return obj; } // Free an object and its thread @@ -595,18 +606,28 @@ final class MonsterClass if(isModule) fail("Cannot delete instances of module " ~ name.str); - // Get the head object - obj = obj.thread.topObj; - // Shut down any active code in the thread - obj.thread.setState(null, null); + obj.clearState(); + + // clearState should also clear the thread + assert(obj.sthread is null); - // Destruct the objects in reverse order - foreach_reverse(ob; obj.thread.topObj.tree) - ob.cls.returnObject(ob); + // This effectively marks the object as dead + obj.cls = null; - // Put the thread back into the free list - threads.remove(obj.thread); + foreach_reverse(i, c; tree) + { + // TODO: Call destructors here + + // Remove from class list + c.objects.removeNode(getListPtr(obj,i)); + } + + // Return it to the freelist + allObjects.remove(obj); + + // Return the data segment + Buffers.free(getDataBlock(obj)); } @@ -616,23 +637,6 @@ final class MonsterClass * * *******************************************************/ - /* For Manual classes. These are just ideas, not implemented yet - void addNative(char[] name, dg_callback dg) {} - void addNative(char[] name, fn_callback fn) {} - - // Not for manual classes, but intended for reloading a changed - // file. It will replace the current class in the scope with a new - // one - and all new objects created will be of the new type - // (requires some work on vm.d and scope.d to get this to work). Old - // objects keep the old class. An alternative is to convert the old - // objects to the new class in some way, if possible. - void reload() {} - */ - - // Will set the name of the class. Can only be called on manual - // classes, and only once. Not implemented yet. - void setName(char[] name) {assert(0);} - // Check if this class is a child of cls. bool childOf(MonsterClass cls) { @@ -651,16 +655,37 @@ final class MonsterClass bool parentOf(MonsterObject *obj) { return obj.cls.childOf(this); } + // Get the tree-index of a given parent class + int upcast(MonsterClass mc) + { + requireScope(); + + int ind = mc.treeIndex; + if(ind < tree.length && tree[ind] is mc) + return ind; + + fail("Cannot upcast " ~ toString ~ " to " ~ mc.toString); + } + + // Get the given class from a tree index + MonsterClass upcast(int ind) + { + requireScope(); + + if(ind < tree.length) return tree[ind]; + + fail("Cannot upcast " ~toString ~ " to index " ~ .toString(ind)); + } + // Get the global index of this class CIndex getIndex() { requireScope(); return gIndex; } int getTreeIndex() { requireScope(); return treeIndex; } char[] getName() { assert(name.str != ""); return name.str; } char[] toString() { return getName(); } - + uint numObjects() { return objects.length; } private: - /******************************************************* * * * Private variables * @@ -687,18 +712,13 @@ final class MonsterClass // overrided functions have been replaced by their successors. Function*[][] virtuals; - // The freelists used for allocation of objects and threads. - ObjectList objects; - ThreadList threads; - int[] data; // Contains the initial object data segment int[] sdata; // Static data segment - // Size of the data segment - uint dataSize; - - // Total for this class + all base classes. - uint totalDataSize; + // The total data segment that's assigned to each object. It + // includes the data segment of all parent objects and some + // additional internal data. + int[] totalData; // Direct parents of this class MonsterClass parents[]; @@ -710,9 +730,7 @@ final class MonsterClass StateDeclaration[] statedecs; StructDeclaration[] structdecs; EnumDeclaration[] enumdecs; - - // Current stage of the loading process - MC loadType = MC.None; + ImportStatement[] imports; // Native constructor type. Changed when the actual constructor is // set. @@ -740,7 +758,7 @@ final class MonsterClass int[] getDataSegment() { assert(sc !is null && sc.isClass(), "Class does not have a class scope"); - assert(dataSize == sc.getDataSize); + uint dataSize = sc.getDataSize; int[] data = new int[dataSize]; int totSize = 0; @@ -757,109 +775,11 @@ final class MonsterClass } // Make sure the total size of the variables match the total size // requested by variables through addNewDataVar. - assert(totSize == sc.getDataSize, "Data size mismatch in scope"); + assert(totSize == dataSize, "Data size mismatch in scope"); return data; } - // Get an object from this class (but do not assign a thread to it) - MonsterObject *getObject(ref int[] dataBuf) - { - requireCompile(); - - MonsterObject *obj = objects.getNew(); - - // Set the class - obj.cls = this; - - // TODO: Better memory management here. I have been thinking - // about a general freelist manager, that works with object - // sizes rather than with templates. That would work with - // objects of any size, and could also be used directly from C / - // C++. If we have one for every size we might end up with a - // whole lot freelists though. Maybe we can pool the sizes, for - // example use one for 16 bytes, one for 64, 128, 256, 1k, 4k, - // etc. We will have to make the system and do some statistics - // to see what sizes are actually used. The entire structure can - // reside inside it's own region. - - // Copy the data segment into the buffer - assert(data.length == dataSize); - assert(dataBuf.length >= dataSize); - obj.data = dataBuf[0..dataSize]; - obj.data[] = data[]; - dataBuf = dataBuf[dataSize..$]; - - // Point to the static data segment - obj.sdata = sdata; - obj.extra = null; - - // Call the custom native constructor - if(constType != FuncType.Native) - { - fstack.pushNConst(obj); - if(constType == FuncType.NativeDDel) - dg_const(); - else if(constType == FuncType.NativeDFunc) - fn_const(); - else if(constType == FuncType.NativeCFunc) - c_const(); - fstack.pop(); - } - - return obj; - } - - // Clone an existing object - MonsterObject *getClone(MonsterObject *source, ref int[] dataBuf) - { - assert(source !is null); - assert(source.cls is this); - assert(source.data.length == data.length); - assert(source.sdata.ptr is sdata.ptr); - - requireCompile(); - - MonsterObject *obj = objects.getNew(); - - // Set the class - obj.cls = this; - - // Copy the data segment from the source - assert(data.length == dataSize); - assert(dataBuf.length >= dataSize); - assert(dataSize == source.data.length); - obj.data = dataBuf[0..dataSize]; - obj.data[] = source.data[]; - dataBuf = dataBuf[dataSize..$]; - - // Point to the static data segment - obj.sdata = sdata; - obj.extra = null; - - // Call the custom native constructor - if(constType != FuncType.Native) - { - fstack.pushNConst(obj); - if(constType == FuncType.NativeDDel) - dg_const(); - else if(constType == FuncType.NativeDFunc) - fn_const(); - else if(constType == FuncType.NativeCFunc) - c_const(); - fstack.pop(); - } - - return obj; - } - - // Delete an object belonging to this class - void returnObject(MonsterObject *obj) - { - // Put it back into the freelist - objects.remove(obj); - } - // Load file based on file name, class name, or both. The order of // the strings doesn't matter, and name2 can be empty. useCase // determines if we require a case sensitive match between the given @@ -956,9 +876,10 @@ final class MonsterClass requireScope(); // Look the function up in the scope - auto fn = sc.findFunc(name); + auto ln = sc.lookupName(name); + auto fn = ln.func; - if(fn is null) + if(!ln.isFunc) fail("Cannot bind to '" ~ name ~ "': no such function"); if(ft == FuncType.Idle) @@ -988,8 +909,6 @@ final class MonsterClass // parse them, and store it in the appropriate list; void store(ref TokenArray toks) { - // canParse() is not ment as a complete syntax test, only to be - // enough to identify which Block parser to apply. if(FuncDeclaration.canParse(toks)) { auto fd = new FuncDeclaration; @@ -1020,17 +939,28 @@ final class MonsterClass sd.parse(toks); enumdecs ~= sd; } + else if(ImportStatement.canParse(toks)) + { + auto sd = new ImportStatement; + sd.parse(toks); + imports ~= sd; + } else fail("Illegal type or declaration", toks); } // Converts a stream to tokens and parses it. void parse(Stream str, char[] fname, int bom) - { - assert(!isParsed(), "parse() called on a parsed class " ~ name.str); + { assert(str !is null); - TokenArray tokens = tokenizeStream(fname, str, bom); + parse(tokens, fname); + } + + // Parses a list of tokens + void parse(ref TokenArray tokens, char[] fname) + { + assert(!isParsed(), "parse() called on a parsed class " ~ name.str); alias Block.isNext isNext; @@ -1178,13 +1108,19 @@ final class MonsterClass objType = new ObjectType(this); classType = objType.getMeta(); - // Insert custom types first + // Insert custom types first. This will never refer to other + // identifiers. foreach(dec; structdecs) dec.insertType(sc); foreach(dec; enumdecs) dec.insertType(sc); - // Then resolve the headers. + // Resolve imports next. May refer to custom types, but no other + // ids. + foreach(dec; imports) + dec.resolve(sc); + + // Then resolve the type headers. foreach(dec; structdecs) dec.resolve(sc); foreach(dec; enumdecs) @@ -1203,9 +1139,7 @@ final class MonsterClass foreach(dec; statedecs) sc.insertState(dec.st); - // Resolve function headers. Here too, the init values will have - // to be moved to the body. We still need the parameter and - // return types though. + // Resolve function headers. foreach(func; funcdecs) func.resolve(sc); @@ -1272,16 +1206,6 @@ final class MonsterClass } } - // Set the data segment size and the total data size for all - // base classes. - dataSize = sc.getDataSize(); - - totalDataSize = 0; - foreach(t; tree) - totalDataSize += t.dataSize; - - assert(totalDataSize >= dataSize); - flags.unset(CFlags.InScope); } @@ -1314,6 +1238,10 @@ final class MonsterClass flags.set(CFlags.Resolved); } + alias int[] ia; + // These are platform dependent: + static const iasize = ia.sizeof / int.sizeof; + void compileBody() { assert(!isCompiled, getName() ~ " is already compiled"); @@ -1321,15 +1249,58 @@ final class MonsterClass // Resolve the class body if it's not already done if(!isResolved) resolveBody(); + // Require that all parent classes are compiled before us + foreach(mc; tree[0..$-1]) + mc.requireCompile(); + // Generate data segment and byte code for functions and // states. The result is stored in the respective objects. foreach(f; funcdecs) f.compile(); foreach(s; statedecs) s.compile(); - // Set the data segment. TODO: Separate static data from - // variables. + // Set the data segment for this class. data = getDataSegment(); + // Calculate the total data size we need to allocate for each + // object + uint tsize = 0; + foreach(c; tree) + { + tsize += c.data.length; // Data segment size + tsize += MonsterObject.exSize; // Extra data per object + tsize += iasize; // The size of our entry in the data[] + // table + } + + // Allocate the buffer + totalData = new int[tsize]; + + // Use this to get subslices of the data segment + int[] slice = totalData; + int[] get(int ints) + { + assert(ints <= slice.length); + int[] res = slice[0..ints]; + slice = slice[ints..$]; + return res; + } + + // Skip the data[] list + get(iasize*tree.length); + + // Assign the data segment values + foreach(c; tree) + { + int[] d = get(c.data.length); + d[] = c.data[]; + + // Skip the extra data + get(MonsterObject.exSize); + } + + // At this point we should have used up the entire slice + assert(slice.length == 0); + flags.set(CFlags.Compiled); // If it's a singleton, set up the object. diff --git a/monster/vm/mobject.d b/monster/vm/mobject.d index 6b315413ce..515ec6a3f8 100644 --- a/monster/vm/mobject.d +++ b/monster/vm/mobject.d @@ -28,6 +28,9 @@ import monster.vm.error; import monster.vm.mclass; import monster.vm.arrays; +import monster.util.freelist; +import monster.util.list; + import monster.compiler.states; import monster.compiler.variables; import monster.compiler.scopes; @@ -39,6 +42,25 @@ import std.utf; // An index to a monster object. typedef int MIndex; +union SharedType +{ + int i; + uint ui; + long l; + ulong ul; + float f; + double d; + + void *vptr; + Object obj; +} + +struct ExtraData +{ + SharedType extra; + vpNode node; +} + struct MonsterObject { /******************************************************* @@ -49,26 +71,28 @@ struct MonsterObject MonsterClass cls; - // Extra data. This allows you to assign additional data to an - // object. We might refine this concept a little later. - void *extra; + // Thread used for running state code. May be null if no code is + // running or scheduled. + Thread *sthread; - // The thread. Each object has its own thread, but not every - // MonsterObject has its own unique thread. For derived classes, we - // allocate a MonsterObject for each parent class, but only one - // thread for the object entire object. - CodeThread *thread; + // The following variables are "tree-indexed". This means that + // they're arrays, with one element for each class in the + // inheritance hierarchy. The corresponding class tree can be found + // in cls.tree. - // Object data segment - int[] data; + // Object data segment. + int[][] data; - // Static data segment. Do not write to this. - int[] sdata; + /******************************************************* + * * + * Private variables * + * * + *******************************************************/ - // Parent object tree. This reflects the equivalent 'tree' table in - // the MonsterClass. - MonsterObject* tree[]; + //private: + State *state; // Current state, null is the empty state. + public: /******************************************************* * * @@ -76,20 +100,10 @@ struct MonsterObject * * *******************************************************/ - // Get the next object in the objects list - used to iterate through - // objects of one class - MonsterObject *getNext() - { - // TODO: This syntax is rather hackish, and bug-prone if we - // suddenly change the list structure. - return cast(MonsterObject*) - ( cast(MonsterClass.ObjectList.TList.Iterator)this ).getNext(); - } - // Get the index of this object MIndex getIndex() { - return cast(MIndex)( MonsterClass.ObjectList.getIndex(this)+1 ); + return cast(MIndex)( ObjectList.getIndex(this)+1 ); } // Delete this object. Do not use the object after calling this @@ -99,61 +113,27 @@ struct MonsterObject cls.deleteObject(this); } - // Create a clone of this object. Note that this will always clone - // and return the top object (thread.topObj), regardless of which - // object in the list it is called on. In other words, the class - // mo.cls is not always the same as mo.clone().cls. + // Create a clone of this object. MonsterObject *clone() - { - auto t = thread.topObj; - return t.cls.createClone(t); - } + { return cls.createClone(this); } /******************************************************* * * - * Casting / polymorphism functions * + * Member variable getters / setters * * * *******************************************************/ - // Cast this object to the given class, if possible. Both upcasts - // and downcasts are allowed. - MonsterObject *Cast(MonsterClass toClass) - { return doCast(toClass, thread.topObj.tree); } - - // Upcast this object to the given class. Upcasting means that - // toClass must be the class of this object, or one of its parent - // classes. - MonsterObject *upcast(MonsterClass toClass) - { - assert(toClass !is null); - return doCast(toClass, tree); - } - // Special version used from bytecode. The index is the global class - // index. - MonsterObject *upcastIndex(int index) + // The last two ints of the data segment can be used to store extra + // data associated with the object. A typical example is the pointer + // to a D/C++ struct or class counterpart to the Monster class. + static const exSize = ExtraData.sizeof / int.sizeof; + static assert(exSize*4 == ExtraData.sizeof); + SharedType *getExtra(int index) { - // Convert the global class index to the tree index. TODO: Later - // on we should pass this index directly, but that is just - // optimization. - index = global.getClass(cast(CIndex)index).treeIndex; - - assert(index < tree.length, "cannot upcast class " ~ cls.getName ~ - " to index " ~ format(index)); - return tree[index]; + return & (cast(ExtraData*)&data[index][$-exSize]).extra; } - - // Is this object part of a linked inheritance chain? - bool isBaseObject() {return !isTopObject(); } - - // Is this object the topmost object in the inheritance chain? - bool isTopObject() { return thread.topObj is this; } - - - /******************************************************* - * * - * Member variable getters / setters * - * * - *******************************************************/ + SharedType *getExtra(MonsterClass mc) + { return getExtra(cls.upcast(mc)); } // This is the work horse for all the set/get functions. T* getPtr(T)(char[] name) @@ -179,10 +159,9 @@ struct MonsterObject assert(vb.sc.isClass(), "variable must be a class variable"); MonsterClass mc = vb.sc.getClass(); assert(mc !is null); - MonsterObject *obj = upcast(mc); // Return the pointer - return cast(T*) obj.getDataInt(vb.number); + return cast(T*) getDataInt(mc.treeIndex, vb.number); } T getType(T)(char[] name) { return *getPtr!(T)(name); } @@ -246,13 +225,16 @@ struct MonsterObject *******************************************************/ // Get an int from the data segment - int *getDataInt(int pos) + int *getDataInt(int treeIndex, int pos) { - if(pos < 0 || pos>=data.length) - fail("MonsterObject: data pointer out of range: " ~ toString(pos)); - return &data[pos]; + assert(treeIndex >= 0 && treeIndex < data.length, + "tree index out of range: " ~ toString(treeIndex)); + assert(pos >= 0 && pos data.length) - fail("MonsterObject: data array out of range: pos=" ~ toString(pos) ~ + assert(len > 0); + assert(treeIndex >= 0 && treeIndex < data.length, + "tree index out of range: " ~ toString(treeIndex)); + assert(pos >= 0 && (pos+len)<=data[treeIndex].length, + "data pointer out of range: pos=" ~ toString(pos) ~ ", len=" ~toString(len)); - return data[pos..pos+len]; + return data[treeIndex][pos..pos+len]; } @@ -283,7 +269,7 @@ struct MonsterObject // will take precedence. void call(char[] name) { - thread.topObj.cls.findFunction(name).call(this); + cls.findFunction(name).call(this); } // Call a function non-virtually. In other words, ignore @@ -293,17 +279,120 @@ struct MonsterObject assert(0, "not implemented"); } - // Set the current state of the object. If called from within state - // code, we have to return all the way back to the state code level - // before the new state is scheduled. If called when the object is - // idle (not actively running state code), the state is scheduled - // now, and the idle function is aborted. New state code does not - // start running until the next frame. - void setState(State *st, StateLabel *lb = null) + /* Set state. Invoked by the statement "state = statename;". This + function can be called in several situations, with various + results: + + + setState called with current state, no label + -> no action is performed + + + setState called with another state + + setState called with current state + a label + -> state is changed normally + + If a state change takes place directly in state code, the code is + aborted immediately. If it takes place in a function called from + state code, then code flow is allowed to return normally back to + the state code level, but is aborted immediately once it reaches + state code. + + State changes outside state code will always unschedule any + previously scheduled code (such as idle functions, or previous + calls to setState.) + */ + void setState(State *st, StateLabel *label) { - assert(st !is null || lb is null, - "If state is null, label must also be null"); - thread.setState(st, lb); + // Does the state actually change? + if(st !is state) + { + // Set the state + state = st; + + // We must handle state functions and other magic here. + } + // If no label is specified and we are already in this state, then + // don't do anything. + else if(label is null) return; + + // TODO: We can reorganize the entire function to deal with one + // sthread !is null test. Just do the label-checking first, and + // store the label offset + + // Do we already have a thread? + if(sthread !is null) + { + // If we are already scheduled (if an idle function has + // scheduled us, or if setState has been called multiple + // times), unschedule. This will automatically cancel any + // scheduled idle functions and call their abort() functions. + if(sthread.isScheduled) + sthread.cancel(); + + assert(sthread.isActive || !sthread.stateChange, + "stateChange was set outside active code"); + + // If we are running from state code, signal it that we must + // now abort execution when we reach the state level. + sthread.stateChange = sthread.isActive; + } + + // If we are jumping to anything but the empty state, we will have + // to schedule some code. + if(st !is null) + { + // Check that this state is valid + assert(st.owner.parentOf(cls), "state '" ~ st.name.str ~ + "' is not part of class " ~ cls.getName()); + + if(label is null) + // findLabel will return null if the label is not found. + // TODO: The begin label should probably be cached within + // State. + label = st.findLabel("begin"); + + if(label !is null) + { + // Make sure there's a thread to run in + if(sthread is null) + sthread = Thread.getNew(this); + + // Schedule the thread to start at the given label + sthread.schedule(label.offs); + assert(sthread.isScheduled); + } + } + + // Don't leave an unused thread dangling - kill it instead. + if(sthread !is null && !sthread.isScheduled) + { + sthread.kill(); + sthread = null; + } + + assert(sthread is null || sthread.isScheduled); + } + + void clearState() { setState(cast(State*)null, null); } + + // Index version of setState - called from bytecode + void setState(int st, int label, int clsInd) + { + if(st == -1) + { + assert(label == -1); + clearState(); + return; + } + + auto cls = cls.upcast(clsInd); + + // TODO: This does not support virtual states yet + auto pair = cls.findState(st, label); + + assert(pair.state.index == st); + assert(pair.state.owner is cls); + + setState(pair.state, pair.label); } // Named version of the above function. An empty string sets the @@ -314,8 +403,8 @@ struct MonsterObject { if(label == "") { - if(name == "") thread.setState(null,null); - else setState(cls.findState(name)); + if(name == "") clearState(); + else setState(cls.findState(name), null); return; } @@ -324,43 +413,13 @@ struct MonsterObject auto stl = cls.findState(name, label); setState(stl.state, stl.label); } +} - /******************************************************* - * * - * Private functions * - * * - *******************************************************/ - private: - - MonsterObject *doCast(MonsterClass toClass, MonsterObject* ptree[]) - { - assert(toClass !is null); - - if(toClass is cls) return this; - - // TODO: At some point, a class will have several possible tree - // indices. We will loop through the list and try them all. - int index = toClass.treeIndex; - MonsterObject *mo = null; - - if(index < ptree.length) - { - mo = ptree[index]; - - assert(mo !is this); - - // It's only a match if the classes match - if(mo.cls !is toClass) mo = null; - } +alias FreeList!(MonsterObject) ObjectList; - // If no match was found, then the cast failed. - if(mo is null) - fail("object of class " ~ cls.name.str ~ - " cannot be cast to " ~ toClass.name.str); - - return mo; - } -} +// The freelist used for allocation of objects. This contains all +// allocated and in-use objects. +ObjectList allObjects; // Convert an index to an object pointer MonsterObject *getMObject(MIndex index) @@ -368,22 +427,15 @@ MonsterObject *getMObject(MIndex index) if(index == 0) fail("Null object reference encountered"); - if(index < 0 || index > getTotalObjects()) + if(index < 0 || index > ObjectList.totLength()) fail("Invalid object reference"); - MonsterObject *obj = MonsterClass.ObjectList.getNode(index-1); + MonsterObject *obj = ObjectList.getNode(index-1); - if(obj.thread == null) + if(obj.cls is null) fail("Dead object reference (index " ~ toString(cast(int)index) ~ ")"); assert(obj.getIndex() == index); return obj; } - -// Get the total number of MonsterObjects ever allocated for the free -// list. Does NOT correspond to the number of objects in use. -int getTotalObjects() -{ - return MonsterClass.ObjectList.totLength(); -} diff --git a/monster/vm/scheduler.d b/monster/vm/scheduler.d deleted file mode 100644 index 0575a3ce41..0000000000 --- a/monster/vm/scheduler.d +++ /dev/null @@ -1,332 +0,0 @@ -/* - Monster - an advanced game scripting language - Copyright (C) 2007, 2008 Nicolay Korslund - Email: - WWW: http://monster.snaptoad.com/ - - This file (scheduler.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.vm.scheduler; - -import monster.compiler.functions; - -import monster.vm.mobject; -import monster.vm.idlefunction; -import monster.vm.error; -import monster.vm.fstack; - -import monster.util.freelist; -import std.string; - -// Enable minor safety checks - can be removed from release code. -debug=safecheck; - -// Are we currently looping through the wait list? -debug(safecheck) bool waitLoop; - -// The various types of code a scheduled node will call -enum CallType - { - None, // Not used and should never be set - Idle, // The return of an idle function (starts state code) - State // The beginning of a state (also starts state code) - } - -// The scheduler singleton -Scheduler scheduler; - -// Represents a code point for the scheduler to jump back to. Points -// to an object (which owns a coe thread object) and a position. TODO: -// Function calls must refer to some index, if the state changes we -// must call the correct function. -struct ScheduleStruct -{ - CallType type; - Function *idle; - MonsterObject *obj; - ListManager *list; - int retPos; // Return position for idle functions. - - // Unschedule this node from the runlist or waitlist it belongs - // to. Any idle function connected to this node is aborted. - void cancel() - { - debug(safecheck) auto node = obj.thread.scheduleNode; - if(idle !is null) - { - fstack.pushIdleAbort(idle, obj); - idle.idleFunc.abort(obj); - fstack.pop(); - } - // Make sure the scheduleNode is the same before and after calling - // abort(). - debug(safecheck) - assert(node == obj.thread.scheduleNode, - "abort() can not reschedule object or change state"); - remove(); - } - - // Remove this node from the list it belongs to. - void remove() - { - debug(safecheck) assert(!waitLoop, "remove() called from hasFinished()"); - type = CallType.None; - list.remove(obj); - } -} - -alias FreeList!(ScheduleStruct) ScheduleFreeList; -alias ScheduleStruct* CallNode; - -// Get the next node in a freelist -static CallNode getNext(CallNode cn) -{ - // Simple hack. The ScheduleStruct (pointed at by the CallNode) is - // the first part of, and therefore in the same location as, the - // iterator struct for the FreeList. It's therefore ok to cast the - // pointer, as long as we never change the iterator struct layout. - return cast(CallNode) - ( cast(ScheduleFreeList.TList.Iterator)cn ).getNext(); -} - -// A wrapper around a freelist. This struct takes care of some -// additional pointers in CodeThread and in ScheduleStruct. -struct ListManager -{ - ScheduleFreeList list; - - // Create a new node in this list. This means scheduling the given - // object in one of the lists. - CallNode newNode(MonsterObject *obj, CallType type, - Function *idle, int retPos) - { - assert(obj.thread.scheduleNode == null, - "CodeThread cannot have two schedule nodes."); - - CallNode cn = list.getNew(); - cn.obj = obj; - cn.type = type; - cn.idle = idle; - cn.list = this; - cn.retPos = retPos; - obj.thread.scheduleNode = cn; - return cn; - } - - // Remove a node from this run list (put it back into the freelist.) - void remove(MonsterObject *obj) - { - CallNode node = obj.thread.scheduleNode; - node.list = null; - obj.thread.scheduleNode = null; - list.remove(node); - } - - CallNode moveTo(ListManager *to, CallNode node) - { - node.list = to; - return list.moveTo(to.list, node); - } -} - -struct Scheduler -{ - // The acutal lists. We use pointers to access the run lists, since - // we want to swap them easily. - ListManager run1, run2, wait; - - // The run lists for this and the next round. - ListManager* runNext, run; - - void init() - { - // Assign the run list pointers - run = &run1; - runNext = &run2; - } - - // Statistics: - - // Number of elements in the waiting list - int numWait() { return wait.list.length; } - - // Number of elements scheduled to run the next frame - int numRun() { return runNext.list.length; } - - // Total number of objects scheduled or waiting - int numTotal() - { - assert(run.list.length == 0); // The 'run' list is always empty - // between frames. - return numRun() + numWait(); - } - - // "Call" an idle function. We must notify the idle function that it - // has been called. We also have the responsibility of rescheduling - // the given code thread at the right moment. - void callIdle(MonsterObject *obj, Function *idle, - int pos) - { - //writefln("Idle function '%s' called", lf.name); - debug(safecheck) assert(!waitLoop, "callIdle() called from hasFinished()"); - assert(obj.thread.scheduleNode == null); - - // Make sure the object and the function are set up correctly - assert(obj.cls is idle.owner); - assert(idle.isIdle); - assert(idle.idleFunc !is null); - - // Notify the idle function - fstack.pushIdleInit(idle, obj); - if(idle.idleFunc.initiate(obj)) - { - // The idle function wants to be scheduled - - // Make sure initiate() didn't change anything it shouldn't - // have. - assert(obj.thread.scheduleNode == null, - "initiate() cannot reschedule object or change state"); - - // Schedule it to be checked next round. - wait.newNode(obj, CallType.Idle, idle, pos); - } - - fstack.pop(); - } - - // Schedule the given state code to run the next frame - void scheduleState(MonsterObject *obj, int offs) - { - runNext.newNode(obj, CallType.State, null, offs); - } - - // Do a complete frame. TODO: Make a distinction between a round and - // a frame later. We could for example do several rounds per frame, - // measured by some criterion of how much time we want to spend on - // script code or whether there are any pending items in the run - // list. We could do several runs of the run-list (to handle state - // changes etc) but only one run on the condition list (actually - // that is a good idea.) We also do not have to execute everything in - // the run list if it is long (otoh, allowing a build-up is not - // good.) But all this falls in the "optimization" category. - void doFrame() - { - //writefln("Beginning of frame"); - - // Turn on some safety features - debug(safecheck) waitLoop = true; - - // Go through the condition list for this round. - CallNode cn = wait.list.getHead(); - CallNode next; - while(cn != null) - { - // Get the next node here, since the current node might move - // somewhere else during this iteration, and then getNext will - // point to another list. - next = getNext(cn); - - assert(cn.obj.thread.scheduleNode == cn); - - // This is an idle function and it is finished. Note that - // hasFinished() is NOT allowed to change the wait list in any - // way, ie to change object states or interact with the - // scheduler. In fact, hasFinished() should do as little as - // possible. - if(cn.type == CallType.Idle) - { - fstack.pushIdleCheck(cn.idle, cn.obj); - if(cn.idle.idleFunc.hasFinished(cn.obj)) - // Schedule the code to start running again this round. We - // move it from the wait list to the run list. - wait.moveTo(runNext,cn); - - fstack.pop(); - } - // Set the next item - cn = next; - } - - debug(safecheck) waitLoop = false; - - - /* - if(wait.list.length) - writefln(" There are idle functions lurking in the shadows"); - */ - - //writefln("Condition phase complete, beginning execution phase."); - - // Swap the runlist for the next frame with the current one. All - // code that is scheduled after this point is executed the next - // frame. - auto tmp = runNext; - runNext = run; - run = tmp; - - // Now execute the run list for this frame. Note that items might - // be removed from the run list as we go (eg. if a scheduled - // object has it's state changed) but this is handled. New nodes - // might also be scheduled, but these are added to the runNext - // list. - - // First element - cn = run.list.getHead(); - while(cn != null) - { - // Remove the current item from the list before starting. - MonsterObject *obj = cn.obj; - auto code = obj.thread; - assert(code.scheduleNode == cn); - run.remove(obj); - - // Now execute the item - if(cn.type == CallType.Idle) - { - // Tell the idle function that we we are reentering - fstack.pushIdleReentry(cn.idle, obj); - cn.idle.idleFunc.reentry(obj); - fstack.pop(); - - assert(code.scheduleNode == null, - "reentry() cannot reschedule object or change state"); - - // Return to the code point - code.callState(cn.retPos); - } - else if(cn.type == CallType.State) - // Code is scheduled after a state change. We must jump to - // the right offset. - code.callState(cn.retPos); - - else assert(0, "Unhandled return type"); - - // The function stack should now be at zero - assert(fstack.isEmpty()); - - // Get the next item. - cn = run.list.getHead(); - } - - // Check that we cleared the run list - assert(run.list.length == 0); - - //writefln("End of frame\n"); - } -} diff --git a/monster/vm/stack.d b/monster/vm/stack.d index 9c7c8f47e2..8ef522e94d 100644 --- a/monster/vm/stack.d +++ b/monster/vm/stack.d @@ -35,14 +35,14 @@ import monster.vm.mclass; import monster.vm.arrays; import monster.vm.error; -// The stack. One nice attribue of our cooperative multithreading -// scheme is that we only need one single stack frame. All functions -// and code is required to "finish up" before returning or giving -// control to other code. When we switch to "real" threads later, we -// will need one stack per system thread, but many virtual threads -// will still share each stack. +// Stack CodeStack stack; +void initStack() +{ + stack.init(); +} + // A simple stack frame. All data are in chunks of 4 bytes struct CodeStack { @@ -205,13 +205,6 @@ struct CodeStack MonsterObject *popObject() { return getMObject(cast(MIndex)popInt()); } - // Push an object, and make sure it is cast to the right type - void pushCast(MonsterObject *obj, MonsterClass cls) - { pushObject(obj.Cast(cls)); } - - void pushCast(MonsterObject *obj, char[] name) - { pushCast(obj, global.getClass(name)); } - // Push arrays of objects. TODO: These do memory allocation, and I'm // not sure that belongs here. I will look into it later. void pushObjects(MonsterObject *objs[]) diff --git a/monster/vm/thread.d b/monster/vm/thread.d index e1953ffb94..58ebcd0b9c 100644 --- a/monster/vm/thread.d +++ b/monster/vm/thread.d @@ -28,17 +28,19 @@ import std.stdio; import std.uni; import std.c.string; +import monster.util.freelist; + import monster.compiler.bytecode; import monster.compiler.linespec; import monster.compiler.states; import monster.compiler.functions; import monster.compiler.scopes; +import monster.compiler.types; import monster.vm.mclass; import monster.vm.mobject; import monster.vm.codestream; import monster.vm.stack; -import monster.vm.scheduler; import monster.vm.idlefunction; import monster.vm.arrays; import monster.vm.iterators; @@ -50,10 +52,18 @@ extern(C) void* memmove(void *dest, void *src, size_t n); extern(C) double floor(double d); +import monster.util.list; +alias _lstNode!(Thread) _tmp1; +alias __FreeNode!(Thread) _tmp2; +alias FreeList!(Thread) NodeList; + +// Current thread +Thread *cthread; + // This represents an execution 'thread' in the system. Each object // has its own thread. The thread contains a link to the object and // the class, along with some other data. -struct CodeThread +struct Thread { /******************************************************* * * @@ -61,15 +71,114 @@ struct CodeThread * * *******************************************************/ - // The object that "owns" this thread. This can only point to the - // top-most object in the linked parent object chain. - MonsterObject* topObj; + // This has been copied from ScheduleStruct, which is now merged + // with Thread. We'll sort it out later. + + // Some generic variables that idle functions can use to store + // temporary data off the stack. + SharedType idleData; + + // The contents of idleObj's extra data for the idle's owner class. + SharedType extraData; + + // Temporarily needed since we need a state and an object to push on + // the stack to return to state code. This'll change soon (we won't + // need to push anything to reenter, since the function stack will + // already be set up for us.) + MonsterObject * theObj; - // Pointer to our current scheduling point. If null, we are not - // currently sceduled. Only applies to state code, not scheduled - // function calls. - CallNode scheduleNode; + Function *idle; + MonsterObject *idleObj; // Object owning the idle function + NodeList * list; // List owning this thread + int retPos; // Return position in byte code. + bool isActive; // Set to true whenever we are running from state + // code. If we are inside the state itself, this will + // be true and 'next' will be 1. + bool stateChange; // Set to true when a state change is in + // progress. Only used when state is changed from + // within a function in active code. + + // Unschedule this node from the runlist or waitlist it belongs to, + // but don't kill it. Any idle function connected to this node is + // aborted. + void cancel() + { + if(idle !is null) + { + fstack.pushIdleAbort(idle, idleObj); + idle.idleFunc.abort(this); + fstack.pop(); + idle = null; + } + retPos = -1; + moveTo(&scheduler.unused); + + assert(!isScheduled); + } + + static Thread* getNew(MonsterObject *obj = null) + { + auto cn = scheduler.unused.getNew(); + cn.list = &scheduler.unused; + cn.initialize(obj); + return cn; + } + + // Remove the thread comletely + void kill() + { + cancel(); + list.remove(this); + list = null; + } + + // Schedule this thread to run next frame + void schedule(uint offs) + { + assert(!isScheduled, + "cannot schedule an already scheduled thread"); + + retPos = offs; + moveTo(scheduler.runNext); + } + + // Move this node to another list. + void moveTo(NodeList *to) + { + assert(list !is null); + list.moveTo(*to, this); + list = to; + } + + // Are we currently scheduled? + bool isScheduled() + { + // The node is per definition scheduled if it is in one of these + // lists + return + list is &scheduler.wait || + list is scheduler.run || + list is scheduler.runNext; + } + + bool isUnused() + { + return list is &scheduler.unused; + } + + bool isIdle() { return idle !is null; } + + // Get the next node in the freelist + Thread* getNext() + { + // Simple hack. The Thread (pointed at by the Thread*) is the + // first part of, and therefore in the same location as, the + // iterator struct for the FreeList. This is per design, so it's + // ok to cast the pointer. + return cast(Thread*) + ( cast(NodeList.TList.Iterator)this ).getNext(); + } /******************************************************* * * @@ -77,44 +186,71 @@ struct CodeThread * * *******************************************************/ - void initialize(MonsterObject* top) + void initialize(MonsterObject *obj) { - topObj = top; + theObj = obj; // Initialize other variables - state = null; // Start in the empty state - scheduleNode = null; + idle = null; + idleObj = null; isActive = false; stateChange = false; + retPos = -1; } - State* getState() { return state; } - - // Call state code for this object. 'pos' gives the byte position - // within the bytecode. It is called when a new state is entered, or - // when an idle funtion returns. The state must already be set with - // setState - void callState(int pos) + // Reenter this thread to the point where it was previously stopped. + void reenter() { - assert(state !is null, "attempted to call the empty state"); + assert(theObj !is null, + "cannot reenter a non-state thread yet"); + + // Most if not all of these checks will have to be removed in the + // future + assert(theObj.state !is null, "attempted to call the empty state"); assert(!isActive, - "callState cannot be called when object is already active"); + "reenter cannot be called when object is already active"); assert(fstack.isEmpty, - "ctate code can only run at the bottom of the function stack"); + "state code can only run at the bottom of the function stack"); + assert(isScheduled); + + if(isIdle) + { + assert(idle !is null); + assert(idleObj !is null || idle.isStatic); - // Set a bool to indicate that we are now actively running state - // code. + // Tell the idle function that we we are reentering + fstack.pushIdleReentry(idle, idleObj); + idle.idleFunc.reentry(this); + fstack.pop(); + + // We're no longer idle + idle = null; + } + + // Remove the current node from the run list + moveTo(&scheduler.unused); + + // Set the active flat to indicate that we are now actively + // running. (Might not be needed in the future) isActive = true; - // Set up the code stack - fstack.push(state, topObj.upcast(state.sc.getClass())); + // Set the thread + assert(cthread is null); + cthread = this; + + // Set up the code stack for state code. + fstack.push(theObj.state, theObj); // Set the position - fstack.cur.code.jump(pos); + assert(retPos >= 0); + fstack.cur.code.jump(retPos); // Run the code execute(); + // Reset the thread + cthread = null; + // We are no longer active isActive = false; @@ -123,98 +259,10 @@ struct CodeThread stack.getPos)); fstack.pop(); - } - - /* Set state. Invoked by the statement "state = statename;". This - function can be called in several situations, with various - results: - - + setState called with current state, no label - -> no action is performed - - + setState called with another state - + setState called with current state + a label - -> state is changed normally - - If a state change takes place directly in state code, the code is - aborted immediately. If it takes place in a function called from - state code, then code flow is allowed to return normally back to - the state code level, but is aborted immediately once it reaches - state code. - - State changes outside state code will always unschedule any - previously scheduled code (such as idle functions, or previous - calls to setState.) - */ - void setState(State *st, StateLabel *label) - { - // If no label is specified and we are already in this state, then - // do nothing. - if(st is state && label is null) - return; - - // Does the state actually change? - if(st !is state) - { - // If so, we must handle state functions and other magic here. - } - - // Set the state - state = st; - - // If we are already scheduled (if an idle function has scheduled - // us, or if setState has been called multiple times), - // unschedule. This will automatically cancel any scheduled idle - // functions and call their abort() functions. - if(scheduleNode) - scheduleNode.cancel(); - - // If we are jumping to anything but the empty state, we might - // have to schedule some code. - if(st !is null) - { - // Check that this state is valid - assert(st.sc.getClass().parentOf(topObj), "state '" ~ st.name.str ~ - "' is not part of class " ~ topObj.cls.getName()); - - if(label is null) - // findLabel will return null if the label is not found. - // TODO: The begin label should probably be cached within - // State. - label = st.findLabel("begin"); - - // Reschedule the new state for the next frame, if a label is - // specified. We have to cast to find the right object first - // though. - auto mo = topObj.upcast(st.sc.getClass()); - - if(label !is null) - scheduler.scheduleState(mo, label.offs); - } - - assert(isActive || !stateChange, - "stateChange was set outside active code"); - // If we are running from state code, signal it that we must now - // abort execution when we reach the state level. - stateChange = isActive; } - /******************************************************* - * * - * Private variables * - * * - *******************************************************/ private: - - bool isActive; // Set to true whenever we are running from state - // code. If we are inside the state itself, this will - // be true and 'next' will be 1. - bool stateChange; // Set to true when a state change is in - // progress. Only used when state is changed from - // within a function in active code. - State *state; // Current state, null is the empty state. - /******************************************************* * * * Private helper functions * @@ -234,113 +282,47 @@ struct CodeThread .fail(msg, file, line); } - // Index version of setState - called from bytecode - void setState(int st, int label, int cls) - { - if(st == -1) - { - assert(label == -1); - setState(null, null); - return; - } - - auto mo = topObj.upcastIndex(cls); - - auto pair = mo.cls.findState(st, label); - setState(pair.state, pair.label); - - assert(pair.state.index == st); - assert(pair.state.sc.getClass().getIndex == cls); - } - - void callIdle() + // Parse the BC.CallIdle instruction parameters and call schedule + // the given idle function. + void callIdle(MonsterObject *iObj) { assert(isActive && fstack.isStateCode, - "Byte code attempted to call an idle function outside of state code."); + "Byte code attempted to call an idle function outside of state code."); assert(!isScheduled, "Thread is already scheduled"); CodeStream *code = &fstack.cur.code; - // Get the correct object - MonsterObject *mo = topObj.upcastIndex(code.getInt()); + // Store the object + idleObj = iObj; + assert(idleObj !is null); - // And the function - Function *fn = mo.cls.findFunction(code.getInt()); - assert(fn !is null && fn.isIdle); + // Get the class from the index + auto cls = iObj.cls.upcast(code.getInt()); - // The IdleFunction object bound to this function is stored in - // fn.idleFunc - if(fn.idleFunc is null) - fail("Called unimplemented idle function '" ~ fn.name.str ~ "'"); + // Get the function + idle = cls.findFunction(code.getInt()); + assert(idle !is null && idle.isIdle); + assert(cls is idle.owner); + assert(idleObj.cls.childOf(cls)); - // Tell the scheduler that an idle function was called. It - // will reschedule us as needed. - scheduler.callIdle(mo, fn, fstack.cur.code.getPos); - } - - // Pops a pointer off the stack. Null pointers will throw an - // exception. - int *popPtr(MonsterObject *obj) - { - PT type; - int index; - decodePtr(stack.popInt(), type, index); - - // Null pointer? - if(type == PT.Null) - fail("Cannot access value, null pointer"); - - // Local variable? - if(type == PT.Stack) - return stack.getFrameInt(index); - - // Variable in this object - if(type == PT.DataOffs) - return obj.getDataInt(index); - - // This object, but another (parent) class - if(type == PT.DataOffsCls) - { - // We have to pop the class index of the stack as well - return obj.upcastIndex(stack.popInt()).getDataInt(index); - } - - // Far pointer, with offset. Both the class index and the object - // reference is on the stack. - if(type == PT.FarDataOffs) - { - int clsIndex = stack.popInt(); - - // Get the object reference from the stack - MonsterObject *tmp = stack.popObject(); - - // Cast the object to the correct class - tmp = tmp.upcastIndex(clsIndex); + // The IdleFunction object bound to this function is stored in + // idle.idleFunc + if(idle.idleFunc is null) + fail("Called unimplemented idle function '" ~ idle.name.str ~ "'"); - // Return the correct pointer - return tmp.getDataInt(index); - } + // Set the return position + retPos = fstack.cur.code.getPos(); - // Array pointer - if(type == PT.ArrayIndex) - { - assert(index==0); - // Array indices are on the stack, not in the opcode. - index = stack.popInt(); - ArrayRef *arf = stack.popArray(); - assert(!arf.isNull); - if(arf.isConst) - fail("Cannot assign to constant array"); - index *= arf.elemSize; - if(index < 0 || index >= arf.iarr.length) - fail("Array index " ~ .toString(index/arf.elemSize) ~ - " out of bounds (array length " ~ .toString(arf.length) ~ ")"); - return &arf.iarr[index]; - } + // Set up extraData + extraData = *idleObj.getExtra(idle.owner); - fail("Unable to handle pointer type " ~ toString(cast(int)type)); + // Notify the idle function + fstack.pushIdleInit(idle, idleObj); + if(idle.idleFunc.initiate(this)) + moveTo(&scheduler.wait); + fstack.pop(); } - bool shouldExitState() + bool shouldExit() { if(fstack.isStateCode && stateChange) { @@ -372,21 +354,87 @@ struct CodeThread { // The maximum amount of instructions we execute before assuming // an infinite loop. - const long limit = 10000000; + static const long limit = 10000000; assert(fstack.cur !is null, - "CodeThread.execute called but there is no code on the function stack."); + "Thread.execute called but there is no code on the function stack."); + + assert(cthread == this, + "can only run the current thread"); // Get some values from the function stack CodeStream *code = &fstack.cur.code; MonsterObject *obj = fstack.cur.obj; MonsterClass cls = fstack.cur.cls; + int clsInd = cls.getTreeIndex(); // Only an object belonging to this thread can be passed to // execute() on the function stack. - assert(obj is null || cls.parentOf(topObj)); + assert(obj is null || cls.parentOf(obj)); + assert(obj is null || obj.cls.upcast(cls) == clsInd); + assert(obj !is null || fstack.cur.isStatic); + + // Pops a pointer off the stack. Null pointers will throw an + // exception. + int *popPtr() + { + PT type; + int index; + decodePtr(stack.popInt(), type, index); + + // Null pointer? + if(type == PT.Null) + fail("Cannot access value, null pointer"); + + // Stack variable? + if(type == PT.Stack) + return stack.getFrameInt(index); + + // Variable in this object + if(type == PT.DataOffs) + return obj.getDataInt(clsInd, index); + + // This object, but another (parent) class + if(type == PT.DataOffsCls) + // We have to pop the class index of the stack as well + return obj.getDataInt(stack.popInt, index); + + // Far pointer, with offset. Both the class index and the object + // reference is on the stack. + if(type == PT.FarDataOffs) + { + int clsIndex = stack.popInt(); + + // Get the object reference from the stack + MonsterObject *tmp = stack.popObject(); + + // Return the correct pointer + return tmp.getDataInt(clsIndex, index); + } + + // Array pointer + if(type == PT.ArrayIndex) + { + assert(index==0); + // Array indices are on the stack + index = stack.popInt(); + ArrayRef *arf = stack.popArray(); + assert(!arf.isNull); + + if(arf.isConst) + fail("Cannot assign to constant array"); - // Reduce or remove as many of these as possible + index *= arf.elemSize; + if(index < 0 || index >= arf.iarr.length) + fail("Array index " ~ .toString(index/arf.elemSize) ~ + " out of bounds (array length " ~ .toString(arf.length) ~ ")"); + return &arf.iarr[index]; + } + + fail("Unable to handle pointer type " ~ toString(cast(int)type)); + } + + // Various temporary stuff int *ptr; long *lptr; float *fptr; @@ -396,7 +444,7 @@ struct CodeThread int val, val2; long lval; - // Disable this for now. It should be a per-function option, perhaps, + // Disable this for now. // or at least a compile time option. //for(long i=0;i= getLong(mo); - } -} diff --git a/mscripts/object.mn b/mscripts/object.mn index cfc9634b4e..7bdf3e2b34 100644 --- a/mscripts/object.mn +++ b/mscripts/object.mn @@ -24,11 +24,7 @@ // This is the base class of all OpenMW Monster classes. class Object; -// Sleeps a given amount of time -idle sleep(float seconds); - -// Print a message to screen -native print(char[][] msg...); +import io, timer; // Get a random number between a and b (inclusive) native int randInt(int a, int b); diff --git a/mscripts/test.mn b/mscripts/test.mn index e3d0d129f4..f0fdcb8a19 100644 --- a/mscripts/test.mn +++ b/mscripts/test.mn @@ -1,5 +1,7 @@ // A sample class -class Test : Object; +class Test; + +import io, timer; test() { @@ -15,7 +17,6 @@ state printMessage loop: print("Howdy from the world of Monster scripts!"); print("This script is located in mscripts/test.mn. Check it out!"); - print("GMST.sThief: ", GMST.sThief); sleep(60); goto loop; } diff --git a/openmw.d b/openmw.d index bfd944efdc..ee23b88b7d 100644 --- a/openmw.d +++ b/openmw.d @@ -42,7 +42,7 @@ import core.memory; import core.config; import monster.util.string; -import monster.vm.mobject; +import monster.vm.mclass; import mscripts.object; import sound.audio; @@ -417,5 +417,5 @@ void main(char[][] args) // Write some memory statistics poolSize(); writefln(esmRegion); - writefln("Total objects: ", getTotalObjects); + writefln("Total objects: ", MonsterClass.getTotalObjects); } diff --git a/scene/gamesettings.d b/scene/gamesettings.d index f4fbe851ca..8b445a9c03 100644 --- a/scene/gamesettings.d +++ b/scene/gamesettings.d @@ -32,7 +32,7 @@ void loadGameSettings() assert(name != a); } - if(mc.sc.findVar(name) is null) + if(!mc.sc.lookupName(name).isVar) { writefln("WARNING: GMST %s not supported!", name); continue; diff --git a/sound/music.d b/sound/music.d index 4f02104258..9a2585ad06 100644 --- a/sound/music.d +++ b/sound/music.d @@ -38,11 +38,12 @@ import core.resource; class Idle_waitUntilFinished : IdleFunction { override: - bool initiate(MonsterObject *mo) { return true; } + bool initiate(Thread*) { return true; } - bool hasFinished(MonsterObject *mo) + bool hasFinished(Thread* trd) { - Jukebox mgr = cast(Jukebox)mo.extra; + Jukebox mgr = cast(Jukebox)trd.extraData.obj; + assert(mgr !is null); // Return when the music is no longer playing return !mgr.isPlaying(); @@ -179,7 +180,7 @@ class Jukebox this(MonsterObject *m) { mo = m; - m.extra = cast(void*)this; + m.getExtra(Music.jukeC).obj = this; sID = 0; bIDs[] = 0; @@ -187,7 +188,7 @@ class Jukebox static Jukebox get(MonsterObject *m) { - auto j = cast(Jukebox)m.extra; + auto j = cast(Jukebox)m.getExtra(Music.jukeC).obj; assert(j !is null); assert(j.mo == m); return j;