Skip to content

Instantly share code, notes, and snippets.

@Sergio0694
Last active September 26, 2019 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sergio0694/365e940799ad77ce0a241d97df7ca6f9 to your computer and use it in GitHub Desktop.
Save Sergio0694/365e940799ad77ce0a241d97df7ca6f9 to your computer and use it in GitHub Desktop.
// Custom delegate that takes the two ref parameters we need
public delegate void DataLoader(object instance, ref object r0, ref byte r1);
/* The method that takes a delegate and the list of discovered
* fields for the closure type and its nested closure types,
* and builds our dynamic IL method that loads all the fields sequentially */
public static DataLoader BuildDataLoader(
Delegate instance
IReadOnlyList<ClosureField> fields)
{
// Prepare the info for our custom delegate
Type returnType = typeof(void);
Type[] parameterTypes =
{
typeof(object),
typeof(object).MakeByRefType(),
typeof(byte).MakeByRefType()
};
// Create a new dynamic method
Type ownerType = instance.GetType();
DynamicMethod method = new DynamicMethod(
$"GetFor{ownerType.Name}",
returnType,
parameterTypes,
ownerType);
ILGenerator il = method.GetILGenerator();
// Mapping of parent members to index of the relative local variable
var map =
fields
.Where(m => m.Parents.Count > 0)
.SelectMany(m => m.Parents)
.Distinct()
.Select((m, i) => (Member: m, Index: i))
.ToDictionary(p => (object)p.Member, p => p.Index + 1);
object root = new object(); // Placeholder
map.Add(root, 0);
// Set of indices of the loaded parent members
HashSet<int> loaded = new HashSet<int>(new[] { 0 });
// Loads a given member on the top of the execution stack
void LoadMember(ClosureField member)
{
// Load the parent instance on the execution stack
int index = map[member.Parents.LastOrDefault() ?? root];
if (loaded.Contains(index)) il.EmitLoadLocal(index);
else
{
// Seek upwards to find the most in depth loaded parent
int i = member.Parents.Count - 1;
while (i > 0 && !loaded.Contains(map[member.Parents[i]])) i--;
// Load the local variables for all the parents of the current member
il.EmitLoadLocal(i);
for (; i < member.Parents.Count; i++)
{
index = map[member.Parents[i]];
il.Emit(OpCodes.Ldfld, member.Parents[i]);
il.EmitStoreLocal(index);
il.EmitLoadLocal(index);
loaded.Add(index);
}
}
// Finally load the field from the object on the stack
il.Emit(OpCodes.Ldfld, member.Info);
}
// Declare the local variables
il.DeclareLocal(instance.Method.DeclaringType);
foreach (FieldInfo field in map.OrderBy(p => p.Value).Skip(1).Select(p => p.Key))
{
il.DeclareLocal(field.FieldType);
}
// Cast the closure instance and assign it to the local variable
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, instance.Method.DeclaringType);
il.Emit(OpCodes.Stloc_0);
// Handle all the captured fields, both objects and value types
int
referenceOffset = 0, // Offset into the references array
byteOffset = 0; // Offset into the bytes array
foreach (ClosureField field in fields)
{
if (field.Info.FieldType.IsValueType)
{
il.Emit(OpCodes.Ldarg_2); // Load ref byte r1
il.EmitAddOffset(byteOffset); // Offset the reference
byteOffset += Marshal.SizeOf(field.Info.FieldType);
}
else
{
// Load the offset address into the resource buffers
il.Emit(OpCodes.Ldarg_1); // Load ref object r0
if (referenceOffset > 0)
{
int offset = Unsafe.SizeOf<object>() * referenceOffset;
il.EmitAddOffset(offset);
}
referenceOffset++;
}
/* Load the current member accordingly.
* When this method returns, the value of the current
* member will be at the top of the execution stack */
LoadMember(field);
il.EmitStoreToAddress(field.Info.FieldType);
}
il.Emit(OpCodes.Ret);
// Create the proper delegate type for the method
return (DataLoader)method.CreateDelegate(typeof(DataLoader));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment