Skip to content

Type equivalence support in Crossgen2 #87899

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

Conversation

davidwrighton
Copy link
Member

@davidwrighton davidwrighton commented Jun 22, 2023

We currently have a small behavior hole on Windows where if a binary uses TypeEquivalence, it is technically possible to get crossgen2 to generate invalid code.

This fixes that by adding support for type equivalence to the managed type system, and adjusting crossgen2 to use it. This is complicated by the detail that this may generate some type references which cannot be referenced from an R2R file, so additional error checking was needed (this additional error checking is what fixes #67855).

However, while basic support for type equivalence has been added to the type system, support for actually performing type equivalent interface resolution has not been, so devirtualization of type equivalent interface calls is disabled. At this time, I do not expect to see a customer need for implementing that fairly complicated feature

Fixes #67855

return null;
return _data;
}
lock(this)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't generally lock on type system objects because with enough unchecked locks we'll eventually get deadlocks (see another spot where I'm policing this: #87532 (comment)). Would CompareExchange be unacceptable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This was just a bit of laziness on my part.

@davidwrighton davidwrighton requested a review from trylek June 23, 2023 23:16
@davidwrighton
Copy link
Member Author

@trylek the tests are passing, (except for a couple mono issues that are known). Could you take a look and let me know what you can sign off on, and what I need to find someone else to sign off on?

}

// Generic equivalence only allows the instantiation to be non-equal
return thisType.HasSameTypeDefinition(otherType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perf nit - wouldn't it be faster to first check whether the two types have the same type definition and only then traverse the instantiations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Changed

return false;
if (type1.IsInterface)
{
if (!type2.IsInterface)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - I believe this check is superfluous; you have already made sure at line 132 that type1.IsInterface is the same as type2.IsInterface so it seems to me that you should be able to safely return true in this conditional statement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you


return true;
}
else if ((type1.IsEnum != type2.IsEnum) || (type1.IsValueType != type2.IsValueType))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - it might make the code slightly easier to read if you removed the else statement at line 141 - the previous conditional statements act as "filters" that either directly return the result or proceed to the next statement.

}

// Compare the field signatures for equivalence
// TODO: Technically this comparison should include custom modifiers on the field signatures
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a real possibility that we should track with an issue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, no. We don't even check field types on fields correctly today for normal field references, much less custom modifiers.

return field;

// Only public instance fields, and literal fields on enums are permitted in type equivalent structures
if (!enumMode || field.IsLiteral)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be missing something but shouldn't this check be negated? I would assume that !field.IsLiteral is what signifies that the type doesn't support equivalence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Good catch, that inspired me to build a whole test suite, so I found some more issues. Sigh. Thank you though.

@@ -284,6 +294,21 @@ private bool Equals(MethodSignature otherSignature, bool allowCovariantReturn)
}

return false;

static bool CompareTypeHelper(TypeDesc type1, TypeDesc type2, bool allowEquivalence, StackOverflowProtect visited)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming nit - both my old C++ background and Compare-like C# APIs typically return plus / minus / zero result of the comparison whereas this feels more like something in the vein of IsTypeEqualHelper. Not a big deal but I had to fight this mental model half a dozen times while reviewing this change.

Copy link
Member

@trylek trylek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the Crossgen2 logic looks reasonable to me. I'm not a COM expert so that the craziness around GUIDs is somewhat beyond my comprehension, I guess Aaron would be a great pick if you're after more detailed scrutiny in this area. I have shared a couple of observations including one place that I believe to be a bug but I'm not blocking my approval on that as I'm well aware of your long-standing engineering diligence in addressing all review feedback.

int field;
}

// A case where the type is not both public
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - I'm missing the purpose of the word both in this code comment (unless both public is a technical term I haven't yet come across); dtto for the next two test cases.

Copy link
Member

@trylek trylek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for implementing the extensive test suite!

@davidwrighton davidwrighton merged commit d510ea2 into dotnet:main Jul 6, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Aug 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

crossgen2 fails on assemblies that include embedded types
3 participants