diff --git a/src/BizHawk.BizInvoke/BizInvoker.cs b/src/BizHawk.BizInvoke/BizInvoker.cs index cf4a36dd53..592a3494c1 100644 --- a/src/BizHawk.BizInvoke/BizInvoker.cs +++ b/src/BizHawk.BizInvoke/BizInvoker.cs @@ -287,6 +287,19 @@ namespace BizHawk.BizInvoke }; } + private class ParameterLoadInfo + { + /// + /// The native type for this parameter, to pass to calli + /// + public Type NativeType; + /// + /// Closure that will actually emit the parameter load to the il stream. The evaluation stack will + /// already have other parameters on it at this time. + /// + public Action EmitLoad; + } + /// /// create a method implementation that uses calli internally /// @@ -296,7 +309,7 @@ namespace BizHawk.BizInvoke { var paramInfos = baseMethod.GetParameters(); var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); - var nativeParamTypes = new List(); + var paramLoadInfos = new List(); var returnType = baseMethod.ReturnType; if (returnType != typeof(void) && !returnType.IsPrimitive && !returnType.IsPointer && !returnType.IsEnum) { @@ -327,10 +340,16 @@ namespace BizHawk.BizInvoke exc = il.BeginExceptionBlock(); } + // phase 1: empty eval stack and each parameter load thunk does any prep work it needs to do for (int i = 0; i < paramTypes.Length; i++) { // arg 0 is this, so + 1 - nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField)); + paramLoadInfos.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField)); + } + // phase 2: actually load the individual params, leaving each one on the stack + foreach (var pli in paramLoadInfos) + { + pli.EmitLoad(); } il.Emit(OpCodes.Ldarg_0); @@ -338,7 +357,7 @@ namespace BizHawk.BizInvoke il.EmitCalli(OpCodes.Calli, nativeCall, returnType == typeof(bool) ? typeof(byte) : returnType, // undo winapi style bool garbage - nativeParamTypes.ToArray()); + paramLoadInfos.Select(p => p.NativeType).ToArray()); if (monitorField != null) // monitor: finally exit { @@ -419,9 +438,10 @@ namespace BizHawk.BizInvoke } /// - /// emit a single parameter load with unmanaged conversions + /// emit a single parameter load with unmanaged conversions. The evaluation stack will be empty when the IL generated here runs, + /// and should end as empty. /// - private static Type EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField) + private static ParameterLoadInfo EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField) { if (type.IsGenericType) { @@ -435,13 +455,18 @@ namespace BizHawk.BizInvoke { throw new NotImplementedException("Only refs of primitive or enum types are supported!"); } - - var loc = il.DeclareLocal(type, true); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, loc); - il.Emit(OpCodes.Conv_I); - return typeof(IntPtr); + return new ParameterLoadInfo + { + NativeType = typeof(IntPtr), + EmitLoad = () => + { + var loc = il.DeclareLocal(type, true); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Conv_I); + } + }; } if (type.IsArray) @@ -463,48 +488,59 @@ namespace BizHawk.BizInvoke throw new NotImplementedException("Only 0-based 1-dimensional arrays are supported!"); } - var loc = il.DeclareLocal(type, true); - var end = il.DefineLabel(); - var isNull = il.DefineLabel(); + return new ParameterLoadInfo + { + NativeType = typeof(IntPtr), + EmitLoad = () => + { + var loc = il.DeclareLocal(type, true); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Brfalse, isNull); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, loc); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldelema, et); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Br, end); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, et); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Br, end); - il.MarkLabel(isNull); - LoadConstant(il, IntPtr.Zero); - il.MarkLabel(end); - - return typeof(IntPtr); + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + } + }; } if (typeof(Delegate).IsAssignableFrom(type)) { // callback -- use the same callingconventionadapter on it that the invoker is being made from - var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate"); - var end = il.DefineLabel(); - var isNull = il.DefineLabel(); + return new ParameterLoadInfo + { + NativeType = typeof(IntPtr), + EmitLoad = () => + { + var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate"); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Brfalse, isNull); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, adapterField); - il.Emit(OpCodes.Ldarg, (short)idx); - il.EmitCall(OpCodes.Callvirt, mi, Type.EmptyTypes); - il.Emit(OpCodes.Br, end); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, adapterField); + il.Emit(OpCodes.Ldarg, (short)idx); + il.EmitCall(OpCodes.Callvirt, mi, Type.EmptyTypes); + il.Emit(OpCodes.Br, end); - il.MarkLabel(isNull); - LoadConstant(il, IntPtr.Zero); - il.MarkLabel(end); - return typeof(IntPtr); + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + } + }; } if (type == typeof(string)) @@ -534,6 +570,8 @@ namespace BizHawk.BizInvoke il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Add); // +1 for null byte il.Emit(OpCodes.Conv_U); + // NB: The evaluation stack must be entirely empty, except for the size argument, when calling localloc. + // That's why we have to split every parameter load into two parts, the first of which runs on an empty stack. il.Emit(OpCodes.Localloc); il.Emit(OpCodes.Stloc, bytes); @@ -556,46 +594,65 @@ namespace BizHawk.BizInvoke // unused ret il.Emit(OpCodes.Pop); - il.Emit(OpCodes.Ldloc, bytes); il.Emit(OpCodes.Br, end); il.MarkLabel(isNull); LoadConstant(il, IntPtr.Zero); + il.Emit(OpCodes.Stloc, bytes); il.MarkLabel(end); - return typeof(IntPtr); + + return new ParameterLoadInfo + { + NativeType = typeof(IntPtr), + EmitLoad = () => + { + il.Emit(OpCodes.Ldloc, bytes); + } + }; } if (type.IsClass) { // non ref of class can just be passed as pointer - var loc = il.DeclareLocal(type, true); - var end = il.DefineLabel(); - var isNull = il.DefineLabel(); + return new ParameterLoadInfo + { + NativeType = typeof(IntPtr), + EmitLoad = () => + { + var loc = il.DeclareLocal(type, true); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Brfalse, isNull); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); - il.Emit(OpCodes.Ldarg, (short)idx); - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, loc); - il.Emit(OpCodes.Conv_I); - // skip past the methodtable pointer to the first field - il.Emit(OpCodes.Ldc_I4, ClassFieldOffset); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Add); - il.Emit(OpCodes.Br, end); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Conv_I); + // skip past the methodtable pointer to the first field + il.Emit(OpCodes.Ldc_I4, ClassFieldOffset); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Br, end); - il.MarkLabel(isNull); - LoadConstant(il, IntPtr.Zero); - il.MarkLabel(end); - - return typeof(IntPtr); + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + } + }; } if (type.IsPrimitive || type.IsEnum || type.IsPointer) { - il.Emit(OpCodes.Ldarg, (short)idx); - return type; + return new ParameterLoadInfo + { + NativeType = type, + EmitLoad = () => + { + il.Emit(OpCodes.Ldarg, (short)idx); + } + }; } throw new NotImplementedException("Unrecognized parameter type!");