using System.Diagnostics; using Mono.Cecil; using Mono.Cecil.Rocks; namespace Burst.Compiler.IL.Syntax { /// /// A generic context contains a mapping between GenericParameter ({T}) and resolved TypeReference (int, float, MyStruct<float>) /// #if UNITY_EDITOR internal #else public #endif readonly struct GenericContext { private readonly GenericInstanceType _typeContext; private readonly GenericInstanceMethod _methodContext; /// /// An empty /// public static readonly GenericContext None = new GenericContext(); /// /// Initializes a new instance of the class. /// /// The generic method instance. private GenericContext(GenericInstanceMethod genericMethod, GenericInstanceType genericType) { _methodContext = genericMethod; _typeContext = genericType; } /// /// Is there no generics in this context? /// /// public bool IsEmpty() { return _typeContext == null && _methodContext == null; } /// /// Resolve the generics of the given /// /// /// public MethodReference Resolve(MethodReference unresolvedMethod) { Debug.Assert(unresolvedMethod != null); // The following code was originally derived from IL2CPP. var resolvedMethod = unresolvedMethod; if (IsEmpty()) { return resolvedMethod; } var declaringType = Resolve(unresolvedMethod.DeclaringType); if (unresolvedMethod is GenericInstanceMethod genericInstanceMethod) { resolvedMethod = new MethodReference(unresolvedMethod.Name, unresolvedMethod.ReturnType, declaringType); foreach (var p in unresolvedMethod.Parameters) { resolvedMethod.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, p.ParameterType)); } foreach (var gp in genericInstanceMethod.ElementMethod.GenericParameters) { resolvedMethod.GenericParameters.Add(new GenericParameter(gp.Name, resolvedMethod)); } resolvedMethod.HasThis = unresolvedMethod.HasThis; var m = new GenericInstanceMethod(resolvedMethod); foreach (var ga in genericInstanceMethod.GenericArguments) { m.GenericArguments.Add(Resolve(ga)); } resolvedMethod = m; } else { if (unresolvedMethod.HasGenericParameters) { var newGenericInstanceMethod = new GenericInstanceMethod(unresolvedMethod); foreach (var gp in unresolvedMethod.GenericParameters) { newGenericInstanceMethod.GenericArguments.Add(Resolve(gp)); } resolvedMethod = newGenericInstanceMethod; } else { resolvedMethod = new MethodReference(unresolvedMethod.Name, unresolvedMethod.ReturnType, declaringType); foreach (var p in unresolvedMethod.Parameters) { resolvedMethod.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, p.ParameterType)); } resolvedMethod.HasThis = unresolvedMethod.HasThis; resolvedMethod.MetadataToken = unresolvedMethod.MetadataToken; } } return resolvedMethod; } /// /// Expands the specified if it is either a or a partially expanded /// /// The type reference. /// TypeReference. public TypeReference Resolve(TypeReference typeReference) { Debug.Assert(typeReference != null); if (IsEmpty()) { return typeReference; } switch (typeReference) { case GenericParameter genericParam: Debug.Assert(genericParam.Owner != null); if (genericParam.Owner.GenericParameterType == GenericParameterType.Type) { Debug.Assert(_typeContext != null); return _typeContext.GenericArguments[genericParam.Position]; } else { Debug.Assert(_methodContext != null); return _methodContext.GenericArguments[genericParam.Position]; } case ArrayType arrayType: return new ArrayType(Resolve(arrayType.ElementType), arrayType.Rank); case PointerType pointerType: return Resolve(pointerType.ElementType).MakePointerType(); case PinnedType pinnedType: return Resolve(pinnedType.ElementType).MakePointerType(); case ByReferenceType byRefType: return Resolve(byRefType.ElementType).MakeByReferenceType(); case RequiredModifierType requiredModType: return new RequiredModifierType(requiredModType.ModifierType, Resolve(requiredModType.ElementType)); case OptionalModifierType optionalModType: return Resolve(optionalModType.ElementType); } if (ContainsGenericParameters(typeReference)) { if (typeReference is GenericInstanceType partialGenericInstance) { // TODO: Ideally, we should cache this GenericInstanceType once it has been resolved var genericInstance = new GenericInstanceType(partialGenericInstance.ElementType); foreach (var genericArgument in partialGenericInstance.GenericArguments) { genericInstance.GenericArguments.Add(Resolve(genericArgument)); } return genericInstance; } else { // Sometimes we can have a TypeDefinition with HasGenericParameters false, but GenericParameters.Count > 0 var typeDefinition = typeReference as TypeDefinition; if (typeDefinition?.GenericParameters.Count > 0) { var genericInstance = new GenericInstanceType(typeDefinition); foreach (var genericArgument in typeDefinition.GenericParameters) { genericInstance.GenericArguments.Add(Resolve(genericArgument)); } return genericInstance; } } } return typeReference; } /// /// If the given type is a reference or pointer type, the underlying type is returned /// /// /// public static TypeReference GetTypeReferenceForPointerOrReference(TypeReference typeReference) { while (true) { switch (typeReference) { case PointerType pointerType: typeReference = pointerType.ElementType; break; case ByReferenceType byRefType: typeReference = byRefType.ElementType; break; default: return typeReference; } } } /// /// Create from a /// /// /// public static GenericContext From(TypeReference typeReference) { Debug.Assert(typeReference != null); if (typeReference is PinnedType pinnedType) { typeReference = pinnedType.ElementType; } typeReference = GetTypeReferenceForPointerOrReference(typeReference); if (typeReference is ArrayType arrayType) { typeReference = arrayType.ElementType; } return new GenericContext(null, typeReference as GenericInstanceType); } /// /// Create from a and a /// /// /// /// public static GenericContext From(MethodReference methodReference, TypeReference typeReference) { Debug.Assert(methodReference != null); Debug.Assert(typeReference != null); typeReference = GetTypeReferenceForPointerOrReference(typeReference); return new GenericContext(methodReference as GenericInstanceMethod, typeReference as GenericInstanceType); } /// /// Checks if the specified TypeReference contains generic parameters that need type expansion /// /// The type reference. /// true if the specified TypeReference contains generic arguments that need type expansion, false otherwise. public static bool ContainsGenericParameters(TypeReference typeReference) { switch (typeReference) { case GenericParameter genericParam: return true; case ArrayType arrayType: return ContainsGenericParameters(arrayType.ElementType); case PointerType pointerType: return ContainsGenericParameters(pointerType.ElementType); case PinnedType pinnedType: return ContainsGenericParameters(pinnedType.ElementType); case ByReferenceType byRefType: return ContainsGenericParameters(byRefType.ElementType); case RequiredModifierType requiredModType: return ContainsGenericParameters(requiredModType.ModifierType); case OptionalModifierType optionalModType: return ContainsGenericParameters(optionalModType.ElementType); case GenericInstanceType partialGenericInstance: { foreach (var genericArgument in partialGenericInstance.GenericArguments) { if (ContainsGenericParameters(genericArgument)) { return true; } } break; } case TypeDefinition typeDefinition: { // Sometimes we can have a TypeDefinition with HasGenericParameters false, but GenericParameters.Count > 0 return typeDefinition.GenericParameters.Count > 0; } } return false; } } }