-
Notifications
You must be signed in to change notification settings - Fork 106
Add awaiter implementation #133
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
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d89f9ec
Add awaiter implementation
extraes dfa39ba
Merge branch 'BepInEx:master' into awaiter-implementation
extraes d06f960
Modder commits "worst code ever", asked to leave library
extraes 5737180
Clean up code & remove vestiges of ML/Cecil
extraes 1a1f541
Fix import ordering & remove an unused `using`
extraes 4710b3f
Add "CorLib" reference for mscorlib to global context
extraes f574321
Changes for PR review
extraes 7272bdf
Remove commented vestigial code
extraes 3f509b2
Remove nullability shut-ups from CreateMemberReference
extraes 3905eaa
Changes for PR review
extraes 4f35b9c
Bump referenced AsmResolver version
extraes 940e028
Use explicit method creation
extraes f9c0461
Remove vestigial code from using MemberCloner
extraes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| using System.Runtime.CompilerServices; | ||
| using AsmResolver.DotNet; | ||
| using AsmResolver.DotNet.Cloning; | ||
| using AsmResolver.DotNet.Signatures; | ||
| using AsmResolver.PE.DotNet.Cil; | ||
| using AsmResolver.PE.DotNet.Metadata.Tables; | ||
| using Il2CppInterop.Common; | ||
| using Il2CppInterop.Generator.Contexts; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Il2CppInterop.Generator.Passes; | ||
|
|
||
| public static class Pass61ImplementAwaiters | ||
| { | ||
| public static void DoPass(RewriteGlobalContext context) | ||
| { | ||
| var corlib = context.CorLib; | ||
|
|
||
| var actionUntyped = corlib.GetTypeByName("System.Action"); | ||
|
|
||
| var actionConversion = actionUntyped.NewType.Methods.Single(m => m.Name == "op_Implicit"); | ||
|
|
||
| foreach (var assemblyContext in context.Assemblies) | ||
| { | ||
| // Use Lazy as a lazy way to not actually import the references until they're needed | ||
|
|
||
| Lazy<ITypeDefOrRef> actionUntypedRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(actionConversion.Parameters[0].ParameterType.ToTypeDefOrRef())!); | ||
| Lazy<IMethodDefOrRef> actionConversionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportMethod(actionConversion)); | ||
| Lazy<ITypeDefOrRef> notifyCompletionRef = new(() => assemblyContext.NewAssembly.ManifestModule!.DefaultImporter.ImportType(typeof(INotifyCompletion))); | ||
| var voidRef = assemblyContext.NewAssembly.ManifestModule!.CorLibTypeFactory.Void; | ||
|
|
||
| foreach (var typeContext in assemblyContext.Types) | ||
| { | ||
| // Odds are a majority of types won't implement any interfaces. Skip them to save time. | ||
| if (typeContext.OriginalType.IsInterface || typeContext.OriginalType.Interfaces.Count == 0) | ||
| continue; | ||
|
|
||
| var iNotifyCompletion = typeof(INotifyCompletion); | ||
| var interfaceImplementation = typeContext.OriginalType.Interfaces.SingleOrDefault(interfaceImpl => interfaceImpl.Interface?.Namespace == iNotifyCompletion.Namespace && interfaceImpl.Interface?.Name == iNotifyCompletion.Name); | ||
| if (interfaceImplementation is null) | ||
| continue; | ||
|
|
||
| var allOnCompleted = typeContext.Methods.Where(m => m.OriginalMethod.Name == nameof(INotifyCompletion.OnCompleted)).Select(mc => mc.NewMethod).ToArray(); | ||
|
|
||
| // 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 SignatureComparer because otherwise equality checks would fail due to the TypeSignatures being different references | ||
| var interopOnCompleted = allOnCompleted.FirstOrDefault(m => !m.IsStatic && m.Parameters.Count == 1 && m.Signature is not null && SignatureComparer.Default.Equals(m.Signature.ReturnType, voidRef) && SignatureComparer.Default.Equals(m.Signature.ParameterTypes[0], actionConversion.Signature?.ReturnType)); | ||
|
|
||
| if (interopOnCompleted is null) | ||
| { | ||
| var typeName = typeContext.OriginalType.FullName; | ||
| var foundMethodCount = allOnCompleted.Length; | ||
| Logger.Instance.LogInformation("Type {typeName} was found to implement INotifyCompletion, but no suitable method was found. {foundMethodCount} method(s) were found with the required name.", typeName, foundMethodCount); | ||
| continue; | ||
| } | ||
|
|
||
| var onCompletedAttr = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual; | ||
| var sig = MethodSignature.CreateInstance(voidRef, [actionUntypedRef.Value.ToTypeSignature()]); | ||
|
|
||
| var proxyOnCompleted = new MethodDefinition(nameof(INotifyCompletion.OnCompleted), onCompletedAttr, sig); | ||
| var parameter = proxyOnCompleted.Parameters[0].GetOrCreateDefinition(); | ||
| parameter.Name = "continuation"; | ||
|
|
||
| var body = proxyOnCompleted.CilMethodBody ??= new(proxyOnCompleted); | ||
|
|
||
| typeContext.NewType.Interfaces.Add(new(notifyCompletionRef.Value)); | ||
| typeContext.NewType.Methods.Add(proxyOnCompleted); | ||
|
|
||
| var instructions = body.Instructions; | ||
| instructions.Add(CilOpCodes.Ldarg_0); // load "this" | ||
| instructions.Add(CilOpCodes.Ldarg_1); // not static, so ldarg1 loads "continuation" | ||
| instructions.Add(CilOpCodes.Call, actionConversionRef.Value); | ||
|
|
||
| // The titular jump to the interop method -- it's gotta reference the method on the right type, so we need to handle generic parameters | ||
| // Without this, awaiters declared in generic types like UniTask<T>.Awaiter would effectively try to cast themselves to their untyped versions (UniTask<>.Awaiter in this case, which isn't a thing) | ||
| var genericParameterCount = typeContext.NewType.GenericParameters.Count; | ||
| if (genericParameterCount > 0) | ||
| { | ||
| var typeArguments = Enumerable.Range(0, genericParameterCount).Select(i => new GenericParameterSignature(GenericParameterType.Type, i)).ToArray(); | ||
| var interopOnCompleteGeneric = typeContext.NewType.MakeGenericInstanceType(typeArguments) | ||
| .ToTypeDefOrRef() | ||
| .CreateMemberReference(interopOnCompleted.Name, interopOnCompleted.Signature); | ||
extraes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| instructions.Add(CilOpCodes.Call, interopOnCompleteGeneric); | ||
| } | ||
| else | ||
| { | ||
| instructions.Add(CilOpCodes.Call, interopOnCompleted); | ||
| } | ||
|
|
||
| instructions.Add(CilOpCodes.Ret); | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.