-
Notifications
You must be signed in to change notification settings - Fork 67
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 awaiter implementation #133
base: master
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
I investigated this pull request and what it's attempting to solve. I discovered that the type system improvements I mentioned above will not automatically fix the issue. As I understand it, the issue is that even if proper interface support is implemented with system interface support, only the Il2Cpp For the rewrite, I could see a generic implementation where all Il2Cpp I am willing to review this pull request once it has been ported to AsmResolver. |
Paste from ML plugin, will be changed to remove unnecessary code/comments & improve generic handling
public static void DoPass(RewriteGlobalContext context) | ||
{ | ||
var corlib = context.GetAssemblyByName("mscorlib"); | ||
var actionUntyped = corlib.GetTypeByName("System.Action"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we seriously not use separate namespace and name when doing this throughout the whole project? That seems so inefficient.
- Use CorLibTypeFactory.Void for void reference - Added a couple more early-outs before LINQ - Use .Single - Use MemberCloner (and define a nested type for that) - Check method signature & forward to methods that may have been wonkily unhollowed
Whenever you're ready for another review, let me know. |
Ah yeah, if you can review again that'd be great |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave it another pass. 👍
var corlib = context.CorLib; | ||
var actionUntyped = corlib.GetTypeByName("System.Action"); | ||
|
||
var actionConversion = actionUntyped.NewType.Methods.FirstOrDefault(m => m.Name == "op_Implicit") ?? throw new MissingMethodException("Untyped action conversion"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods.Single
foreach (var typeContext in assemblyContext.Types) | ||
{ | ||
// Used later for MemberCloner, just putting up here as an early exit in case .Module is ever null | ||
if (typeContext.NewType.Module is null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be impossible. I don't mind the null check, but I think a Debug.Assert would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check prevents the compiler from warning later down the line. I can replace it with a Debug.Assert if you'd like, but I'd then have to either ignore the warnings or !
the nullability away.
if (typeContext.OriginalType.IsInterface || typeContext.OriginalType.Interfaces.Count == 0) | ||
continue; | ||
|
||
var interfaceImplementation = typeContext.OriginalType.Interfaces.FirstOrDefault(interfaceImpl => interfaceImpl.Interface?.Name == nameof(INotifyCompletion)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer you check the namespace too.
if (allOnCompleted.Length == 0) | ||
{ | ||
// Likely defined as INotifyCompletion.OnCompleted & the name is unhollowed as something like "System_Runtime_CompilerServices_INotifyCompletion_OnCompleted" | ||
allOnCompleted = typeContext.NewType.Methods.Where(m => ((string?)m.Name)?.EndsWith(nameof(INotifyCompletion.OnCompleted)) ?? false).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can check the method contexts for the exact name.
|
||
// Conversion spits out an Il2CppSystem.Action, so look for methods that take that (and only that) in & return void, so the stack is balanced | ||
// And use IsAssignableTo because otherwise equality checks would fail due to the TypeSignatures being different references | ||
var interopOnCompleted = allOnCompleted.FirstOrDefault(m => m.Parameters.Count == 1 && m.Signature is not null && m.Signature.ReturnType == voidRef && SignatureComparer.Default.Equals(m.Signature.ParameterTypes[0], actionConversion.Signature?.ReturnType)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use signature comparer instead of equality for the void check.
You also need to check that it's an instance method.
typeContext.NewType.Interfaces.Add(new(notifyCompletionRef.Value)); | ||
|
||
var instructions = body.Instructions; | ||
instructions.Add(CilOpCodes.Nop); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nops are there because I was copying the IL I got from a normally-compiled OnComplete. I'll remove them.
instructions.Add(CilOpCodes.Call, interopOnCompleted); | ||
} | ||
|
||
instructions.Add(CilOpCodes.Nop); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary.
} | ||
|
||
// Conversion spits out an Il2CppSystem.Action, so look for methods that take that (and only that) in & return void, so the stack is balanced | ||
// And use IsAssignableTo because otherwise equality checks would fail due to the TypeSignatures being different references |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like you no longer use IsAssignableTo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah that was a comment from before I saw SignatureComparer. I'll change it to reflect the current state.
Adds
Pass61ImplementAwaiters
Looks for types implementing
INotifyCompletion
and generates new methods that allow the interop types to implement that interface, calling the Il2CppSystem.Action -> System.Action implicit conversion before calling the original method.This makes originally-awaitable types (e.g.
UniTask
s, if the game has them) awaitable again.I previously did this in a much less automated way with Cecil in another project, but the runtime didn't like what I was doing, so I figured I'd make it less janky and add it to Il2CppInterop, and sure enough the runtime no longer rejects the type.