Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Discussion Implementing a gc-free virtual machine

Discussion in 'Scripting' started by fmcdeoliveira, Mar 1, 2023.

  1. fmcdeoliveira

    fmcdeoliveira

    Joined:
    Sep 21, 2021
    Posts:
    19
    I've wondering how to implement a stack based virtual machine in c# that is gc-free and can also call/access c# native methods and properties. Probably the most used approach for virtual machine/visual scripting is to use reflection to access fields and call methods, but it would require a lot of boxing and consequently garbage generation. So I thought, why don't use pointers to access data, but it would be impossible to access managed object data. But I was wrong, it's possible to do so, but we have to be in a certain condition: the garbage collector should be disabled. If we disable the GC it won't move our managed object in the memory, so we can access them by their address as we would do in an unmanaged language. For example, the code above shows how to access a field in an object without boxing it:

    Code (CSharp):
    1.  
    2. public unsafe class Example
    3. {
    4.    public class Object { public int IntegerField; }
    5.    struct ObjectPointerWrapper { public void* Value; }
    6.    public int GetObjectField(object obj) // notice that we don't need to get a typed object
    7.    {
    8.       // turn off gc to avoid it to move the object in the memory
    9.       var gcMode = GarbageCollector.GCMode;
    10.       GarbageCollector.GCMode = GarbageCollector.Mode.Disabled;
    11.       // convert object reference to a pointer
    12.       var objPtr = UnsafeUtility.As<object, ObjectPointerWrapper>(ref obj).Value;
    13.       // get field offset, it should be cached
    14.       var fieldInfo = typeof(Object).GetField("IntegerField", BindingFlags.Instance | BindingFlags.Public);
    15.       int fieldOffset = UnsafeUtility.GetFieldOffset(fieldInfo);
    16.       // get the field ptr by its offset
    17.       var fieldPtr = (int*)((byte*)objPtr + fieldOffset);
    18.       // finally we can read/write a field inside a managed object using a pointer
    19.       int value = *fieldPtr;
    20.       // restore gc mode
    21.       GarbageCollector.GCMode = gcMode;
    22.       return value;
    23.    }
    24. }
    25.  
    Ok! first part is done, I can read and write data from/into an managed object. But how to call native methods and properties? Mono.Cecil!!! My solution is to use Mono.Cecil to generate interop methods that can be called by the virtual machine, something like this:

    Code (CSharp):
    1.  
    2. public delegate void InteropDelegate(VirtualMachine vm);
    3.  
    4. public class InteropMethodLibrary
    5. {
    6.    public static Dictionary<string, InteropDelegate> InteropMethods => s_InteropMethods;
    7.    static Dictionary<string, InteropDelegate> s_InteropMethods = new Dictionary<string, InteropDelegate>();
    8.  
    9.    // this method should have its body generated using Mono.Cecil
    10.    public static void Initialize()
    11.    {
    12.       s_InteropMethods.Add
    13.       (
    14.          "UnityEngine.Vector3.get__X",
    15.          (vm) =>
    16.          {
    17.             Vector3 vector = vm.PopFromStack<Vector3>();
    18.             vm.PushToStack(vector.x);
    19.          }
    20.       );
    21.  
    22.       s_InteropMethods.Add
    23.       (
    24.          "UnityEngine.Vector3.Dot",
    25.          (vm) =>
    26.          {
    27.             Vector3 lhs = vm.PopFromStack<Vector3>();
    28.             Vector3 rhs = vm.PopFromStack<Vector3>();
    29.             vm.PushToStack(Vector3.Dot(lhs, rhs));
    30.          }
    31.       );
    32.  
    33.       // ...
    34.    }
    35. }
    36.  
    This dictionary would provide an interop method to call C# methods and properties (some methods wouldn't be supported like generics), once every method is a known-type delegate it's quite simple to call it from the vm!

    Well, I'm sharing all this thing to you, so maybe someone can spot any issue to my approach. What you guys think about it?
     
    Last edited: Mar 2, 2023
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    You really shouldn't mess with the GC directly like that. C# unsafe code has the fixed keyword for exactly that purpose.

    Apart from that usually it's not worth to try to avoid any garbage. Only focus on either large or very frequent allocations.
     
    fmcdeoliveira, Yoreki and Kurt-Dekker like this.
  3. fmcdeoliveira

    fmcdeoliveira

    Joined:
    Sep 21, 2021
    Posts:
    19
    What also should I be aware? is there anything else that can break my pointers?

    It actually doesn't, I'm trying to access managed object data through pointers and fixed keyword doesn't allow me to do. I can indeed access a specific field like "fixed (float* ptr = &managedObjectRef.FloatField)" and then use UnsafeUtility.GetFieldOffset to subtract from this field address and get the object base address, although it requires me to know a field from this object.

    I wish I could use to develop a visual scripting like Unreal's Blueprint, this virtual machine would run multiple times per frame and the amount of constant garbage generated due to boxing is what I'm trying to avoid.