A better but more difficult solution: Any structs that have ref type fields also get the same hard-coded functions to manage their pointers just like classes get

That sounds good. And I might have a slightly different idea, treat the stack frame as objects:

  • for each method create a struct that contains all the method parameters and locals
  • when you call a method allocate such a struct on the stack (by using alloca or other means)
  • fill the parameters
  • call the method with a pointer to the allocated struct
  • you'll also need to store pointers to these stack frames on that virtual stack of yours or simply chain the stack frames into a linked list (pointed to by the thread)

Pros:

  • "everything" is an object, you don't need separate cases for "real" objects and stack frames
  • this works transparently with value types that contain references
  • this might avoid the register problem completly. if a method makes a "alloc" call the compiler won't be able to prove that the stack frame object hasn't been modified and it will have to reload any registers that have been previously read from stack frame

Cons:

  • This will very likely disable some compiler optimizations. Usually some of the parameters are passed through registers, this can't happen anymore since the only real parameter will be the pointer to stack frame. Similarly for local variables.

That is certainly not "illegal" code and the compiler can't guard against it. Now if the original ref to MyClass goes out of scope, then it is no longer reachable and it will be GC'd

MyClass can't go out of scope, if one of its methods is running then there must be a stack frame for it and that stack frame must contain the "this" pointer for MyClass.

On a microcontroller it might be more important for the GC to not lock everything up for long periods of times instead of absolute performance.

Sounds like you want generational GC too. Even more complicated Big Smile.

What I'm suggesting is improving on this process by stripping the fields out of types that were never referenced during all of the code paths.

Sure, you could do that. Not sure if it helps with the List<T> case. I looked at the code and it's indeed possible that CultureInfo gets dragged in by an exception (see the Capacity prop). But it's unlikely that you can eliminate that by doing some static code analysis.