/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using OpenSim.Region.ScriptEngine.Shared.ScriptBase; using OpenSim.Region.ScriptEngine.XMREngine; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Reflection.Emit; using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; /** * @brief Compute values used during code generation to keep track of where computed values are stored. * * Conceptually holds the memory address and type of the value * such as that used for a local variable, global variable, temporary variable. * Also used for things like constants and function/method entrypoints, * they are basically treated as read-only variables. * * cv.type - type of the value * * cv.PushVal() - pushes the value on the CIL stack * cv.PushRef() - pushes address of the value on the CIL stack * * cv.PopPre() - gets ready to pop from the CIL stack * ...by possibly pushing something * * cv.PushPre() - pops value from the CIL stack * * If the type is a TokenTypeSDTypeDelegate, the location is callable, * so you get these additional functions: * * cv.GetRetType() - gets function/method's return value type * TokenTypeVoid if void * null if not a delegate * cv.GetArgTypes() - gets array of argument types * as seen by script level, ie, * does not include any hidden 'this' type * cv.GetArgSig() - gets argument signature eg, "(integer,list)" * null if not a delegate * * cv.CallPre() - gets ready to call the function/method * ...by possibly pushing something * such as a 'this' pointer * * cv.CallPost() - calls the function/method */ namespace OpenSim.Region.ScriptEngine.XMREngine { /** * @brief Location of a value * Includes constants, expressions and temp variables. */ public abstract class CompValu { protected static readonly MethodInfo gsmdMethodInfo = typeof (XMRInstAbstract).GetMethod ("GetScriptMethodDelegate", new Type[] { typeof (string), typeof (string), typeof (object) }); private static readonly MethodInfo avpmListMethInfo = typeof (XMRInstArrays).GetMethod ("PopList", new Type[] { typeof (int), typeof (LSL_List) }); private static readonly MethodInfo avpmObjectMethInfo = typeof (XMRInstArrays).GetMethod ("PopObject", new Type[] { typeof (int), typeof (object) }); private static readonly MethodInfo avpmStringMethInfo = typeof (XMRInstArrays).GetMethod ("PopString", new Type[] { typeof (int), typeof (string) }); public TokenType type; // type of the value and where in the source it was used public CompValu (TokenType type) { this.type = type; } public Type ToSysType() { return (type.ToLSLWrapType () != null) ? type.ToLSLWrapType () : type.ToSysType (); } // if a field of an XMRInstArrays array cannot be directly written, // get the method that can write it private static MethodInfo ArrVarPopMeth (FieldInfo fi) { if (fi.Name == "iarLists") return avpmListMethInfo; if (fi.Name == "iarObjects") return avpmObjectMethInfo; if (fi.Name == "iarStrings") return avpmStringMethInfo; return null; } // emit code to push value onto stack public void PushVal (ScriptCodeGen scg, Token errorAt, TokenType stackType) { this.PushVal (scg, errorAt, stackType, false); } public void PushVal (ScriptCodeGen scg, Token errorAt, TokenType stackType, bool explicitAllowed) { this.PushVal (scg, errorAt); TypeCast.CastTopOfStack (scg, errorAt, this.type, stackType, explicitAllowed); } public abstract void PushVal (ScriptCodeGen scg, Token errorAt); public abstract void PushRef (ScriptCodeGen scg, Token errorAt); // emit code to pop value from stack public void PopPost (ScriptCodeGen scg, Token errorAt, TokenType stackType) { TypeCast.CastTopOfStack (scg, errorAt, stackType, this.type, false); this.PopPost (scg, errorAt); } public virtual void PopPre (ScriptCodeGen scg, Token errorAt) { } // call this before pushing value to be popped public abstract void PopPost (ScriptCodeGen scg, Token errorAt); // call this after pushing value to be popped // return true: doing a PushVal() does not involve CheckRun() // false: otherwise public virtual bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return true; } /* * These additional functions are available if the type is a delegate */ public TokenType GetRetType () { if (!(type is TokenTypeSDTypeDelegate)) return null; return ((TokenTypeSDTypeDelegate)type).decl.GetRetType (); } public TokenType[] GetArgTypes () { if (!(type is TokenTypeSDTypeDelegate)) return null; return ((TokenTypeSDTypeDelegate)type).decl.GetArgTypes (); } public string GetArgSig () { if (!(type is TokenTypeSDTypeDelegate)) return null; return ((TokenTypeSDTypeDelegate)type).decl.GetArgSig (); } // These are used only if type is a delegate too // - but it is a real delegate pointer in a global or local variable or a field, etc // ie, PushVal() pushes a delegate pointer // - so we must have CallPre() push the delegate pointer as a 'this' for this.Invoke(...) // - and CallPost() call the delegate's Invoke() method // - we assume the target function is non-trivial so we always use a call label public virtual void CallPre (ScriptCodeGen scg, Token errorAt) // call this before pushing arguments { new ScriptCodeGen.CallLabel (scg, errorAt); this.PushVal (scg, errorAt); } public virtual void CallPost (ScriptCodeGen scg, Token errorAt) // call this after pushing arguments { TokenTypeSDTypeDelegate ttd = (TokenTypeSDTypeDelegate)type; MethodInfo invokeMethodInfo = ttd.decl.GetInvokerInfo (); scg.ilGen.Emit (errorAt, OpCodes.Callvirt, invokeMethodInfo); scg.openCallLabel = null; } /* * Utilities used by CompValuGlobalVar and CompValuInstField * where the value is located in a type-dependent array. */ protected void EmitFieldPushVal (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) { scg.ilGen.Emit (errorAt, OpCodes.Ldfld, var.vTableArray); // which array scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); // which array element if (type is TokenTypeFloat) { scg.ilGen.Emit (errorAt, OpCodes.Ldelem_R8); } else if (type is TokenTypeInt) { scg.ilGen.Emit (errorAt, OpCodes.Ldelem_I4); } else if (type is TokenTypeSDTypeDelegate) { scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (object)); scg.ilGen.Emit (errorAt, OpCodes.Castclass, ToSysType ()); } else { scg.ilGen.Emit (errorAt, OpCodes.Ldelem, ToSysType ()); } } protected void EmitFieldPushRef (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) { if (ArrVarPopMeth (var.vTableArray) != null) { scg.ErrorMsg (errorAt, "can't take address of this variable"); } scg.ilGen.Emit (errorAt, OpCodes.Ldfld, var.vTableArray); scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); scg.ilGen.Emit (errorAt, OpCodes.Ldelema, ToSysType()); } protected void EmitFieldPopPre (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) { if (ArrVarPopMeth (var.vTableArray) != null) { scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); } else { scg.ilGen.Emit (errorAt, OpCodes.Ldfld, var.vTableArray); scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, var.vTableIndex); } } protected void EmitFieldPopPost (ScriptCodeGen scg, Token errorAt, TokenDeclVar var) { if (ArrVarPopMeth (var.vTableArray) != null) { scg.ilGen.Emit (errorAt, OpCodes.Call, ArrVarPopMeth (var.vTableArray)); } else if (type is TokenTypeFloat) { scg.ilGen.Emit (errorAt, OpCodes.Stelem_R8); } else if (type is TokenTypeInt) { scg.ilGen.Emit (errorAt, OpCodes.Stelem_I4); } else if (type is TokenTypeSDTypeDelegate) { scg.ilGen.Emit (errorAt, OpCodes.Stelem, typeof (object)); } else { scg.ilGen.Emit (errorAt, OpCodes.Stelem, ToSysType ()); } } /** * @brief With value pushed on stack, emit code to set a property by calling its setter() method. * @param scg = which script is being compiled * @param errorAt = for error messages * @param type = property type * @param setProp = setter() method */ protected void EmitPopPostProp (ScriptCodeGen scg, Token errorAt, TokenType type, CompValu setProp) { ScriptMyLocal temp = scg.ilGen.DeclareLocal (type.ToSysType (), "__spr_" + errorAt.Unique); scg.ilGen.Emit (errorAt, OpCodes.Stloc, temp); setProp.CallPre (scg, errorAt); scg.ilGen.Emit (errorAt, OpCodes.Ldloc, temp); setProp.CallPost (scg, errorAt); } } // The value is kept in an (XMR_Array) array element public class CompValuArEle : CompValu { public CompValu arr; private CompValu idx; private TokenTypeObject tto; private static readonly MethodInfo getByKeyMethodInfo = typeof (XMR_Array).GetMethod ("GetByKey", new Type[] { typeof (object) }); private static readonly MethodInfo setByKeyMethodInfo = typeof (XMR_Array).GetMethod ("SetByKey", new Type[] { typeof (object), typeof (object) }); // type = TokenTypeObject always, as our array elements are always of type 'object' // arr = where the array object itself is stored // idx = where the index value is stored public CompValuArEle (TokenType type, CompValu arr, CompValu idx) : base (type) { this.arr = arr; this.idx = idx; this.tto = new TokenTypeObject (this.type); } public override void PushVal (ScriptCodeGen scg, Token errorAt) { arr.PushVal (scg, errorAt); // array idx.PushVal (scg, errorAt, this.tto); // key scg.ilGen.Emit (errorAt, OpCodes.Call, getByKeyMethodInfo); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "array element not allowed here"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } public override void PopPre (ScriptCodeGen scg, Token errorAt) { arr.PushVal (scg, errorAt); // array idx.PushVal (scg, errorAt, this.tto); // key } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Call, setByKeyMethodInfo); } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading an // XMR_Array element is trivial public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l; } } // The value is kept in the current function's argument list public class CompValuArg : CompValu { public int index; public bool readOnly; private static OpCode[] ldargs = { OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3 }; public CompValuArg (TokenType type, int index) : base (type) { this.index = index; } public CompValuArg (TokenType type, int index, bool ro) : base (type) { this.index = index; this.readOnly = ro; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { if (index < ldargs.Length) scg.ilGen.Emit (errorAt, ldargs[index]); else if (index <= 255) scg.ilGen.Emit (errorAt, OpCodes.Ldarg_S, index); else scg.ilGen.Emit (errorAt, OpCodes.Ldarg, index); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { if (readOnly) { scg.ErrorMsg (errorAt, "location cannot be written to"); } if (index <= 255) scg.ilGen.Emit (errorAt, OpCodes.Ldarga_S, index); else scg.ilGen.Emit (errorAt, OpCodes.Ldarga, index); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { if (readOnly) { scg.ErrorMsg (errorAt, "location cannot be written to"); } scg.ilGen.Emit (errorAt, OpCodes.Starg, index); } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading an // argument is trivial public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l; } } // The value is a character constant public class CompValuChar : CompValu { public char x; public CompValuChar (TokenType type, char x) : base (type) { if (!(this.type is TokenTypeChar)) { this.type = new TokenTypeChar (this.type); } this.x = x; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, (int)x); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get constant's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into contant"); } } // The value is kept in a struct/class field of an internal struct/class public class CompValuField : CompValu { CompValu obj; FieldInfo field; public CompValuField (TokenType type, CompValu obj, FieldInfo field) : base (type) { this.obj = obj; this.field = field; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { if (field.ReflectedType.IsValueType) { obj.PushRef (scg, errorAt); } else { obj.PushVal (scg, errorAt); } scg.ilGen.Emit (errorAt, OpCodes.Ldfld, field); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { if (field.ReflectedType.IsValueType) { obj.PushRef (scg, errorAt); } else { obj.PushVal (scg, errorAt); } scg.ilGen.Emit (errorAt, OpCodes.Ldflda, field); } public override void PopPre (ScriptCodeGen scg, Token errorAt) { if (field.ReflectedType.IsValueType) { obj.PushRef (scg, errorAt); } else { obj.PushVal (scg, errorAt); } } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Stfld, field); } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading an // field of a class/struct is trivial public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l; } } // Accessing an element of a fixed-dimension array public class CompValuFixArEl : CompValu { private CompValu baseRVal; private CompValu[] subRVals; private int nSubs; private TokenDeclVar getFunc; private TokenDeclVar setFunc; private TokenTypeInt tokenTypeInt; /** * @brief Set up to access an element of an array. * @param scg = what script we are compiling * @param baseRVal = what array we are accessing * @param subRVals = the subscripts being applied */ public CompValuFixArEl (ScriptCodeGen scg, CompValu baseRVal, CompValu[] subRVals) : base (GetElementType (scg, baseRVal, subRVals)) { this.baseRVal = baseRVal; // location of the array itself this.subRVals = subRVals; // subscript values this.nSubs = subRVals.Length; TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseRVal.type; TokenDeclSDTypeClass sdtDecl = sdtType.decl; tokenTypeInt = new TokenTypeInt (sdtType); TokenName name = new TokenName (sdtType, "Get"); TokenType[] argsig = new TokenType[nSubs]; for (int i = 0; i < nSubs; i ++) { argsig[i] = tokenTypeInt; } getFunc = scg.FindThisMember (sdtDecl, name, argsig); name = new TokenName (sdtType, "Set"); argsig = new TokenType[nSubs+1]; for (int i = 0; i < nSubs; i ++) { argsig[i] = tokenTypeInt; } argsig[nSubs] = getFunc.retType; setFunc = scg.FindThisMember (sdtDecl, name, argsig); } /** * @brief Read array element and push value on stack. */ public override void PushVal (ScriptCodeGen scg, Token errorAt) { // call script-defined class' Get() method to fetch the value baseRVal.PushVal (scg, errorAt); for (int i = 0; i < nSubs; i ++) { subRVals[i].PushVal (scg, errorAt, tokenTypeInt); } scg.ilGen.Emit (errorAt, OpCodes.Call, getFunc.ilGen); } /** * @brief Push address of array element on stack. */ public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("tu stOOpid to get array element address"); } /** * @brief Prepare to write array element. */ public override void PopPre (ScriptCodeGen scg, Token errorAt) { // set up call to script-defined class' Set() method to write the value baseRVal.PushVal (scg, errorAt); for (int i = 0; i < nSubs; i ++) { subRVals[i].PushVal (scg, errorAt, tokenTypeInt); } } /** * @brief Pop value from stack and write array element. */ public override void PopPost (ScriptCodeGen scg, Token errorAt) { // call script-defined class' Set() method to write the value scg.ilGen.Emit (errorAt, OpCodes.Call, setFunc.ilGen); } /** * @brief Get the array element type by getting the Get() functions return type. * Crude but effective. * @param scg = what script we are compiling * @param baseRVal = what array we are accessing * @param subRVals = the subscripts being applied * @returns array element type */ private static TokenType GetElementType (ScriptCodeGen scg, CompValu baseRVal, CompValu[] subRVals) { TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseRVal.type; TokenDeclSDTypeClass sdtDecl = sdtType.decl; TokenName name = new TokenName (sdtType, "Get"); int nSubs = subRVals.Length; TokenType[] argsig = new TokenType[nSubs]; argsig[0] = new TokenTypeInt (sdtType); for (int i = 0; ++ i < nSubs;) { argsig[i] = argsig[0]; } TokenDeclVar getFunc = scg.FindThisMember (sdtDecl, name, argsig); return getFunc.retType; } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading an // fixed-dimension array element is trivial public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l; } } // The value is a float constant public class CompValuFloat : CompValu { public double x; public CompValuFloat (TokenType type, double x) : base (type) { if (!(this.type is TokenTypeFloat)) { this.type = new TokenTypeFloat (this.type); } this.x = x; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, x); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get constant's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into constant"); } } // The value is the entrypoint of a script-defined global function. // These are also used for script-defined type static methods as the calling convention is the same, // ie, the XMRInstance pointer is a hidden first argument. // There is just one of these created when the function is being compiled as there is only one value // of the function. public class CompValuGlobalMeth : CompValu { private TokenDeclVar func; public CompValuGlobalMeth (TokenDeclVar declFunc) : base (declFunc.GetDelType ()) { this.func = declFunc; } /** * @brief PushVal for a function/method means push a delegate on the stack. * We build a call to the DynamicMethod's CreateDelegate() function * to create the delegate. Slip the scriptinstance pointer as the * function's arg 0 so it will get passed to the function when called. */ public override void PushVal (ScriptCodeGen scg, Token errorAt) { string dtn = type.ToString (); if (dtn.StartsWith ("delegate ")) dtn = dtn.Substring (9); // delegateinstance = (signature)scriptinstance.GetScriptMethodDelegate (methName, signature, arg0); // where methName = [.]() // signature = () // arg0 = scriptinstance (XMRInstance) scg.PushXMRInst (); // [0] scriptinstance scg.ilGen.Emit (errorAt, OpCodes.Ldstr, func.ilGen.methName); // [1] method name scg.ilGen.Emit (errorAt, OpCodes.Ldstr, dtn); // [2] delegate type name scg.PushXMRInst (); // [3] scriptinstance scg.ilGen.Emit (errorAt, OpCodes.Callvirt, gsmdMethodInfo); // [0] delegate instance scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // [0] cast to correct delegate class } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get ref to global method"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into global method"); } /** * @brief A direct call is much simpler than pushing a delegate. * Just push the XMRInstance pointer, push the args and finally call the function. */ public override void CallPre (ScriptCodeGen scg, Token errorAt) { if (!this.func.IsFuncTrivial (scg)) new ScriptCodeGen.CallLabel (scg, errorAt); // all script-defined global functions are static methods created by DynamicMethod() // and the first argument is always the XMR_Instance pointer scg.PushXMRInst (); } public override void CallPost (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Call, func.ilGen); if (!this.func.IsFuncTrivial (scg)) scg.openCallLabel = null; } } // The value is in a script-global variable = ScriptModule instance variable // It could also be a script-global property public class CompValuGlobalVar : CompValu { private static readonly FieldInfo glblVarsFieldInfo = typeof (XMRInstAbstract).GetField ("glblVars"); private TokenDeclVar declVar; public CompValuGlobalVar (TokenDeclVar declVar, XMRInstArSizes glblSizes) : base (declVar.type) { this.declVar = declVar; if ((declVar.getProp == null) && (declVar.setProp == null)) { declVar.type.AssignVarSlot (declVar, glblSizes); } } public override void PushVal (ScriptCodeGen scg, Token errorAt) { if ((declVar.getProp == null) && (declVar.setProp == null)) { scg.PushXMRInst (); scg.ilGen.Emit (errorAt, OpCodes.Ldfld, glblVarsFieldInfo); EmitFieldPushVal (scg, errorAt, declVar); } else if (declVar.getProp != null) { declVar.getProp.location.CallPre (scg, errorAt); declVar.getProp.location.CallPost (scg, errorAt); } else { scg.ErrorMsg (errorAt, "property not readable"); scg.PushDefaultValue (declVar.type); } } public override void PushRef (ScriptCodeGen scg, Token errorAt) { if ((declVar.getProp == null) && (declVar.setProp == null)) { scg.PushXMRInst (); scg.ilGen.Emit (errorAt, OpCodes.Ldfld, glblVarsFieldInfo); EmitFieldPushRef (scg, errorAt, declVar); } else { scg.ErrorMsg (errorAt, "cannot get address of property"); } } public override void PopPre (ScriptCodeGen scg, Token errorAt) { if ((declVar.getProp == null) && (declVar.setProp == null)) { scg.PushXMRInst (); scg.ilGen.Emit (errorAt, OpCodes.Ldfld, glblVarsFieldInfo); EmitFieldPopPre (scg, errorAt, declVar); } else if (declVar.setProp == null) { scg.ErrorMsg (errorAt, "property not writable"); } } public override void PopPost (ScriptCodeGen scg, Token errorAt) { if ((declVar.getProp == null) && (declVar.setProp == null)) { EmitFieldPopPost (scg, errorAt, declVar); } else if (declVar.setProp != null) { EmitPopPostProp (scg, errorAt, declVar.type, declVar.setProp.location); } else { scg.ilGen.Emit (errorAt, OpCodes.Pop); } } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading an // global variable is trivial provided it is // not a property or the property function is // trivial. public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l && ((declVar.getProp == null) || declVar.getProp.IsFuncTrivial (scg)); } } // The value is in an $idxprop property of a script-defined type class or interface instance. // Reading and writing is via a method call. public class CompValuIdxProp : CompValu { private TokenDeclVar idxProp; // $idxprop property within baseRVal private CompValu baseRVal; // pointer to class or interface object containing property private TokenType[] argTypes; // argument types as required by $idxprop declaration private CompValu[] indices; // actual index values to pass to getter/setter method private CompValu setProp; // location of setter method public CompValuIdxProp (TokenDeclVar idxProp, CompValu baseRVal, TokenType[] argTypes, CompValu[] indices) : base (idxProp.type) { this.idxProp = idxProp; this.baseRVal = baseRVal; this.argTypes = argTypes; this.indices = indices; } /** * @brief Pushing the property's value is a matter of calling the getter method * with the supplied argument list as is. */ public override void PushVal (ScriptCodeGen scg, Token errorAt) { if (idxProp.getProp != null) { if (!idxProp.getProp.IsFuncTrivial (scg)) { for (int i = indices.Length; -- i >= 0;) { indices[i] = scg.Trivialize (indices[i], errorAt); } } CompValu getProp = GetIdxPropMeth (idxProp.getProp); getProp.CallPre (scg, errorAt); for (int i = 0; i < indices.Length; i ++) { indices[i].PushVal (scg, errorAt, argTypes[i]); } getProp.CallPost (scg, errorAt); } else { // write-only property scg.ErrorMsg (errorAt, "member not readable"); scg.PushDefaultValue (idxProp.type); } } /** * @brief A property does not have a memory address. */ public override void PushRef (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "member has no address"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } /** * @brief Preparing to write a property consists of preparing to call the setter method * then pushing the index arguments. */ public override void PopPre (ScriptCodeGen scg, Token errorAt) { if (idxProp.setProp != null) { if (!idxProp.setProp.IsFuncTrivial (scg)) { for (int i = indices.Length; -- i >= 0;) { indices[i] = scg.Trivialize (indices[i], errorAt); } } this.setProp = GetIdxPropMeth (idxProp.setProp); this.setProp.CallPre (scg, errorAt); for (int i = 0; i < indices.Length; i ++) { indices[i].PushVal (scg, errorAt, argTypes[i]); } } else { // read-only property scg.ErrorMsg (errorAt, "member not writable"); } } /** * @brief Finishing writing a property consists of finishing the call to the setter method * now that the value to be written has been pushed by our caller. */ public override void PopPost (ScriptCodeGen scg, Token errorAt) { if (idxProp.setProp != null) { this.setProp.CallPost (scg, errorAt); } else { scg.ilGen.Emit (errorAt, OpCodes.Pop); } } public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { // if no getter, reading would throw an error, so doesn't really matter what we say if (idxProp.getProp == null) return true; // assume interface methods are always non-trivial because we don't know anything about the actual implementation if (baseRVal.type is TokenTypeSDTypeInterface) return false; // accessing it in any way can't be trivial if reading the pointer isn't trivial if (!baseRVal.IsReadTrivial (scg, readAt)) return false; // likewise with the indices foreach (CompValu idx in indices) { if (!idx.IsReadTrivial (scg, readAt)) return false; } // now the only way it can be non-trivial to read is if the getter() method itself is non-trivial. return idxProp.getProp.IsFuncTrivial (scg); } /** * @brief Get how to call the getter or setter method. */ private CompValu GetIdxPropMeth (TokenDeclVar meth) { if (baseRVal.type is TokenTypeSDTypeClass) { return new CompValuInstMember (meth, baseRVal, false); } return new CompValuIntfMember (meth, baseRVal); } } // This represents the type and location of an internally-defined function // that a script can call public class CompValuInline : CompValu { public TokenDeclInline declInline; public CompValuInline (TokenDeclInline declInline) : base (declInline.GetDelType ()) { this.declInline = declInline; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } public override void PopPre (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot use built-in for delegate, wrap it"); scg.ilGen.Emit (errorAt, OpCodes.Pop); } } // The value is the entrypoint of a script-defined type's interface method combined with // the pointer used to access the method. Thus there is one of these per call site. // They also handle accessing interface properties. public class CompValuIntfMember : CompValu { private TokenDeclVar declVar; private CompValu baseRVal; public CompValuIntfMember (TokenDeclVar declVar, CompValu baseRVal) : base (declVar.type) { if (this.type == null) throw new Exception ("interface member type is null"); this.declVar = declVar; // which element of the baseRVal vector to be accessed this.baseRVal = baseRVal; // the vector of delegates implementing the interface } /** * @brief Reading a method's value means getting a delegate to that method. * Reading a property's value means calling the getter method for that property. */ public override void PushVal (ScriptCodeGen scg, Token errorAt) { if (declVar.retType != null) { baseRVal.PushVal (scg, errorAt); // push pointer to delegate array on stack scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, declVar.vTableIndex); // select which delegate to access scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate)); // push delegate on stack scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // cast to correct delegate class } else if (declVar.getProp != null) { CompValu getProp = new CompValuIntfMember (declVar.getProp, baseRVal); getProp.CallPre (scg, errorAt); // reading property, call its getter getProp.CallPost (scg, errorAt); // ... with no arguments } else { scg.ErrorMsg (errorAt, "member not readable"); scg.PushDefaultValue (declVar.type); } } /** * @brief Can't get the address of either a method or a property. */ public override void PushRef (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "member has no address"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } /** * @brief Can't write a method. * For property, it means calling the setter method for that property. */ public override void PopPre (ScriptCodeGen scg, Token errorAt) { if (declVar.setProp == null) { // read-only property scg.ErrorMsg (errorAt, "member not writable"); } } public override void PopPost (ScriptCodeGen scg, Token errorAt) { if (declVar.setProp != null) { CompValu setProp = new CompValuIntfMember (declVar.setProp, baseRVal); EmitPopPostProp (scg, errorAt, declVar.type, setProp); } else { scg.ilGen.Emit (errorAt, OpCodes.Pop); } } /** * @brief Reading a method (ie, it's delegate) is always trivial, it's just retrieving * an element from the delegate array that make up the interface object. * * Reading a property is always non-trivial because we don't know which implementation * the interface is pointing to, so we don't know if it's trivial or not, so assume * the worst, ie, that it is non-trivial and might call CheckRun(). * * But all that assumes that locating the interface object in the first place is * trivial, ie, baseRVal.PushVal() must not call CheckRun() either. */ public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return baseRVal.IsReadTrivial (scg, readAt) && (declVar.getProp == null); } /** * @brief We just defer to the default CallPre() and CallPost() methods. * They expect this.PushVal() to push a delegate to the method to be called. * If this member is a method, our PushVal() will read the correct element * of the iTable array and push it on the stack, ready for Invoke() to be * called. If this member is a property, the only way it can be called is * if the property is a delegate, in which case PushVal() will retrieve the * delegate by calling the property's getter method. */ } // The value is the entrypoint of an internal instance method // such as XMR_Array.index() public class CompValuIntInstMeth : CompValu { private TokenTypeSDTypeDelegate delType; private CompValu baseRVal; private MethodInfo methInfo; public CompValuIntInstMeth (TokenTypeSDTypeDelegate delType, CompValu baseRVal, MethodInfo methInfo) : base (delType) { this.delType = delType; this.baseRVal = baseRVal; this.methInfo = methInfo; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { // its value, ie, without applying the (arglist), is a delegate... baseRVal.PushVal (scg, errorAt); scg.ilGen.Emit (errorAt, OpCodes.Ldftn, methInfo); scg.ilGen.Emit (errorAt, OpCodes.Newobj, delType.decl.GetConstructorInfo ()); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get ref to instance method"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into instance method"); } public override void CallPre (ScriptCodeGen scg, Token errorAt) { // internal instance methods are always trivial so never need a CallLabel. baseRVal.PushVal (scg, errorAt); } public override void CallPost (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Call, methInfo); } } // The value is fetched by calling an internal instance method // such as XMR_Array.count public class CompValuIntInstROProp : CompValu { private CompValu baseRVal; private MethodInfo methInfo; public CompValuIntInstROProp (TokenType valType, CompValu baseRVal, MethodInfo methInfo) : base (valType) { this.baseRVal = baseRVal; this.methInfo = methInfo; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { baseRVal.PushVal (scg, errorAt); scg.ilGen.Emit (errorAt, OpCodes.Call, methInfo); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot get ref to read-only property"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot store into read-only property"); scg.ilGen.Emit (errorAt, OpCodes.Pop); } } // The value is in a member of a script-defined type class instance. // field: value is in one of the arrays contained within XMRSDTypeClObj.instVars // method: value is a delegate; can be called // property: reading and writing is via a method call public class CompValuInstMember : CompValu { private static readonly FieldInfo instVarsFieldInfo = typeof (XMRSDTypeClObj).GetField ("instVars"); private static readonly FieldInfo vTableFieldInfo = typeof (XMRSDTypeClObj).GetField ("sdtcVTable"); private TokenDeclVar declVar; // member being accessed private CompValu baseRVal; // pointer to particular object instance private bool ignoreVirt; // ignore virtual attribute; use declVar's non-virtual method/property public CompValuInstMember (TokenDeclVar declVar, CompValu baseRVal, bool ignoreVirt) : base (declVar.type) { this.declVar = declVar; this.baseRVal = baseRVal; this.ignoreVirt = ignoreVirt; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { if (declVar.retType != null) { // a method's value, ie, without applying the (arglist), is a delegate... PushValMethod (scg, errorAt); } else if (declVar.vTableArray != null) { // a field's value is its XMRSDTypeClObj.instVars array element baseRVal.PushVal (scg, errorAt); scg.ilGen.Emit (errorAt, OpCodes.Ldfld, instVarsFieldInfo); EmitFieldPushVal (scg, errorAt, declVar); } else if (declVar.getProp != null) { // a property's value is calling its get method with no arguments CompValu getProp = new CompValuInstMember (declVar.getProp, baseRVal, ignoreVirt); getProp.CallPre (scg, errorAt); getProp.CallPost (scg, errorAt); } else { // write-only property scg.ErrorMsg (errorAt, "member not readable"); scg.PushDefaultValue (declVar.type); } } public override void PushRef (ScriptCodeGen scg, Token errorAt) { if (declVar.vTableArray != null) { // a field's value is its XMRSDTypeClObj.instVars array element baseRVal.PushVal (scg, errorAt); scg.ilGen.Emit (errorAt, OpCodes.Ldfld, instVarsFieldInfo); EmitFieldPushRef (scg, errorAt, declVar); } else { scg.ErrorMsg (errorAt, "member has no address"); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } } public override void PopPre (ScriptCodeGen scg, Token errorAt) { if (declVar.vTableArray != null) { // a field's value is its XMRSDTypeClObj.instVars array element baseRVal.PushVal (scg, errorAt); scg.ilGen.Emit (errorAt, OpCodes.Ldfld, instVarsFieldInfo); EmitFieldPopPre (scg, errorAt, declVar); } else if (declVar.setProp == null) { // read-only property scg.ErrorMsg (errorAt, "member not writable"); } } public override void PopPost (ScriptCodeGen scg, Token errorAt) { if (declVar.vTableArray != null) { EmitFieldPopPost (scg, errorAt, declVar); } else if (declVar.setProp != null) { CompValu setProp = new CompValuInstMember (declVar.setProp, baseRVal, ignoreVirt); EmitPopPostProp (scg, errorAt, declVar.type, setProp); } else { scg.ilGen.Emit (errorAt, OpCodes.Pop); } } public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { // accessing it in any way can't be trivial if reading the pointer isn't trivial. // this also handles strict right-to-left mode detection as the side-effect can // only apply to the pointer (it can't change which field or method we access). if (!baseRVal.IsReadTrivial (scg, readAt)) return false; // now the only way it can be non-trivial to read is if it is a property and the // getter() method is non-trivial. reading a method means getting a delegate // which is always trivial, and reading a simple field is always trivial, ie, no // CheckRun() call can possibly be involved. if (declVar.retType != null) { // a method's value, ie, without applying the (arglist), is a delegate... return true; } if (declVar.vTableArray != null) { // a field's value is its XMRSDTypeClObj.instVars array element return true; } if (declVar.getProp != null) { // a property's value is calling its get method with no arguments return declVar.getProp.IsFuncTrivial (scg); } // write-only property return true; } public override void CallPre (ScriptCodeGen scg, Token errorAt) { if (declVar.retType != null) { CallPreMethod (scg, errorAt); } else { base.CallPre (scg, errorAt); } } public override void CallPost (ScriptCodeGen scg, Token errorAt) { if (declVar.retType != null) { CallPostMethod (scg, errorAt); } else { base.CallPost (scg, errorAt); } } /** * @brief A PushVal() for a method means to push a delegate for the method on the stack. */ private void PushValMethod (ScriptCodeGen scg, Token errorAt) { if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) throw new Exception ("dont use for statics"); if (ignoreVirt || (declVar.vTableIndex < 0)) { /* * Non-virtual instance method, create a delegate that references the method. */ string dtn = type.ToString (); // delegateinstance = (signature)scriptinstance.GetScriptMethodDelegate (methName, signature, arg0); // where methName = .() // signature = () // arg0 = sdt istance (XMRSDTypeClObj) 'this' value scg.PushXMRInst (); // [0] scriptinstance scg.ilGen.Emit (errorAt, OpCodes.Ldstr, declVar.ilGen.methName); // [1] method name scg.ilGen.Emit (errorAt, OpCodes.Ldstr, dtn); // [2] delegate type name baseRVal.PushVal (scg, errorAt); // [3] sdtinstance scg.ilGen.Emit (errorAt, OpCodes.Callvirt, gsmdMethodInfo); // [0] delegate instance scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // [0] cast to correct delegate class } else { /* * Virtual instance method, get the delegate from the vtable. */ baseRVal.PushVal (scg, errorAt); // 'this' selecting the instance scg.ilGen.Emit (errorAt, OpCodes.Ldfld, vTableFieldInfo); // get pointer to instance's vtable array scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, declVar.vTableIndex); // select vtable element scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate)); // get delegate pointer = 'this' for 'Invoke()' scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // cast to correct delegate class } } private void CallPreMethod (ScriptCodeGen scg, Token errorAt) { if ((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) throw new Exception ("dont use for statics"); if (!this.declVar.IsFuncTrivial (scg)) new ScriptCodeGen.CallLabel (scg, errorAt); if (ignoreVirt || (declVar.vTableIndex < 0)) { baseRVal.PushVal (scg, errorAt); // 'this' being passed directly to method } else { baseRVal.PushVal (scg, errorAt); // 'this' selecting the instance scg.ilGen.Emit (errorAt, OpCodes.Ldfld, vTableFieldInfo); // get pointer to instance's vtable array scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, declVar.vTableIndex); // select vtable element scg.ilGen.Emit (errorAt, OpCodes.Ldelem, typeof (Delegate)); // get delegate pointer = 'this' for 'Invoke()' scg.ilGen.Emit (errorAt, OpCodes.Castclass, type.ToSysType ()); // cast to correct delegate class } } private void CallPostMethod (ScriptCodeGen scg, Token errorAt) { if (ignoreVirt || (declVar.vTableIndex < 0)) { // non-virt instance, just call function directly scg.ilGen.Emit (errorAt, OpCodes.Call, declVar.ilGen); } else { // virtual, call via delegate Invoke(...) method TokenTypeSDTypeDelegate ttd = (TokenTypeSDTypeDelegate)type; MethodInfo invokeMethodInfo = ttd.decl.GetInvokerInfo (); scg.ilGen.Emit (errorAt, OpCodes.Callvirt, invokeMethodInfo); } if (!this.declVar.IsFuncTrivial (scg)) scg.openCallLabel = null; } } // The value is an integer constant public class CompValuInteger : CompValu { public int x; public CompValuInteger (TokenType type, int x) : base (type) { if (!(this.type is TokenTypeInt)) { this.type = new TokenTypeInt (this.type); } this.x = x; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, x); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get constant's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into constant"); } } // The value is an element of a list public class CompValuListEl : CompValu { private static readonly MethodInfo getElementFromListMethodInfo = typeof (CompValuListEl).GetMethod ("GetElementFromList", new Type[] { typeof (LSL_List), typeof (int) }); private CompValu theList; private CompValu subscript; public CompValuListEl (TokenType type, CompValu theList, CompValu subscript) : base (type) { this.theList = theList; this.subscript = subscript; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { theList.PushVal (scg, errorAt, new TokenTypeList (type)); subscript.PushVal (scg, errorAt, new TokenTypeInt (type)); scg.ilGen.Emit (errorAt, OpCodes.Call, getElementFromListMethodInfo); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get list element's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot store into list element"); scg.ilGen.Emit (errorAt, OpCodes.Pop); } public static object GetElementFromList (LSL_List lis, int idx) { object element = lis.Data[idx]; if (element is LSL_Float) return TypeCast.EHArgUnwrapFloat (element); if (element is LSL_Integer) return TypeCast.EHArgUnwrapInteger (element); if (element is LSL_String) return TypeCast.EHArgUnwrapString (element); if (element is OpenMetaverse.Quaternion) return TypeCast.EHArgUnwrapRotation (element); if (element is OpenMetaverse.Vector3) return TypeCast.EHArgUnwrapVector (element); return element; } } // The value is kept in a script-addressable local variable public class CompValuLocalVar : CompValu { private static int htpopseq = 0; private ScriptMyLocal localBuilder; public CompValuLocalVar (TokenType type, string name, ScriptCodeGen scg) : base (type) { if (type.ToHeapTrackerType () != null) { this.localBuilder = scg.ilGen.DeclareLocal (type.ToHeapTrackerType (), name); scg.PushXMRInst (); scg.ilGen.Emit (type, OpCodes.Newobj, type.GetHeapTrackerCtor ()); scg.ilGen.Emit (type, OpCodes.Stloc, localBuilder); } else { this.localBuilder = scg.ilGen.DeclareLocal (ToSysType (), name); } } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); if (type.ToHeapTrackerType () != null) { type.CallHeapTrackerPushMeth (errorAt, scg.ilGen); } } public override void PushRef (ScriptCodeGen scg, Token errorAt) { if (type.ToHeapTrackerType () != null) { scg.ErrorMsg (errorAt, "can't take ref of heap-tracked type " + type.ToString ()); scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } else { scg.ilGen.Emit (errorAt, OpCodes.Ldloca, localBuilder); } } public override void PopPre (ScriptCodeGen scg, Token errorAt) { if (type.ToHeapTrackerType () != null) { scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); } } public override void PopPost (ScriptCodeGen scg, Token errorAt) { if (type.ToHeapTrackerType () != null) { type.CallHeapTrackerPopMeth (errorAt, scg.ilGen); } else { scg.ilGen.Emit (errorAt, OpCodes.Stloc, localBuilder); } } public void Pop (ScriptCodeGen scg, Token errorAt) { if (type.ToHeapTrackerType () != null) { /* * Popping into a heap tracker wrapped local variable. * First pop value into a temp var, then call the heap tracker's pop method. */ ScriptMyLocal htpop = scg.ilGen.DeclareLocal (type.ToSysType (), "htpop$" + (++ htpopseq).ToString ()); scg.ilGen.Emit (errorAt, OpCodes.Stloc, htpop); scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); scg.ilGen.Emit (errorAt, OpCodes.Ldloc, htpop); type.CallHeapTrackerPopMeth (errorAt, scg.ilGen); } else { /* * Not a heap-tracked local var, just pop directly into it. */ scg.ilGen.Emit (errorAt, OpCodes.Stloc, localBuilder); } } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading a // local variable is trivial. public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l; } } // The value is a null public class CompValuNull : CompValu { public CompValuNull (TokenType type) : base (type) { } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldnull); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get null's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into null"); } } // The value is a rotation public class CompValuRot : CompValu { public CompValu x; public CompValu y; public CompValu z; public CompValu w; private static readonly ConstructorInfo lslRotConstructorInfo = typeof (LSL_Rotation).GetConstructor (new Type[] { typeof (double), typeof (double), typeof (double), typeof (double) }); public CompValuRot (TokenType type, CompValu x, CompValu y, CompValu z, CompValu w) : base (type) { if (!(type is TokenTypeRot)) { this.type = new TokenTypeRot (type); } this.x = x; this.y = y; this.z = z; this.w = w; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { this.x.PushVal (scg, errorAt, new TokenTypeFloat (this.x.type)); this.y.PushVal (scg, errorAt, new TokenTypeFloat (this.y.type)); this.z.PushVal (scg, errorAt, new TokenTypeFloat (this.z.type)); this.w.PushVal (scg, errorAt, new TokenTypeFloat (this.w.type)); scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslRotConstructorInfo); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get constant's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into constant"); } public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { // the supplied values must be trivial because when we call their PushVal()s // there will be stuff on the stack for all but the first PushVal() and so // they would have a non-empty stack at their call label. if (!this.w.IsReadTrivial (scg, readAt) || !this.x.IsReadTrivial (scg, readAt) || !this.y.IsReadTrivial (scg, readAt) || !this.z.IsReadTrivial (scg, readAt)) { throw new Exception ("rotation values must be trivial"); } return true; } } // The value is in a static field of an internally defined struct/class public class CompValuSField : CompValu { public FieldInfo field; public CompValuSField (TokenType type, FieldInfo field) : base (type) { this.field = field; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { if ((field.Attributes & FieldAttributes.Literal) == 0) { scg.ilGen.Emit (errorAt, OpCodes.Ldsfld, field); return; } if (field.FieldType == typeof (LSL_Rotation)) { LSL_Rotation rot = (LSL_Rotation)field.GetValue (null); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.x); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.y); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.z); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, rot.s); scg.ilGen.Emit (errorAt, OpCodes.Newobj, ScriptCodeGen.lslRotationConstructorInfo); return; } if (field.FieldType == typeof (LSL_Vector)) { LSL_Vector vec = (LSL_Vector)field.GetValue (null); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, vec.x); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, vec.y); scg.ilGen.Emit (errorAt, OpCodes.Ldc_R8, vec.z); scg.ilGen.Emit (errorAt, OpCodes.Newobj, ScriptCodeGen.lslRotationConstructorInfo); return; } if (field.FieldType == typeof (string)) { string str = (string)field.GetValue (null); scg.ilGen.Emit (errorAt, OpCodes.Ldstr, str); return; } throw new Exception ("unsupported literal type " + field.FieldType.Name); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { if ((field.Attributes & FieldAttributes.Literal) != 0) { throw new Exception ("can't write a constant"); } scg.ilGen.Emit (errorAt, OpCodes.Ldflda, field); } public override void PopPre (ScriptCodeGen scg, Token errorAt) { } public override void PopPost (ScriptCodeGen scg, Token errorAt) { if ((field.Attributes & FieldAttributes.Literal) != 0) { throw new Exception ("can't write a constant"); } scg.ilGen.Emit (errorAt, OpCodes.Stsfld, field); } // non-trivial because it needs to be copied into a temp // in case the idiot does dumb-ass side effects tricks // eg, (x = 0) + x + 2 // should read old value of x not 0 // but if 'xmroption norighttoleft;' in effect, // we can read it in any order so reading a // local variable is trivial. public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { return readAt.nr2l; } } // The value is a character within a string public class CompValuStrChr : CompValu { private static readonly MethodInfo getCharFromStringMethodInfo = typeof (CompValuStrChr).GetMethod ("GetCharFromString", new Type[] { typeof (string), typeof (int) }); private CompValu theString; private CompValu subscript; public CompValuStrChr (TokenType type, CompValu theString, CompValu subscript) : base (type) { this.theString = theString; this.subscript = subscript; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { theString.PushVal (scg, errorAt, new TokenTypeStr (type)); subscript.PushVal (scg, errorAt, new TokenTypeInt (type)); scg.ilGen.Emit (errorAt, OpCodes.Call, getCharFromStringMethodInfo); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get string character's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ErrorMsg (errorAt, "cannot store into string character"); scg.ilGen.Emit (errorAt, OpCodes.Pop); } public static char GetCharFromString (string s, int i) { return s[i]; } } // The value is a key or string constant public class CompValuString : CompValu { public string x; public CompValuString (TokenType type, string x) : base (type) { if (!(type is TokenTypeKey) && !(this.type is TokenTypeStr)) { throw new Exception ("bad type " + type.ToString ()); } this.x = x; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldstr, x); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get constant's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into constant"); } } // The value is kept in a temp local variable public class CompValuTemp : CompValu { public ScriptMyLocal localBuilder; public CompValuTemp (TokenType type, ScriptCodeGen scg) : base (type) { string name = "tmp$" + (++ scg.tempCompValuNum); this.localBuilder = scg.ilGen.DeclareLocal (ToSysType(), name); } protected CompValuTemp (TokenType type) : base (type) { } // CompValuVoid uses this public override void PushVal (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldloc, localBuilder); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Ldloca, localBuilder); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { scg.ilGen.Emit (errorAt, OpCodes.Stloc, localBuilder); } public void Pop (ScriptCodeGen scg, Token errorAt, TokenType stackType) { TypeCast.CastTopOfStack (scg, errorAt, stackType, this.type, false); this.PopPost (scg, errorAt); // in case PopPost() overridden eg by CompValuVoid } public void Pop (ScriptCodeGen scg, Token errorAt) { this.PopPost (scg, errorAt); // in case PopPost() overridden eg by CompValuVoid } } // The value is a vector public class CompValuVec : CompValu { public CompValu x; public CompValu y; public CompValu z; private static readonly ConstructorInfo lslVecConstructorInfo = typeof (LSL_Vector).GetConstructor (new Type[] { typeof (double), typeof (double), typeof (double) }); public CompValuVec (TokenType type, CompValu x, CompValu y, CompValu z) : base (type) { if (!(type is TokenTypeVec)) { this.type = new TokenTypeVec (type); } this.x = x; this.y = y; this.z = z; } public override void PushVal (ScriptCodeGen scg, Token errorAt) { this.x.PushVal (scg, errorAt, new TokenTypeFloat (this.x.type)); this.y.PushVal (scg, errorAt, new TokenTypeFloat (this.y.type)); this.z.PushVal (scg, errorAt, new TokenTypeFloat (this.z.type)); scg.ilGen.Emit (errorAt, OpCodes.Newobj, lslVecConstructorInfo); } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get constant's address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot store into constant"); } public override bool IsReadTrivial (ScriptCodeGen scg, Token readAt) { // the supplied values must be trivial because when we call their PushVal()s // there will be stuff on the stack for all but the first PushVal() and so // they would have a non-empty stack at their call label. if (!this.x.IsReadTrivial (scg, readAt) || !this.y.IsReadTrivial (scg, readAt) || !this.z.IsReadTrivial (scg, readAt)) { throw new Exception ("vector values must be trivial"); } return true; } } // Used to indicate value will be discarded (eg, where to put return value from a call) public class CompValuVoid : CompValuTemp { public CompValuVoid (Token token) : base ((token is TokenTypeVoid) ? (TokenTypeVoid)token : new TokenTypeVoid (token)) { } public override void PushVal (ScriptCodeGen scg, Token errorAt) { } public override void PushRef (ScriptCodeGen scg, Token errorAt) { throw new Exception ("cannot get void address"); } public override void PopPost (ScriptCodeGen scg, Token errorAt) { } } }