/// <summary> /// Generates CIL for a compound assignment expression. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="target"> The target to modify. </param> private void GenerateCompoundAssignment(ILGenerator generator, OptimizationInfo optimizationInfo, IReferenceExpression target) { // Evaluate the left hand side only once. target.GenerateReference(generator, optimizationInfo); target.DuplicateReference(generator, optimizationInfo); // For the GenerateSet, later on. // Load the value to assign. var compoundOperator = new BinaryExpression(GetCompoundBaseOperator(this.OperatorType), new ReferenceGetExpression(target), this.GetOperand(1)); compoundOperator.GenerateCode(generator, optimizationInfo); ILLocalVariable result = null; if (optimizationInfo.IgnoreReturnValue != this) { // Store the resulting value so we can return it as the result of the expression. result = generator.CreateTemporaryVariable(compoundOperator.ResultType); generator.Duplicate(); generator.StoreVariable(result); } // Store the value. target.GenerateSet(generator, optimizationInfo, compoundOperator.ResultType); if (optimizationInfo.IgnoreReturnValue != this) { // Restore the expression result. generator.LoadVariable(result); generator.ReleaseTemporaryVariable(result); } else { optimizationInfo.ReturnValueWasNotGenerated = true; } }
/// <summary> /// Generates CIL for an assignment expression. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="target"> The target to modify. </param> private void GenerateAssignment(ILGenerator generator, OptimizationInfo optimizationInfo, IReferenceExpression target) { // Evaluate the left hand side first! target.GenerateReference(generator, optimizationInfo); // Load the value to assign. var rhs = this.GetOperand(1); rhs.GenerateCode(generator, optimizationInfo); ILLocalVariable result = null; if (optimizationInfo.IgnoreReturnValue != this) { // Store the RHS value so we can return it as the result of the expression. result = generator.CreateTemporaryVariable(rhs.ResultType); generator.Duplicate(); generator.StoreVariable(result); } // Store the value. target.GenerateSet(generator, optimizationInfo, rhs.ResultType); if (optimizationInfo.IgnoreReturnValue != this) { // Restore the RHS value. generator.LoadVariable(result); generator.ReleaseTemporaryVariable(result); } else { optimizationInfo.ReturnValueWasNotGenerated = true; } }
/// <summary> /// Pops the value from the top of the stack and stores it in the given local variable. /// </summary> /// <param name="variable"> The variable to store the value. </param> public override void StoreVariable(ILLocalVariable variable) { if (variable as ReflectionEmitILLocalVariable == null) { throw new ArgumentNullException(nameof(variable)); } this.generator.Emit(OpCodes.Stloc, ((ReflectionEmitILLocalVariable)variable).UnderlyingLocal); }
/// <summary> /// Pushes the address of the given variable onto the stack. /// </summary> /// <param name="variable"> The variable whose address will be pushed. </param> public override void LoadAddressOfVariable(ILLocalVariable variable) { if (variable as ReflectionEmitILLocalVariable == null) { throw new ArgumentNullException("variable"); } this.generator.Emit(OpCodes.Ldloca, ((ReflectionEmitILLocalVariable)variable).UnderlyingLocal); }
/// <summary> /// Pushes the value of the reference onto the stack. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="throwIfUnresolvable"> <c>true</c> to throw a ReferenceError exception if /// the name is unresolvable; <c>false</c> to output <c>null</c> instead. </param> public void GenerateGet(ILGenerator generator, OptimizationInfo optimizationInfo, bool throwIfUnresolvable) { string propertyName = null; TypeOfMemberAccess memberAccessType = DetermineTypeOfMemberAccess(optimizationInfo, out propertyName); if (memberAccessType == TypeOfMemberAccess.ArrayIndex) { // Array indexer // ------------- // xxx = object[index] // Call the indexer. generator.Call(ReflectionHelpers.ObjectInstance_Indexer_UInt); } else if (memberAccessType == TypeOfMemberAccess.Static) { // Named property access (e.g. x = y.property) // ------------------------------------------- // Use a PropertyReference to speed up access if we are inside a loop. if (optimizationInfo.InsideLoop) { // C# // if (propertyReference != null) // propertyReference = new PropertyReference("property"); // value = object.GetPropertyValue(propertyReference) ILLocalVariable propertyReference = optimizationInfo.GetPropertyReferenceVariable(generator, propertyName); generator.LoadVariable(propertyReference); generator.Duplicate(); var afterIf = generator.CreateLabel(); generator.BranchIfNotNull(afterIf); generator.Pop(); generator.LoadString(propertyName); generator.NewObject(ReflectionHelpers.PropertyName_Constructor); generator.Duplicate(); generator.StoreVariable(propertyReference); generator.DefineLabelPosition(afterIf); generator.Call(ReflectionHelpers.ObjectInstance_GetPropertyValue_PropertyReference); } else { // C# // value = object.GetPropertyValue("property") generator.LoadString(propertyName); generator.Call(ReflectionHelpers.ObjectInstance_Indexer_Object); } } else { // Dynamic property access // ----------------------- // x = y.GetPropertyValue("property") generator.Call(ReflectionHelpers.ObjectInstance_Indexer_Object); } }
/// <summary> /// Pops the value from the top of the stack and stores it in the given local variable. /// </summary> /// <param name="variable"> The variable to store the value. </param> public abstract void StoreVariable(ILLocalVariable variable);
/// <summary> /// Pushes the value of the given variable onto the stack. /// </summary> /// <param name="variable"> The variable whose value will be pushed. </param> public abstract void LoadVariable(ILLocalVariable variable);
/// <summary> /// Indicates that the given temporary variable is no longer needed. /// </summary> /// <param name="variable"> The temporary variable created using CreateTemporaryVariable(). </param> public void ReleaseTemporaryVariable(ILLocalVariable temporaryVariable) { if (temporaryVariable == null) throw new ArgumentNullException("temporaryVariable"); if (this.temporaryVariables == null) this.temporaryVariables = new List<ILLocalVariable>(); this.temporaryVariables.Add(temporaryVariable); }
/// <summary> /// Pops the value from the top of the stack and stores it in the given local variable. /// </summary> /// <param name="variable"> The variable to store the value. </param> public override void StoreVariable(ILLocalVariable variable) { if (variable as ReflectionEmitILLocalVariable == null) throw new ArgumentNullException("variable"); this.generator.Emit(OpCodes.Stloc, ((ReflectionEmitILLocalVariable)variable).UnderlyingLocal); }
/// <summary> /// Pushes the address of the given variable onto the stack. /// </summary> /// <param name="variable"> The variable whose address will be pushed. </param> public abstract void LoadAddressOfVariable(ILLocalVariable variable);
/// <summary> /// Pops the value from the top of the stack and stores it in the given local variable. /// </summary> /// <param name="variable"> The variable to store the value. </param> public override void StoreVariable(ILLocalVariable variable) { if (variable as DynamicILLocalVariable == null) throw new ArgumentNullException("variable"); if (((DynamicILLocalVariable)variable).ILGenerator != this) throw new ArgumentException("The variable wasn't created by this generator.", "variable"); if (variable.Index <= 3) { // stloc.0 = 0A // stloc.1 = 0B // stloc.2 = 0C // stloc.3 = 0D Emit1ByteOpCode((byte)(0x0A + variable.Index), 1, 0); } else if (variable.Index < 256) { // stloc.s index = 13 <unsigned int8> Emit1ByteOpCodeInt8(0x13, 1, 0, variable.Index); } else if (variable.Index < 65535) { // stloc index = FE 0E <unsigned int16> Emit2ByteOpCodeInt16(0xFE, 0x0E, 1, 0, variable.Index); } else throw new InvalidOperationException("Too many local variables."); PopStackOperands(ToVESType(variable.Type)); }
/// <summary> /// Stores the value on the top of the stack in the reference. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="valueType"> The primitive type of the value that is on the top of the stack. </param> /// <param name="throwIfUnresolvable"> <c>true</c> to throw a ReferenceError exception if /// the name is unresolvable; <c>false</c> to create a new property instead. </param> public void GenerateSet(ILGenerator generator, OptimizationInfo optimizationInfo, PrimitiveType valueType, bool throwIfUnresolvable) { // The value is initially on the top of the stack but is stored in this variable // at the last possible moment. ILLocalVariable value = null; var scope = this.Scope; ILLocalVariable scopeVariable = null; var endOfSet = generator.CreateLabel(); do { if (scope is DeclarativeScope) { // Get information about the variable. var variable = scope.GetDeclaredVariable(this.Name); if (variable != null) { // The variable was declared in this scope. if (scope.ExistsAtRuntime == false) { // The scope has been optimized away. The value of the variable is stored // in an ILVariable. // Declare an IL local variable if no storage location has been allocated yet. if (variable.Store == null) { variable.Store = generator.DeclareVariable(typeof(object), variable.Name); } if (value == null) { // The value to store is on the top of the stack - convert it to the // storage type of the variable. EmitConversion.Convert(generator, valueType, variable.Type, optimizationInfo); } else { // The value to store is in a temporary variable. generator.LoadVariable(value); EmitConversion.Convert(generator, PrimitiveType.Any, variable.Type, optimizationInfo); } // Store the value in the variable. generator.StoreVariable(variable.Store); } else if (variable.Writable == true) { if (value == null) { // The value to store is on the top of the stack - convert it to an // object and store it in a temporary variable. EmitConversion.Convert(generator, valueType, PrimitiveType.Any, optimizationInfo); value = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(value); } // scope.Values[index] = value if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.Call(ReflectionHelpers.DeclarativeScope_Values); generator.LoadInt32(variable.Index); generator.LoadVariable(value); generator.StoreArrayElement(typeof(object)); } else { // The variable exists, but is read-only. // Pop the value off the stack (if it is still there). if (value == null) { generator.Pop(); } } // The variable was found - no need to search any more parent scopes. break; } else { // The variable was not defined at compile time, but may have been // introduced by an eval() statement. if (optimizationInfo.MethodOptimizationHints.HasEval == true) { if (value == null) { // The value to store is on the top of the stack - convert it to an // object and store it in a temporary variable. EmitConversion.Convert(generator, valueType, PrimitiveType.Any, optimizationInfo); value = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(value); } // Check the variable exists: if (scope.HasValue(variableName) == true) { if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_HasValue); var hasValueClause = generator.CreateLabel(); generator.BranchIfFalse(hasValueClause); // Set the value of the variable. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.LoadVariable(value); generator.Call(ReflectionHelpers.Scope_SetValue); generator.Branch(endOfSet); // } generator.DefineLabelPosition(hasValueClause); } } } else { if (value == null) { // The value to store is on the top of the stack - convert it to an // object and store it in a temporary variable. EmitConversion.Convert(generator, valueType, PrimitiveType.Any, optimizationInfo); value = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(value); } if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); if (scope.ParentScope == null && throwIfUnresolvable == false) { // Sets the value of a global variable. // JS: object.property = value // C# ==> // if (propertyName == null) // propertyName = new PropertyName("property"); // object.SetPropertyValue(propertyName, value, strictMode); ILLocalVariable propertyName = optimizationInfo.GetGlobalPropertyReferenceVariable(generator, this.Name); generator.LoadVariable(propertyName); generator.Duplicate(); var afterIf = generator.CreateLabel(); generator.BranchIfNotNull(afterIf); generator.Pop(); generator.LoadString(this.Name); generator.NewObject(ReflectionHelpers.PropertyName_Constructor); generator.Duplicate(); generator.StoreVariable(propertyName); generator.DefineLabelPosition(afterIf); generator.LoadVariable(value); generator.LoadBoolean(optimizationInfo.StrictMode); generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValue_PropertyReference); } else { // Setting a variable within a "with" scope. generator.LoadString(this.Name); generator.LoadVariable(value); generator.LoadBoolean(optimizationInfo.StrictMode); // Set the property value if the property exists. generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValueIfExists); // The return value is true if the property was defined, and false if it wasn't. generator.BranchIfTrue(endOfSet); } } // Try the parent scope. if (scope.ParentScope != null && scope.ExistsAtRuntime == true) { if (scopeVariable == null) { scopeVariable = generator.CreateTemporaryVariable(typeof(Scope)); EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.Call(ReflectionHelpers.Scope_ParentScope); generator.StoreVariable(scopeVariable); } scope = scope.ParentScope; } while (scope != null); // The value might be still on top of the stack. if (value == null && scope == null) { generator.Pop(); } // Throw an error if the name does not exist and throwIfUnresolvable is true. if (scope == null && throwIfUnresolvable == true) { EmitHelpers.EmitThrow(generator, ErrorType.ReferenceError, this.Name + " is not defined", optimizationInfo); } // Release the temporary variables. if (value != null) { generator.ReleaseTemporaryVariable(value); } if (scopeVariable != null) { generator.ReleaseTemporaryVariable(scopeVariable); } // Define a label at the end. generator.DefineLabelPosition(endOfSet); }
/// <summary> /// Generates CIL for an increment or decrement expression. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="target"> The target to modify. </param> /// <param name="postfix"> <c>true</c> if this is the postfix version of the operator; /// <c>false</c> otherwise. </param> /// <param name="increment"> <c>true</c> if this is the increment operator; <c>false</c> if /// this is the decrement operator. </param> private void GenerateIncrementOrDecrement(ILGenerator generator, OptimizationInfo optimizationInfo, IReferenceExpression target, bool postfix, bool increment) { // Note: increment and decrement can produce a number that is out of range if the // target is of type Int32. The only time this should happen is for a loop variable // where the range has been carefully checked to make sure an out of range condition // cannot happen. // Evaluate the left hand side only once. target.GenerateReference(generator, optimizationInfo); target.DuplicateReference(generator, optimizationInfo); // For the GenerateSet, later on. // Get the target value. target.GenerateGet(generator, optimizationInfo, true); // Convert it to a number. if (target.Type != PrimitiveType.Int32) { EmitConversion.ToNumber(generator, target.Type); } ILLocalVariable result = null; if (optimizationInfo.IgnoreReturnValue != this && postfix == true) { // If this is PostIncrement or PostDecrement, store the value so it can be returned later. result = generator.CreateTemporaryVariable(target.Type == PrimitiveType.Int32 ? PrimitiveType.Int32 : PrimitiveType.Number); generator.Duplicate(); generator.StoreVariable(result); } // Load the increment constant. if (target.Type == PrimitiveType.Int32) { generator.LoadInt32(1); } else { generator.LoadDouble(1.0); } // Add or subtract the constant to the target value. if (increment == true) { generator.Add(); } else { generator.Subtract(); } if (optimizationInfo.IgnoreReturnValue != this && postfix == false) { // If this is PreIncrement or PreDecrement, store the value so it can be returned later. result = generator.CreateTemporaryVariable(target.Type == PrimitiveType.Int32 ? PrimitiveType.Int32 : PrimitiveType.Number); generator.Duplicate(); generator.StoreVariable(result); } // Store the value. target.GenerateSet(generator, optimizationInfo, target.Type == PrimitiveType.Int32 ? PrimitiveType.Int32 : PrimitiveType.Number); if (optimizationInfo.IgnoreReturnValue != this) { // Restore the expression result. generator.LoadVariable(result); generator.ReleaseTemporaryVariable(result); } else { optimizationInfo.ReturnValueWasNotGenerated = true; } }
/// <summary> /// Pushes the address of the given variable onto the stack. /// </summary> /// <param name="variable"> The variable whose address will be pushed. </param> public override void LoadAddressOfVariable(ILLocalVariable variable) { if (variable as DynamicILLocalVariable == null) throw new ArgumentNullException("variable"); if (((DynamicILLocalVariable)variable).ILGenerator != this) throw new ArgumentException("The variable wasn't created by this generator.", "variable"); if (variable.Index < 256) { // ldloca.s index = 12 <unsigned int8> Emit1ByteOpCodeInt8(0x12, 0, 1, variable.Index); } else if (variable.Index < 65535) { // ldloca index = FE 0D <unsigned int16> Emit2ByteOpCodeInt16(0xFE, 0x0D, 0, 1, variable.Index); } else throw new InvalidOperationException("Too many local variables."); PushStackOperand(VESType.ManagedPointer); }
/// <summary> /// Stores the value on the top of the stack in the reference. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="valueType"> The primitive type of the value that is on the top of the stack. </param> /// <param name="throwIfUnresolvable"> <c>true</c> to throw a ReferenceError exception if /// the name is unresolvable; <c>false</c> to create a new property instead. </param> public void GenerateSet(ILGenerator generator, OptimizationInfo optimizationInfo, PrimitiveType valueType, bool throwIfUnresolvable) { // The value is initially on the top of the stack but is stored in this variable // at the last possible moment. ILLocalVariable value = null; var scope = this.Scope; ILLocalVariable scopeVariable = null; var endOfSet = generator.CreateLabel(); do { if (scope is DeclarativeScope) { // Get information about the variable. var variable = scope.GetDeclaredVariable(this.Name); if (variable != null) { // The variable was declared in this scope. if (scope.ExistsAtRuntime == false) { // The scope has been optimized away. The value of the variable is stored // in an ILVariable. // Declare an IL local variable if no storage location has been allocated yet. if (variable.Store == null) { variable.Store = generator.DeclareVariable(typeof(object), variable.Name); } if (value == null) { // The value to store is on the top of the stack - convert it to the // storage type of the variable. EmitConversion.Convert(generator, valueType, variable.Type, optimizationInfo); } else { // The value to store is in a temporary variable. generator.LoadVariable(value); EmitConversion.Convert(generator, PrimitiveType.Any, variable.Type, optimizationInfo); } // Store the value in the variable. generator.StoreVariable(variable.Store); } else if (variable.Writable == true) { if (value == null) { // The value to store is on the top of the stack - convert it to an // object and store it in a temporary variable. EmitConversion.Convert(generator, valueType, PrimitiveType.Any, optimizationInfo); value = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(value); } // scope.Values[index] = value if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.Call(ReflectionHelpers.DeclarativeScope_Values); generator.LoadInt32(variable.Index); generator.LoadVariable(value); generator.StoreArrayElement(typeof(object)); } else { // The variable exists, but is read-only. // Pop the value off the stack (if it is still there). if (value == null) { generator.Pop(); } } // The variable was found - no need to search any more parent scopes. break; } else { // The variable was not defined at compile time, but may have been // introduced by an eval() statement. if (optimizationInfo.MethodOptimizationHints.HasEval == true) { if (value == null) { // The value to store is on the top of the stack - convert it to an // object and store it in a temporary variable. EmitConversion.Convert(generator, valueType, PrimitiveType.Any, optimizationInfo); value = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(value); } // Check the variable exists: if (scope.HasValue(variableName) == true) { if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_HasValue); var hasValueClause = generator.CreateLabel(); generator.BranchIfFalse(hasValueClause); // Set the value of the variable. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.LoadVariable(value); generator.Call(ReflectionHelpers.Scope_SetValue); generator.Branch(endOfSet); // } generator.DefineLabelPosition(hasValueClause); } } } else { if (value == null) { // The value to store is on the top of the stack - convert it to an // object and store it in a temporary variable. EmitConversion.Convert(generator, valueType, PrimitiveType.Any, optimizationInfo); value = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(value); } if (scope.ParentScope == null) { // Optimization: if this is the global scope, use hidden classes to // optimize variable access. // Global variable modification // ---------------------------- // __object_cacheKey = null; // __object_property_cachedIndex = 0; // ... // if (__object_cacheKey != object.InlineCacheKey) // object.InlineSetPropertyValueIfExists("property", value, strictMode, out __object_property_cachedIndex, out __object_cacheKey) // else // object.InlinePropertyValues[__object_property_cachedIndex] = value; // Get a reference to the global object. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); // TODO: share these variables somehow. var cacheKey = generator.DeclareVariable(typeof(object)); var cachedIndex = generator.DeclareVariable(typeof(int)); // Store the object into a temp variable. var objectInstance = generator.DeclareVariable(PrimitiveType.Object); generator.StoreVariable(objectInstance); // if (__object_cacheKey != object.InlineCacheKey) generator.LoadVariable(cacheKey); generator.LoadVariable(objectInstance); generator.Call(ReflectionHelpers.ObjectInstance_InlineCacheKey); var elseClause = generator.CreateLabel(); generator.BranchIfEqual(elseClause); // xxx = object.InlineSetPropertyValueIfExists("property", value, strictMode, out __object_property_cachedIndex, out __object_cacheKey) generator.LoadVariable(objectInstance); generator.LoadString(this.Name); generator.LoadVariable(value); generator.LoadBoolean(optimizationInfo.StrictMode); generator.LoadAddressOfVariable(cachedIndex); generator.LoadAddressOfVariable(cacheKey); if (throwIfUnresolvable == false) { // Set the property value unconditionally. generator.Call(ReflectionHelpers.ObjectInstance_InlineSetPropertyValue); } else { // Set the property value if the property exists. generator.Call(ReflectionHelpers.ObjectInstance_InlineSetPropertyValueIfExists); // The return value is true if the property was defined, and false if it wasn't. generator.BranchIfTrue(endOfSet); } var endOfIf = generator.CreateLabel(); generator.Branch(endOfIf); // else generator.DefineLabelPosition(elseClause); // object.InlinePropertyValues[__object_property_cachedIndex] = value; generator.LoadVariable(objectInstance); generator.Call(ReflectionHelpers.ObjectInstance_InlinePropertyValues); generator.LoadVariable(cachedIndex); generator.LoadVariable(value); generator.StoreArrayElement(typeof(object)); generator.Branch(endOfSet); // End of the if statement generator.DefineLabelPosition(endOfIf); } else { // Slow route. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); generator.LoadString(this.Name); generator.LoadVariable(value); generator.LoadBoolean(optimizationInfo.StrictMode); if (scope.ParentScope == null && throwIfUnresolvable == false) { // Set the property value unconditionally. generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValue_Object); } else { // Set the property value if the property exists. generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValueIfExists); // The return value is true if the property was defined, and false if it wasn't. generator.BranchIfTrue(endOfSet); } } } // Try the parent scope. if (scope.ParentScope != null && scope.ExistsAtRuntime == true) { if (scopeVariable == null) { scopeVariable = generator.CreateTemporaryVariable(typeof(Scope)); EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.Call(ReflectionHelpers.Scope_ParentScope); generator.StoreVariable(scopeVariable); } scope = scope.ParentScope; } while (scope != null); // The value might be still on top of the stack. if (value == null && scope == null) { generator.Pop(); } // Throw an error if the name does not exist and throwIfUnresolvable is true. if (scope == null && throwIfUnresolvable == true) { EmitHelpers.EmitThrow(generator, ErrorType.ReferenceError, this.Name + " is not defined", optimizationInfo); } // Release the temporary variables. if (value != null) { generator.ReleaseTemporaryVariable(value); } if (scopeVariable != null) { generator.ReleaseTemporaryVariable(scopeVariable); } // Define a label at the end. generator.DefineLabelPosition(endOfSet); }
/// <summary> /// Generates code to push the "this" value for a function call. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> public void GenerateThis(ILGenerator generator) { // Optimization: if there are no with scopes, simply emit undefined. bool scopeChainHasWithScope = false; var scope = this.Scope; do { if (scope is ObjectScope && ((ObjectScope)scope).ProvidesImplicitThisValue == true) { scopeChainHasWithScope = true; break; } scope = scope.ParentScope; } while (scope != null); if (scopeChainHasWithScope == false) { // No with scopes in the scope chain, use undefined as the "this" value. EmitHelpers.EmitUndefined(generator); return; } var end = generator.CreateLabel(); scope = this.Scope; ILLocalVariable scopeVariable = generator.CreateTemporaryVariable(typeof(Scope)); EmitHelpers.LoadScope(generator); generator.StoreVariable(scopeVariable); do { if (scope is DeclarativeScope) { if (scope.HasDeclaredVariable(this.Name)) { // The variable exists but declarative scopes always produce undefined for // the "this" value. EmitHelpers.EmitUndefined(generator); break; } } else { var objectScope = (ObjectScope)scope; // Check if the property exists by calling scope.ScopeObject.HasProperty(propertyName) if (objectScope.ProvidesImplicitThisValue == false) { EmitHelpers.EmitUndefined(generator); } generator.LoadVariable(scopeVariable); generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); if (objectScope.ProvidesImplicitThisValue == true) { generator.Duplicate(); } generator.LoadString(this.Name); generator.Call(ReflectionHelpers.ObjectInstance_HasProperty); generator.BranchIfTrue(end); generator.Pop(); // If the name is not defined, use undefined for the "this" value. if (scope.ParentScope == null) { EmitHelpers.EmitUndefined(generator); } } // Try the parent scope. if (scope.ParentScope != null && scope.ExistsAtRuntime == true) { generator.LoadVariable(scopeVariable); generator.Call(ReflectionHelpers.Scope_ParentScope); generator.StoreVariable(scopeVariable); } scope = scope.ParentScope; } while (scope != null); // Release the temporary variable. generator.ReleaseTemporaryVariable(scopeVariable); // Define a label at the end. generator.DefineLabelPosition(end); }
/// <summary> /// Pops the value from the top of the stack and stores it in the given local variable. /// </summary> /// <param name="variable"> The variable to store the value. </param> public override void StoreVariable(ILLocalVariable variable) { Log("stloc", variable); this.generator.StoreVariable(variable); }
/// <summary> /// Pushes the address of the given variable onto the stack. /// </summary> /// <param name="variable"> The variable whose address will be pushed. </param> public override void LoadAddressOfVariable(ILLocalVariable variable) { Log("ldloca", variable); this.generator.LoadAddressOfVariable(variable); }
/// <summary> /// Outputs an instruction and a variable to the log. /// </summary> /// <param name="instruction"> The instruction to output. </param> /// <param name="variable"> The variable to output. </param> private void Log(string instruction, ILLocalVariable variable) { if (variable.Name == null) LogInstruction(instruction, string.Format("V{0}", variable.Index)); else LogInstruction(instruction, string.Format("V{0} ({1})", variable.Index, variable.Name)); }
/// <summary> /// Generates CIL for the expression. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Check if this is a direct call to eval(). if (this.Target is NameExpression && ((NameExpression)this.Target).Name == "eval") { GenerateEval(generator, optimizationInfo); return; } // Check if this is a super() call. if (this.Target is SuperExpression) { // executionContext.CallSuperClass(arguments) EmitHelpers.LoadExecutionContext(generator); GenerateArgumentsArray(generator, optimizationInfo); generator.Call(ReflectionHelpers.ExecutionContext_CallSuperClass); return; } // Emit the function instance first. ILLocalVariable targetBase = null; if (this.Target is MemberAccessExpression) { // The function is a member access expression (e.g. "Math.cos()"). // Evaluate the left part of the member access expression. var baseExpression = ((MemberAccessExpression)this.Target).Base; baseExpression.GenerateCode(generator, optimizationInfo); EmitConversion.ToAny(generator, baseExpression.ResultType); targetBase = generator.CreateTemporaryVariable(typeof(object)); generator.StoreVariable(targetBase); // Evaluate the right part of the member access expression. var memberAccessExpression = new MemberAccessExpression(((MemberAccessExpression)this.Target).Operator); memberAccessExpression.Push(new TemporaryVariableExpression(targetBase)); memberAccessExpression.Push(((MemberAccessExpression)this.Target).GetOperand(1)); memberAccessExpression.GenerateCode(generator, optimizationInfo); EmitConversion.ToAny(generator, this.Target.ResultType); } else { // Something else (e.g. "my_func()"). this.Target.GenerateCode(generator, optimizationInfo); EmitConversion.ToAny(generator, this.Target.ResultType); } // Check the object really is a function - if not, throw an exception. generator.Duplicate(); generator.IsInstance(typeof(FunctionInstance)); var endOfTypeCheck = generator.CreateLabel(); generator.BranchIfTrue(endOfTypeCheck); // Throw an nicely formatted exception. generator.Pop(); EmitHelpers.EmitThrow(generator, ErrorType.TypeError, string.Format("'{0}' is not a function", this.Target.ToString()), optimizationInfo); generator.DefineLabelPosition(endOfTypeCheck); // Pass in the path, function name and line. generator.ReinterpretCast(typeof(FunctionInstance)); generator.LoadStringOrNull(optimizationInfo.Source.Path); generator.LoadStringOrNull(optimizationInfo.FunctionName); generator.LoadInt32(optimizationInfo.SourceSpan.StartLine); // Generate code to produce the "this" value. There are three cases. if (this.Target is NameExpression) { // 1. The function is a name expression (e.g. "parseInt()"). // If we are inside a with() block, then there is an implicit 'this' value, // otherwise 'this' is undefined. Scope.GenerateReference(generator, optimizationInfo); generator.Call(ReflectionHelpers.RuntimeScope_ImplicitThis); } else if (this.Target is MemberAccessExpression targetMemberAccessExpression) { // 2. The function is a member access expression (e.g. "Math.cos()"). // In this case this = Math. // Unless it's a super call like super.blah(). if (targetMemberAccessExpression.Base is SuperExpression) { EmitHelpers.LoadThis(generator); } else { generator.LoadVariable(targetBase); } } else { // 3. Neither of the above (e.g. "(function() { return 5 })()") // In this case this = undefined. EmitHelpers.EmitUndefined(generator); } // Emit an array containing the function arguments. GenerateArgumentsArray(generator, optimizationInfo); // Call FunctionInstance.CallLateBound(thisValue, argumentValues) generator.Call(ReflectionHelpers.FunctionInstance_CallWithStackTrace); // Allow reuse of the temporary variable. if (targetBase != null) { generator.ReleaseTemporaryVariable(targetBase); } }
public TemporaryVariableExpression(ILLocalVariable variable) { this.variable = variable; }
/// <summary> /// Deletes the reference and pushes <c>true</c> if the delete succeeded, or <c>false</c> /// if the delete failed. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public void GenerateDelete(ILGenerator generator, OptimizationInfo optimizationInfo) { // Deleting a variable is not allowed in strict mode. if (optimizationInfo.StrictMode == true) { throw new JavaScriptException(optimizationInfo.Engine, ErrorType.SyntaxError, string.Format("Cannot delete {0} because deleting a variable or argument is not allowed in strict mode", this.Name), optimizationInfo.SourceSpan.StartLine, optimizationInfo.Source.Path, optimizationInfo.FunctionName); } var endOfDelete = generator.CreateLabel(); var scope = this.Scope; ILLocalVariable scopeVariable = generator.CreateTemporaryVariable(typeof(Scope)); EmitHelpers.LoadScope(generator); generator.StoreVariable(scopeVariable); do { if (scope is DeclarativeScope) { var variable = scope.GetDeclaredVariable(this.Name); if (variable != null) { // The variable is known at compile-time. if (variable.Deletable == false) { // The variable cannot be deleted - return false. generator.LoadBoolean(false); } else { // The variable can be deleted (it was declared inside an eval()). // Delete the variable. generator.LoadVariable(scopeVariable); generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_Delete); } break; } else { // The variable was not defined at compile time, but may have been // introduced by an eval() statement. if (optimizationInfo.MethodOptimizationHints.HasEval == true) { // Check the variable exists: if (scope.HasValue(variableName) == true) { generator.LoadVariable(scopeVariable); generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_HasValue); var hasValueClause = generator.CreateLabel(); generator.BranchIfFalse(hasValueClause); // If the variable does exist, return true. generator.LoadVariable(scopeVariable); generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_Delete); generator.Branch(endOfDelete); // } generator.DefineLabelPosition(hasValueClause); } } } else { // Check if the property exists by calling scope.ScopeObject.HasProperty(propertyName) generator.LoadVariable(scopeVariable); generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); generator.Duplicate(); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.ObjectInstance_HasProperty); // Jump past the delete if the property doesn't exist. var endOfExistsCheck = generator.CreateLabel(); generator.BranchIfFalse(endOfExistsCheck); // Call scope.ScopeObject.Delete(key, false) generator.LoadString(this.Name); generator.LoadBoolean(false); generator.Call(ReflectionHelpers.ObjectInstance_Delete); generator.Branch(endOfDelete); generator.DefineLabelPosition(endOfExistsCheck); generator.Pop(); // If the name is not defined, return true. if (scope.ParentScope == null) { generator.LoadBoolean(true); } } // Try the parent scope. if (scope.ParentScope != null && scope.ExistsAtRuntime == true) { generator.LoadVariable(scopeVariable); generator.Call(ReflectionHelpers.Scope_ParentScope); generator.StoreVariable(scopeVariable); } scope = scope.ParentScope; } while (scope != null); // Release the temporary variable. generator.ReleaseTemporaryVariable(scopeVariable); // Define a label at the end. generator.DefineLabelPosition(endOfDelete); // Delete obviously has side-effects so we evaluate the return value then pop it from // the stack. //if (optimizationInfo.SuppressReturnValue == true) // generator.Pop(); }
/// <summary> /// Stores the value on the top of the stack in the reference. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="valueType"> The primitive type of the value that is on the top of the stack. </param> public void GenerateSet(ILGenerator generator, OptimizationInfo optimizationInfo, PrimitiveType valueType) { string propertyName = null; TypeOfMemberAccess memberAccessType = DetermineTypeOfMemberAccess(optimizationInfo, out propertyName); if (memberAccessType == TypeOfMemberAccess.ArrayIndex) { // Array indexer // ------------- // xxx = object[index] // Call the indexer. EmitConversion.ToAny(generator, valueType); generator.LoadBoolean(optimizationInfo.StrictMode); generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValue_Int); generator.Pop(); } else if (memberAccessType == TypeOfMemberAccess.Static) { // Named property modification (e.g. object.property = value) // ---------------------------------------------------------- // object.SetPropertyValue(property, value, strictMode) // Convert the value to an object and store it in a temporary variable. var value = generator.CreateTemporaryVariable(typeof(object)); EmitConversion.ToAny(generator, valueType); generator.StoreVariable(value); // Use a PropertyReference to speed up access if we are inside a loop. if (optimizationInfo.InsideLoop) { ILLocalVariable propertyReference = optimizationInfo.GetPropertyReferenceVariable(generator, propertyName); generator.LoadVariable(propertyReference); generator.Duplicate(); var afterIf = generator.CreateLabel(); generator.BranchIfNotNull(afterIf); generator.Pop(); generator.LoadString(propertyName); generator.NewObject(ReflectionHelpers.PropertyName_Constructor); generator.Duplicate(); generator.StoreVariable(propertyReference); generator.DefineLabelPosition(afterIf); generator.LoadVariable(value); generator.LoadBoolean(optimizationInfo.StrictMode); generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValue_PropertyReference); generator.Pop(); } else { generator.LoadString(propertyName); generator.LoadVariable(value); generator.LoadBoolean(optimizationInfo.StrictMode); generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValue_Object); generator.Pop(); } generator.ReleaseTemporaryVariable(value); } else { // Dynamic property access // ----------------------- // object.SetPropertyValue(property, value, strictMode) EmitConversion.ToAny(generator, valueType); generator.LoadBoolean(optimizationInfo.StrictMode); generator.Call(ReflectionHelpers.ObjectInstance_SetPropertyValue_Object); generator.Pop(); } }
/// <summary> /// Pushes the value of the reference onto the stack. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> /// <param name="throwIfUnresolvable"> <c>true</c> to throw a ReferenceError exception if /// the name is unresolvable; <c>false</c> to output <c>null</c> instead. </param> public void GenerateGet(ILGenerator generator, OptimizationInfo optimizationInfo, bool throwIfUnresolvable) { // This method generates code to retrieve the value of a variable, given the name of // variable and scope in which the variable is being referenced. The variable was // not necessary declared in this scope - it might be declared in any of the parent // scopes (together called a scope chain). The general algorithm is to start at the // head of the chain and search backwards until the variable is found. There are // two types of scopes: declarative scopes and object scopes. Object scopes are hard - // it cannot be known at compile time whether the variable exists or not so runtime // checks have to be inserted. Declarative scopes are easier - variables have to be // declared and cannot be deleted. There is one tricky bit: new variables can be // introduced into a declarative scope at runtime by a non-strict eval() statement. // Even worse, variables that were introduced by means of an eval() *can* be deleted. var scope = this.Scope; ILLocalVariable scopeVariable = null; var endOfGet = generator.CreateLabel(); do { if (scope is DeclarativeScope) { // The variable was declared in this scope. var variable = scope.GetDeclaredVariable(this.Name); if (variable != null) { if (scope.ExistsAtRuntime == false) { // The scope has been optimized away. The value of the variable is stored // in an ILVariable. // Declare an IL local variable if no storage location has been allocated yet. if (variable.Store == null) { variable.Store = generator.DeclareVariable(typeof(object), variable.Name); } // Load the value from the variable. generator.LoadVariable(variable.Store); // Ensure that we match ResultType. EmitConversion.Convert(generator, variable.Type, this.ResultType, optimizationInfo); } else { // scope.Values[index] if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.Call(ReflectionHelpers.DeclarativeScope_Values); generator.LoadInt32(variable.Index); generator.LoadArrayElement(typeof(object)); } // The variable was found - no need to search any more parent scopes. break; } else { // The variable was not defined at compile time, but may have been // introduced by an eval() statement. if (optimizationInfo.MethodOptimizationHints.HasEval == true) { // Check the variable exists: if (scope.HasValue(variableName) == true) { if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_HasValue); var hasValueClause = generator.CreateLabel(); generator.BranchIfFalse(hasValueClause); // Load the value of the variable. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(DeclarativeScope)); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.Scope_GetValue); generator.Branch(endOfGet); // } generator.DefineLabelPosition(hasValueClause); } } } else { if (scope.ParentScope == null) { // Global variable access // ------------------------------------------- // __object_cacheKey = null; // __object_property_cachedIndex = 0; // ... // if (__object_cacheKey != object.InlineCacheKey) // xxx = object.InlineGetPropertyValue("variable", out __object_property_cachedIndex, out __object_cacheKey) // else // xxx = object.InlinePropertyValues[__object_property_cachedIndex]; // Get a reference to the global object. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); // TODO: share these variables somehow. var cacheKey = generator.DeclareVariable(typeof(object)); var cachedIndex = generator.DeclareVariable(typeof(int)); // Store the object into a temp variable. var objectInstance = generator.DeclareVariable(PrimitiveType.Object); generator.StoreVariable(objectInstance); // if (__object_cacheKey != object.InlineCacheKey) generator.LoadVariable(cacheKey); generator.LoadVariable(objectInstance); generator.Call(ReflectionHelpers.ObjectInstance_InlineCacheKey); var elseClause = generator.CreateLabel(); generator.BranchIfEqual(elseClause); // value = object.InlineGetProperty("property", out __object_property_cachedIndex, out __object_cacheKey) generator.LoadVariable(objectInstance); generator.LoadString(this.Name); generator.LoadAddressOfVariable(cachedIndex); generator.LoadAddressOfVariable(cacheKey); generator.Call(ReflectionHelpers.ObjectInstance_InlineGetPropertyValue); var endOfIf = generator.CreateLabel(); generator.Branch(endOfIf); // else generator.DefineLabelPosition(elseClause); // value = object.InlinePropertyValues[__object_property_cachedIndex]; generator.LoadVariable(objectInstance); generator.Call(ReflectionHelpers.ObjectInstance_InlinePropertyValues); generator.LoadVariable(cachedIndex); generator.LoadArrayElement(typeof(object)); // End of the if statement generator.DefineLabelPosition(endOfIf); // Check if the value is null. generator.Duplicate(); generator.BranchIfNotNull(endOfGet); if (scope.ParentScope != null) { generator.Pop(); } } else { // Gets the value of a variable in an object scope. if (scopeVariable == null) { EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); generator.LoadString(this.Name); generator.Call(ReflectionHelpers.ObjectInstance_GetPropertyValue_Object); // Check if the value is null. generator.Duplicate(); generator.BranchIfNotNull(endOfGet); if (scope.ParentScope != null) { generator.Pop(); } } } // Try the parent scope. if (scope.ParentScope != null && scope.ExistsAtRuntime == true) { if (scopeVariable == null) { scopeVariable = generator.CreateTemporaryVariable(typeof(Scope)); EmitHelpers.LoadScope(generator); } else { generator.LoadVariable(scopeVariable); } generator.Call(ReflectionHelpers.Scope_ParentScope); generator.StoreVariable(scopeVariable); } scope = scope.ParentScope; } while (scope != null); // Throw an error if the name does not exist and throwIfUnresolvable is true. if (scope == null && throwIfUnresolvable == true) { EmitHelpers.EmitThrow(generator, ErrorType.ReferenceError, this.Name + " is not defined", optimizationInfo); } // Release the temporary variable. if (scopeVariable != null) { generator.ReleaseTemporaryVariable(scopeVariable); } // Define a label at the end. generator.DefineLabelPosition(endOfGet); // Object scope references may have side-effects (because of getters) so if the value // is to be ignored we evaluate the value then pop the value from the stack. //if (optimizationInfo.SuppressReturnValue == true) // generator.Pop(); }
/// <summary> /// Pushes the value of the given variable onto the stack. /// </summary> /// <param name="variable"> The variable whose value will be pushed. </param> public override void LoadVariable(ILLocalVariable variable) { if (variable as DynamicILLocalVariable == null) throw new ArgumentNullException("variable"); if (((DynamicILLocalVariable)variable).ILGenerator != this) throw new ArgumentException("The variable wasn't created by this generator.", "variable"); if (variable.Index <= 3) { // ldloc.0 = 06 // ldloc.1 = 07 // ldloc.2 = 08 // ldloc.3 = 09 Emit1ByteOpCode((byte)(0x06 + variable.Index), 0, 1); } else if (variable.Index < 256) { // ldloc.s index = 11 <unsigned int8> Emit1ByteOpCodeInt8(0x11, 0, 1, variable.Index); } else if (variable.Index < 65535) { // ldloc index = FE 0C <unsigned int16> Emit2ByteOpCodeInt16(0xFE, 0x0C, 0, 1, variable.Index); } else throw new InvalidOperationException("Too many local variables."); PushStackOperand(ToVESType(variable.Type)); }