/// <summary> /// Emits code that loads a specified parameter on the evaluation stack. /// </summary> /// <param name="paramInfo">The parameter to load.</param> /// <param name="requiredTypeCode">Specifies whether <see cref="PhpReference"/> /// (<see cref="PhpTypeCode.PhpReference"/>), <see cref="object"/> (<see cref="PhpTypeCode.Object"/>), /// or the most fitting of these two should be loaded.</param> public void EmitLoadClrParameter(ParameterInfo /*!*/ paramInfo, PhpTypeCode requiredTypeCode) { if (paramInfo.IsOut) { il.Emit(OpCodes.Ldnull); } else { il.Ldarg(paramInfo.Position + paramOffset); // dereference ref param Type param_type = paramInfo.ParameterType; if (param_type.IsByRef) { param_type = param_type.GetElementType(); il.Ldind(param_type); } // convert the parameter to PHP type PhpTypeCode type_code = ClrOverloadBuilder.EmitConvertToPhp( il, param_type /*, * scriptContextPlace*/); il.EmitBoxing(type_code); } // check whether we have to create a PhpReference if (requiredTypeCode == PhpTypeCode.Object || (requiredTypeCode == PhpTypeCode.Unknown && !paramInfo.ParameterType.IsByRef)) { return; } if (paramInfo.ParameterType.IsByRef) { LocalBuilder ref_local = il.DeclareLocal(Types.PhpReference[0]); // remember the PhpReference in a local il.Emit(OpCodes.Newobj, Constructors.PhpReference_Object); il.Emit(OpCodes.Dup); il.Stloc(ref_local); referenceLocals[paramInfo.Position] = ref_local; } else { // no reference store-back is necessary il.Emit(OpCodes.Newobj, Constructors.PhpReference_Object); } }
private ArrayList refReferences = new ArrayList(3); // GENERICS: <LocalBuilder> #endregion #region Call Switch Emitter /// <summary> /// Emits calls to specified overloads and a switch statement which calls appropriate overload /// according to the current value of <see cref="PhpStack.ArgCount"/> field of the current stack. /// </summary> /// <param name="thisRef">Reference to self.</param> /// <param name="script_context">Current script context.</param> /// <param name="rtVariables"> /// Place where a run-time variables table can be loaded from. /// </param> /// <param name="namingContext">Naming context load-from place.</param> /// <param name="classContext">Class context load.</param> /// <param name="overloads">The overload list.</param> /// <remarks> /// Example: given overloads (2,5,7,9+), i.e. there are four overloads having 2, 5, 7 and 9 PHP parameters, /// respectively, and the last overload is marked as vararg, /// the method emits the following code: /// <code> /// switch(ArgCount - 2) // 2 = minimum { arg count of overload } /// { /// case 0: return call #2; // call to the 2nd overload with appropriate arg. and return value handling /// case 1: goto case error; /// case 2: goto case error; /// case 3: return call #5; /// case 4: goto case error; /// case 5: return call #7; /// case 6: goto case error; /// /// #if vararg /// case 7: goto default; /// default: return call #vararg (9 mandatory args,optional args);break; /// #elif /// case 7: return call #9; /// default: goto case error; /// #endif /// /// case error: PhpException.InvalidArgumentCount(null, functionName); break; /// } /// </code> /// </remarks> public void EmitCallSwitch(IPlace /*!*/ thisRef, IPlace /*!*/ script_context, IPlace /*!*/ rtVariables, IPlace /*!*/ namingContext, IPlace /*!*/ classContext, List <PhpLibraryFunction.Overload> /*!!*/ overloads) { Debug.AssertAllNonNull(overloads); int last = overloads.Count - 1; int min = overloads[0].ParamCount; int max = overloads[last].ParamCount; var flags = overloads[last].Flags; // if function is not supported, just throw the warning: if ((flags & PhpLibraryFunction.OverloadFlags.NotSupported) != 0) { // stack.RemoveFrame(); if (stack != null) { stack.EmitLoad(il); il.Emit(OpCodes.Call, Methods.PhpStack.RemoveFrame); } // PhpException.FunctionNotSupported( <FullName> ); il.Emit(OpCodes.Ldstr, FunctionName.Value); il.Emit(OpCodes.Call, Methods.PhpException.FunctionNotSupported_String); if (debug) { il.Emit(OpCodes.Nop); } // load methods default value il.EmitBoxing(OverloadsBuilder.EmitLoadDefault(il, overloads[last].Method)); return; } bool is_vararg = (flags & PhpLibraryFunction.OverloadFlags.IsVararg) != 0; if ((flags & PhpLibraryFunction.OverloadFlags.NeedsScriptContext) == 0) { script_context = null; } if ((flags & PhpLibraryFunction.OverloadFlags.NeedsThisReference) == 0) { thisRef = null; } if ((flags & PhpLibraryFunction.OverloadFlags.NeedsVariables) == 0) { rtVariables = null; } if ((flags & PhpLibraryFunction.OverloadFlags.NeedsNamingContext) == 0) { namingContext = null; } if ((flags & (PhpLibraryFunction.OverloadFlags.NeedsClassContext | PhpLibraryFunction.OverloadFlags.NeedsLateStaticBind)) == 0) { classContext = null; } Label end_label = il.DefineLabel(); Label error_label = il.DefineLabel(); Label[] cases = new Label[max - min + 1]; MethodInfo method; // fills cases with "goto case error": for (int i = 0; i < cases.Length; i++) { cases[i] = error_label; } // define labels for valid cases: for (int i = 0; i < overloads.Count; i++) { int count = overloads[i].ParamCount; cases[count - min] = il.DefineLabel(); } // LOAD(stack.ArgCount - min); stack.EmitLoad(il); il.Emit(OpCodes.Ldfld, Fields.PhpStack_ArgCount); il.LdcI4(min); il.Emit(OpCodes.Sub); // SWITCH(tmp) il.Emit(OpCodes.Switch, cases); // CASE >=N or <0 (underflows); // if the last overload is vararg: if (is_vararg) { LocalBuilder opt_arg_count_local = il.DeclareLocal(typeof(int)); // CASE N: il.MarkLabel(cases[cases.Length - 1]); // opt_arg_count = stack.ArgCount - max; stack.EmitLoad(il); il.Emit(OpCodes.Ldfld, Fields.PhpStack_ArgCount); il.LdcI4(max); il.Emit(OpCodes.Sub); il.Stloc(opt_arg_count_local); // IF(tmp<0) GOTO CASE error; il.Ldloc(opt_arg_count_local); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Blt, error_label); // emits argument loading, stack frame removal, method call, return value conversion: method = overloads[last].Method; Type return_type = EmitOverloadCall(method, overloads[last].RealParameters, max, script_context, rtVariables, namingContext, classContext, new Place(opt_arg_count_local), thisRef, false); // loads boxed return value: if (return_type != Types.Void) { //il.LoadBoxed(return_value); if (return_type.IsValueType) { il.Emit(OpCodes.Box, return_type); } } else { il.Emit(OpCodes.Ldnull); } // RETURN; il.Emit(OpCodes.Ret); //bug in Reflector: il.Emit(OpCodes.Br,end_label); } else { // GOTO CASE error; il.Emit(OpCodes.Br, error_label); } // emits all valid cases which are not vararg: int j = 0; for (int i = min; i <= max - (is_vararg ? 1 : 0); i++) { if (overloads[j].ParamCount == i) { // CASE <i>; il.MarkLabel(cases[i - min]); // emits argument loading, stack frame removal, method call, return value conversion: method = overloads[j].Method; Type return_type = EmitOverloadCall(method, overloads[j].RealParameters, i, script_context, rtVariables, namingContext, classContext, null, thisRef, false); // loads boxed return value: if (return_type != Types.Void) { //il.LoadBoxed(return_value); if (return_type.IsValueType) { il.Emit(OpCodes.Box, return_type); } } else { il.Emit(OpCodes.Ldnull); } // RETURN; il.Emit(OpCodes.Ret); //bug in Reflector: il.Emit(OpCodes.Br,end_label); j++; } } Debug.Assert(j + (is_vararg ? 1 : 0) == overloads.Count); // ERROR: il.MarkLabel(error_label); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ldstr, this.functionName.ToString()); il.Emit(OpCodes.Call, Methods.PhpException.InvalidArgumentCount); if (debug) { il.Emit(OpCodes.Nop); } // RETURN null: il.Emit(OpCodes.Ldnull); il.MarkLabel(end_label); }
/// <summary> /// Emits assignment. /// </summary> /// <remarks> /// Pattern: a op= b /// /// PREPARE a (prepared) /// LOAD a (prepared,a) /// LOAD b (prepared,a,b) /// OP (prepared,result) /// *DUP (prepared,result,result) /// *STORE tmp (prepared,result) must be this stack here! /// STORE a () /// *LOAD tmp (result) /// /// * only if the resulting value needs to be propagated to the right /// /// Note: There is a possible improvement: some store operations (SetVariable) may return the value set /// which would replace DUP and second temp op. /// </remarks> internal override PhpTypeCode Emit(CodeGenerator /*!*/ codeGenerator) { Debug.Assert(access == AccessType.Read || access == AccessType.None || access == AccessType.ReadRef || access == AccessType.ReadUnknown); Statistics.AST.AddNode("Assign.Value"); ILEmitter il = codeGenerator.IL; AccessType old_selector = codeGenerator.AccessSelector; codeGenerator.ChainBuilder.Create(); PhpTypeCode result; if (operation == Operations.AssignValue) { // // Access Type = ReadRef/ReadUnknown // --------------------------------- // // f(&$x) { } // // f($a = $b); // f($a = $b =& $c); // // Destination variable $a is prepared for reference write. // A new reference is created and its value set to a deep copy of the result of RHS ($b, $b =& $c). // RHS has Read access => it has been dereferenced. // // PREPARE a: codeGenerator.AccessSelector = AccessType.Write; lvalue.Emit(codeGenerator); codeGenerator.AccessSelector = AccessType.None; PhpTypeCode src_type_code = EmitSourceValRead(codeGenerator); // RHS should have Read access => should be dereferenced Debug.Assert(src_type_code != PhpTypeCode.PhpReference); // LOAD BOX b codeGenerator.EmitBoxing(src_type_code); // makes a copy if necessary: if (PhpTypeCodeEnum.IsDeeplyCopied(src_type_code)) { codeGenerator.EmitVariableCopy(CopyReason.Assigned, rvalue); } } else { // PREPARE a: codeGenerator.AccessSelector = AccessType.Write; lvalue.Emit(codeGenerator); codeGenerator.AccessSelector = AccessType.None; // LOAD b,a (rvalue must be processed first, than +-*/ with lvalue, since lvalu can be changed by rvalue expression) //must be the second operand// EmitDestVarRead(codeGenerator); PhpTypeCode right_type = EmitSourceValRead(codeGenerator); var rvalue_tmp = codeGenerator.IL.GetTemporaryLocal(PhpTypeCodeEnum.ToType(right_type), false); codeGenerator.IL.Emit(OpCodes.Stloc, rvalue_tmp); EmitDestVarRead(codeGenerator); codeGenerator.IL.Emit(OpCodes.Ldloc, rvalue_tmp); codeGenerator.IL.ReturnTemporaryLocal(rvalue_tmp); switch (operation) { #region Arithmetic case Operations.AssignAdd: { switch (right_type) { case PhpTypeCode.Integer: result = codeGenerator.EmitMethodCall(Methods.Operators.Add.Object_Int32); break; case PhpTypeCode.Double: result = codeGenerator.EmitMethodCall(Methods.Operators.Add.Object_Double); break; default: codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Add.Object_Object); break; } break; } case Operations.AssignSub: { switch (right_type) { case PhpTypeCode.Integer: result = codeGenerator.EmitMethodCall(Methods.Operators.Subtract.Object_Int); break; default: codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Subtract.Object_Object); break; } break; } case Operations.AssignDiv: { switch (right_type) { case PhpTypeCode.Integer: result = codeGenerator.EmitMethodCall(Methods.Operators.Divide.Object_Int32); break; case PhpTypeCode.Double: result = codeGenerator.EmitMethodCall(Methods.Operators.Divide.Object_Double); break; default: codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Divide.Object_Object); break; } break; } case Operations.AssignMul: { switch (right_type) { case PhpTypeCode.Integer: result = codeGenerator.EmitMethodCall(Methods.Operators.Multiply.Object_Int32); break; case PhpTypeCode.Double: result = codeGenerator.EmitMethodCall(Methods.Operators.Multiply.Object_Double); break; default: codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Multiply.Object_Object); break; } break; } case Operations.AssignMod: if (right_type == PhpTypeCode.Integer) { result = codeGenerator.EmitMethodCall(Methods.Operators.Remainder.Object_Int32); } else { codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Remainder.Object_Object); } break; #endregion #region Bitwise case Operations.AssignAnd: codeGenerator.EmitBoxing(right_type); il.Emit(OpCodes.Ldc_I4, (int)Operators.BitOp.And); result = codeGenerator.EmitMethodCall(Methods.Operators.BitOperation); break; case Operations.AssignOr: codeGenerator.EmitBoxing(right_type); il.Emit(OpCodes.Ldc_I4, (int)Operators.BitOp.Or); result = codeGenerator.EmitMethodCall(Methods.Operators.BitOperation); break; case Operations.AssignXor: codeGenerator.EmitBoxing(right_type); il.Emit(OpCodes.Ldc_I4, (int)Operators.BitOp.Xor); result = codeGenerator.EmitMethodCall(Methods.Operators.BitOperation); break; case Operations.AssignShiftLeft: codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.ShiftLeft); break; case Operations.AssignShiftRight: codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.ShiftRight); break; #endregion #region String case Operations.AssignAppend: { if (right_type == PhpTypeCode.String) { result = codeGenerator.EmitMethodCall(Methods.Operators.Append.Object_String); } else if (right_type == PhpTypeCode.PhpBytes) { result = codeGenerator.EmitMethodCall(Methods.PhpBytes.Append_Object_PhpBytes); } else { codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Append.Object_Object); } break; } case Operations.AssignPrepend: { if (right_type == PhpTypeCode.String) { result = codeGenerator.EmitMethodCall(Methods.Operators.Prepend.Object_String); } else { codeGenerator.EmitBoxing(right_type); result = codeGenerator.EmitMethodCall(Methods.Operators.Prepend.Object_Object); } break; } #endregion default: Debug.Fail(); throw null; } il.EmitBoxing(result); } switch (access) { case AccessType.Read: { // DUP il.Emit(OpCodes.Dup); // STORE tmp il.Stloc(il.GetAssignmentLocal()); // STORE prepared, result codeGenerator.AccessSelector = AccessType.Write; result = lvalue.EmitAssign(codeGenerator); codeGenerator.AccessSelector = AccessType.None; Debug.Assert(result == PhpTypeCode.Void); // LOAD result il.Ldloc(il.GetAssignmentLocal()); result = PhpTypeCode.Object; break; } case AccessType.ReadRef: case AccessType.ReadUnknown: // STORE prepared,result codeGenerator.AccessSelector = AccessType.Write; result = lvalue.EmitAssign(codeGenerator); codeGenerator.AccessSelector = AccessType.None; Debug.Assert(result == PhpTypeCode.Void); // loads a reference on the LHS variable: codeGenerator.AccessSelector = access; codeGenerator.ChainBuilder.Create(); result = lvalue.Emit(codeGenerator); codeGenerator.ChainBuilder.EndRef(); codeGenerator.AccessSelector = AccessType.None; break; case AccessType.None: // STORE a: codeGenerator.AccessSelector = AccessType.Write; result = lvalue.EmitAssign(codeGenerator); codeGenerator.AccessSelector = AccessType.None; Debug.Assert(result == PhpTypeCode.Void); break; default: Debug.Fail("Invalid access type."); result = PhpTypeCode.Invalid; break; } codeGenerator.ChainBuilder.End(); codeGenerator.AccessSelector = old_selector; return(result); }