Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for byref generic parameters in methods #79

Merged
merged 8 commits into from
Feb 24, 2023
17 changes: 14 additions & 3 deletions Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,20 @@ public static void EmitUpdateRef(this ILProcessor body, ParameterDefinition newM
var stnop = body.Create(OpCodes.Nop);
body.Emit(OpCodes.Brfalse_S, nullbr);

body.Emit(OpCodes.Newobj,
new MethodReference(".ctor", imports.Module.Void(), newMethodParameter.ParameterType.GetElementType())
{ HasThis = true, Parameters = { new ParameterDefinition(imports.Module.IntPtr()) } });
if (newMethodParameter.ParameterType.GetElementType() is GenericParameter)
{
body.Emit(OpCodes.Ldc_I4_0);
body.Emit(OpCodes.Ldc_I4_0);
body.Emit(OpCodes.Call,
imports.Module.ImportReference(new GenericInstanceMethod(imports.IL2CPP_PointerToValueGeneric.Value)
{ GenericArguments = { newMethodParameter.ParameterType.GetElementType() } }));
}
else
{
body.Emit(OpCodes.Newobj,
new MethodReference(".ctor", imports.Module.Void(), newMethodParameter.ParameterType.GetElementType())
{ HasThis = true, Parameters = { new ParameterDefinition(imports.Module.IntPtr()) } });
}
body.Emit(OpCodes.Br_S, stnop);

body.Append(nullbr);
Expand Down
86 changes: 80 additions & 6 deletions Il2CppInterop.Generator/Passes/Pass50GenerateMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,61 @@ public static void DoPass(RewriteGlobalContext context)
}

var newParam = newMethod.Parameters[i];
bodyBuilder.EmitObjectToPointer(originalMethod.Parameters[i].ParameterType, newParam.ParameterType,
methodRewriteContext.DeclaringType, argOffset + i, false, true, true, out var refVar);
// NOTE(Kas): out parameters of value type are passed directly as a pointer to the il2cpp method
// since we don't need to perform any additional copies
if (newParam.IsOut && !newParam.ParameterType.GetElementType().IsValueType)
{
var elementType = newParam.ParameterType.GetElementType();

// Storage for the output Il2CppObjectBase pointer, it's
// unused if there's a generic value type parameter
var outVar = new VariableDefinition(imports.Module.IntPtr());
bodyBuilder.Body.Variables.Add(outVar);

if (elementType.IsGenericParameter)
{
bodyBuilder.Emit(OpCodes.Ldtoken, elementType);
bodyBuilder.Emit(OpCodes.Call, imports.Module.TypeGetTypeFromHandle());
bodyBuilder.Emit(OpCodes.Callvirt, imports.Module.TypeGetIsValueType());

var valueTypeBlock = bodyBuilder.Create(OpCodes.Nop);
var continueBlock = bodyBuilder.Create(OpCodes.Nop);

bodyBuilder.Emit(OpCodes.Brtrue, valueTypeBlock);

// The generic parameter is an Il2CppObjectBase => set the output storage to a nullptr
bodyBuilder.EmitLdcI4(0);
bodyBuilder.Emit(OpCodes.Stloc, outVar);
bodyBuilder.Emit(OpCodes.Ldloca, outVar);
bodyBuilder.Emit(OpCodes.Conv_I);

bodyBuilder.Emit(OpCodes.Br_S, continueBlock);

// Instruction block that handles generic value types, we only need to return a reference
// to the output argument since it is already allocated for us
bodyBuilder.Append(valueTypeBlock);
bodyBuilder.Emit(OpCodes.Ldarg, argOffset + i);

bodyBuilder.Append(continueBlock);
}
else
{
bodyBuilder.EmitLdcI4(0);
bodyBuilder.Emit(OpCodes.Stloc, outVar);
bodyBuilder.Emit(OpCodes.Ldloca, outVar);
bodyBuilder.Emit(OpCodes.Conv_I);
}
byRefParams.Add((i, outVar));
}
else
{
bodyBuilder.EmitObjectToPointer(originalMethod.Parameters[i].ParameterType, newParam.ParameterType,
methodRewriteContext.DeclaringType, argOffset + i, false, true, true, out var refVar);
if (refVar != null)
byRefParams.Add((i, refVar));
}
bodyBuilder.Emit(OpCodes.Stind_I);

if (refVar != null)
byRefParams.Add((i, refVar));
}

if (!originalMethod.DeclaringType.IsSealed && !originalMethod.IsFinal &&
Expand Down Expand Up @@ -168,8 +217,33 @@ public static void DoPass(RewriteGlobalContext context)
{
var paramIndex = byRefParam.Item1;
var paramVariable = byRefParam.Item2;
bodyBuilder.EmitUpdateRef(newMethod.Parameters[paramIndex], paramIndex + argOffset, paramVariable,
imports);
var methodParam = newMethod.Parameters[paramIndex];

if (methodParam.IsOut && methodParam.ParameterType.GetElementType().IsGenericParameter)
{
bodyBuilder.Emit(OpCodes.Ldtoken, methodParam.ParameterType.GetElementType());
bodyBuilder.Emit(OpCodes.Call, imports.Module.TypeGetTypeFromHandle());
bodyBuilder.Emit(OpCodes.Callvirt, imports.Module.TypeGetIsValueType());

var continueBlock = bodyBuilder.Create(OpCodes.Nop);

bodyBuilder.Emit(OpCodes.Brtrue, continueBlock);

// The generic parameter is an Il2CppObjectBase => update the reference appropriately
bodyBuilder.EmitUpdateRef(newMethod.Parameters[paramIndex], paramIndex + argOffset, paramVariable,
imports);

bodyBuilder.Emit(OpCodes.Br_S, continueBlock);

// There is no need to handle generic value types, they are already passed by reference

bodyBuilder.Append(continueBlock);
}
else
{
bodyBuilder.EmitUpdateRef(newMethod.Parameters[paramIndex], paramIndex + argOffset, paramVariable,
imports);
}
}

bodyBuilder.EmitPointerToObject(originalMethod.ReturnType, newMethod.ReturnType, typeContext,
Expand Down
15 changes: 13 additions & 2 deletions Il2CppInterop.Runtime/IL2CPP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,20 @@ private static T GenerateDelegateForMissingICall<T>(string signature) where T :
else
objectPointer = *(IntPtr*)objectPointer;
}

if (!valueTypeWouldBeBoxed && il2cpp_class_is_valuetype(Il2CppClassPointerStore<T>.NativeClassPtr))
objectPointer = il2cpp_value_box(Il2CppClassPointerStore<T>.NativeClassPtr, objectPointer);
{
uint align = 0;
if (typeof(T).IsValueType && il2cpp_class_value_size(Il2CppClassPointerStore<T>.NativeClassPtr, ref align) <= sizeof(IntPtr))
{
// NOTE(Kas): This assumes that objectPointer isn't pointing to the data, but is the actual data
// which has only started ocurring since adding support for generic byrefs in method proxygen
return Unsafe.As<IntPtr, T>(ref objectPointer);
}
else
{
objectPointer = il2cpp_value_box(Il2CppClassPointerStore<T>.NativeClassPtr, objectPointer);
}
}
Kasuromi marked this conversation as resolved.
Show resolved Hide resolved

if (typeof(T) == typeof(string))
return (T)(object)Il2CppStringToManaged(objectPointer);
Expand Down