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) { DebugHelper.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); }