0

I am creating a system to store value types(int, byte, structs) on the heap and in order to prevent boxing and unboxing of said value types. This is because all of the constant boxing and unboxing in the Unity 3D engine is creating large GC CPU spikes in our large code base.

VerificationException: Operation could destabilize the runtime.

The above exception is thrown when I try to invoke the dynamic method. The stack trace ends just before it goes into the dynamic method and it's not possible to break point the execution. More information is given in the example below.

void Main()
{
    var fieldInfo = typeof(MyClass).GetMember("Number")[0] as FieldInfo;
    var pointerSetFunc = CreatePointerFieldSetMethod(fieldInfo);
    object myClass = new MyClass();
    // The exception occurs when invoking the dynamic method.
    pointerSetFunc(myClass, 0);
}

public class MyClass
{
    public byte Number;
}

public static Action<object, int> CreatePointerFieldSetMethod(FieldInfo field)
{
    var setMethod = new DynamicMethod("SetFieldFromPointer", typeof(void), new[] { typeof(object), typeof(int) }, true);
    ILGenerator generator = setMethod.GetILGenerator();

    // This returns the correct value. byte CustomBox<byte>.Unbox(Int32 index);
    var unboxFunc = typeof(CustomBox<>).MakeGenericType(field.FieldType).GetMethod("Unbox", BindingFlags.Static | BindingFlags.Public);

    // Somewhere in the below code the exception occurs.
    generator.Emit(OpCodes.Ldarg_1); // This should be the index or "pointer" to pass into the CustomBox.Unbox function.
    generator.EmitCall(OpCodes.Call, unboxFunc, null);
    generator.Emit(OpCodes.Stloc_0); // This should be the result of unboxing.

    // This code does not get called.
    generator.Emit(OpCodes.Ldarg_0); // This should be the object MyClass.
    generator.Emit(OpCodes.Ldloc_0); // This should be the value received from the CustomBox.Unbox function.
    generator.Emit(OpCodes.Stfld, field); // Set the MyClass.Number field.

    generator.Emit(OpCodes.Ret);
    return (Action<object, int>)setMethod.CreateDelegate(typeof(Action<object, int>));
}

// The point of this class is to store values types (int, byte, struct, etc..) in an array already on the heap to avoid boxing.
// Boxing has become an issue on our application.
public struct CustomBox<T> where T : struct
{
    public static T Unbox(int index)
    {
        // TODO: Actually make the unbox code.
        return default(T);
    }
}

Edit: Heres the method I'm trying to create and it's generated IL:

private static void SetFieldUsingIndex(object myClass, int index)
{
    byte number = Values<byte>.Unbox(index);
    ((MyClass)myClass).Number = number;
}

/* Generated IL for above method.
    IL_0000: nop
    IL_0001: ldarg.1
    IL_0002: call !0 class CPURaceTest.Values`1<uint8>::Unbox(int32)
    IL_0007: stloc.0
    IL_0008: ldarg.0
    IL_0009: castclass CPURaceTest.MyClass
    IL_000e: ldloc.0
    IL_000f: stfld uint8 CPURaceTest.MyClass::Number
    IL_0014: ret
*/
svick
  • 236,525
  • 50
  • 385
  • 514
Tristan C
  • 21
  • 3
  • You use a local, but didn't define it; and I'm fairly sure the field passed in was not defined on `object`, you should cast the target instance to the appropriate type before trying to set a field on it. – Brian Reichle Dec 01 '16 at 08:21
  • @BrianReichle That's odd because when I write code and decompile it the generated IL doesn't declare a local. Doesn't OpCodes.Stloc_0 handle that for me? I'm specifying to store it in local 0 which is already defined. – Tristan C Dec 01 '16 at 20:02
  • Don't you only have to declare a local if you use OpCodes.Stloc but I used OpCodes.Stloc_0. – Tristan C Dec 01 '16 at 20:05
  • Well, I was wrong. I needed to declare the local like you said. It now works. – Tristan C Dec 01 '16 at 20:14
  • Many disassemblers don't give you anything but opcodes. Use ILDASM to get the whole method. – hoodaticus Jan 24 '17 at 16:06

1 Answers1

1

The problem was because I was not delcaring the local I was using. I also needed to cast the target object to the proper type.

public static Action<object, int> CreatePointerFieldSetMethod(FieldInfo field)
{
    var setMethod = new DynamicMethod("SetFieldFromPointer", typeof(void), new[] { typeof(object), typeof(int) }, true);
    ILGenerator generator = setMethod.GetILGenerator();

    var unboxFunc = typeof(CustomBox<>).MakeGenericType(field.FieldType).GetMethod("Unbox", BindingFlags.Static | BindingFlags.Public);

    var local = generator.DeclareLocal(field.FieldType); // Delcare a local.

    generator.Emit(OpCodes.Ldarg_1);
    generator.EmitCall(OpCodes.Call, unboxFunc, null);
    generator.Emit(OpCodes.Stloc, local); // Use the declared local.

    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Castclass, field.DeclaringType); // Added this cast.
    generator.Emit(OpCodes.Ldloc, local); // Use the declared local.
    generator.Emit(OpCodes.Stfld, field);

    generator.Emit(OpCodes.Ret);
    return (Action<object, int>)setMethod.CreateDelegate(typeof(Action<object, int>));
}
Tristan C
  • 21
  • 3
  • The CustomBox class is a class that just creates an array on the heap of the specified value type. It then has functions to get and set values to and from the heap without boxing and unboxing. It has the initial allocation cost on the heap but does not create garbage every time a value is stored. – Tristan C Dec 02 '16 at 03:01