Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Finalize override lookup algorithm #12753

Merged
merged 17 commits into from
Aug 15, 2017

Conversation

yizhang82
Copy link

@yizhang82 yizhang82 commented Jul 11, 2017

  • properly detect diamond shape positive case (where I4 overrides both I2/I3 which both overrides I1) by keep tracking of a current list of best candidates. I went for the simplest algorithm and didn't build any complex graph / DFS since the majority case the list of interfaces would be small, and interface dispatch cache would ensure majority of cases we don't need to redo the (slow) dispatch. If needed we can revisit this to make it a proper topological sort.
  • VerifyVirtualMethodsImplemented now properly validates default interface scenarios - it is happy if there is at least one implementation and early returns. It doesn't worry about conflicting overrides, for performance reasons.
  • add more test case for diamond shape scenarios, both positive and negative
  • NotSupportedException thrown in conflicting override scenario now has a proper error message
  • properly supports GVM when detecting method impl overrides
  • Revisited code that adds method impl for interfaces. added proper methodimpl validation and ensure methodimpl are virtual and final (and throw exception if it is not final)
  • Added test scenario with method that has multiple method impl. found and fixed a bug where the slot array is not big enough when building method impls for interfaces.
  • Disabled incremental build in this branch as I'm running into incremental linker bugs

This fixes https://github.com/dotnet/coreclr/issues/12149, https://github.com/dotnet/coreclr/issues/12147

@davidwrighton PTAL.

@yizhang82 yizhang82 force-pushed the dev/defaultintf branch 2 times, most recently from 32e1682 to fb212e6 Compare August 1, 2017 17:24
@yizhang82
Copy link
Author

@fadimounir Can you please review this early next week?

CMakeLists.txt Outdated
@@ -344,6 +344,9 @@ if (WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /PDBCOMPRESS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:1572864")

# Disable incremental link due to incremental linking CFG bug crashing crossgen

Choose a reason for hiding this comment

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

Disable incremental link due to incremental linking CFG bug crashing crossgen [](start = 2, length = 79)

Can you file a tracking bug, so that when the linker gets fixed and updated in LKG, we can restore this back?

Also, if you can add a small comment with a brief description of what the bug was, and the bug ID tracking the undoing of this change, that would be awesome :)

Copy link
Author

Choose a reason for hiding this comment

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

Sure. Logged https://github.com/dotnet/coreclr/issues/13344 and added comment

@@ -1252,6 +1252,8 @@ BEGIN
IDS_CLASSLOAD_MI_VIRTUALMISMATCH "Method '%3' on type '%1' from assembly '%2' tried to implement a method declaration with a different virtual state."
IDS_CLASSLOAD_MI_MUSTBEVIRTUAL "Method '%3' on type '%1' from assembly '%2' must be virtual to implement a method on an interface or super type."
IDS_CLASSLOAD_MI_BAD_SIG "Type '%1' from assembly '%2' contains an invalid method implementation signature."
IDS_CLASSLOAD_MI_FINAL_IMPL "Method implementation on an interface must be a final method. Type: '%1'. Assembly: '%2'."

Choose a reason for hiding this comment

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

Method implementation on an interface must be a final method. Type: '%1'. Assembly: '%2'." [](start = 45, length = 92)

Change the text to be:

"Method implementation on interface '%1' from assembly '%2' must be a final method."

To follow the pattern

Copy link
Author

Choose a reason for hiding this comment

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

sounds good. fixed.

TypeString::AppendType(strTargetClassName, pTargetClass);

COMPlusThrow(
kNotSupportedException,

Choose a reason for hiding this comment

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

kNotSupportedException [](start = 8, length = 22)

I think it would make more sense to make this a TypeLoadException, or do we hit this code path during an actual call instead of during type loading?
If it's type loading, then we should change this to TypeLoadException

Copy link
Author

Choose a reason for hiding this comment

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

This is in a interface dispatch. So not TypeLoadException :)

continue;

MethodTable *pDeclMT = pDeclMD->GetMethodTable();
BOOL bMatch = FALSE;

Choose a reason for hiding this comment

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

BOOL bMatch = FALSE; [](start = 36, length = 20)

unused variable

Copy link
Author

Choose a reason for hiding this comment

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

removed

{
// This is a matching override. We'll instantiate pCurMD later
pCurMD = pMD;
break;

Choose a reason for hiding this comment

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

break [](start = 44, length = 5)

This will just break from the inner loop. I think you also want to break out of the outer loop

Copy link
Author

Choose a reason for hiding this comment

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

Actually the outter loop also compares each match against all current list of candidates. So this is correct. But I do agree the code is getting complicated and the else statement is too big. I'll refactor it

}
else if (pDeclMD == pInterfaceMD)
{
// Exact match override
pCurMD = pMD;
break;

Choose a reason for hiding this comment

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

break [](start = 40, length = 5)

Same

Copy link
Author

Choose a reason for hiding this comment

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

See above

// Find the default interface implementation method for interface dispatch
// It is either the interface method with default interface method implementation,
// or an most specific interface with an explicit methodimpl overriding the method
BOOL MethodTable::FindDefaultInterfaceImplementation(

Choose a reason for hiding this comment

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

FindDefaultInterfaceImplementation [](start = 18, length = 34)

This method is becoming increasingly complicated, with many levels of nested loops. Could you please refactor?

Copy link
Author

Choose a reason for hiding this comment

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

Agreed. I'll break this up.

{
if (!pBestCandidateMT->CanCastToInterface(pCurMT))
// pCurMT is a more specific choice
// If pCurMT is more specific than IFoo and IBar, only update first entry IFoo and null out IBar

Choose a reason for hiding this comment

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

IFoo and IBar [](start = 63, length = 13)

Please add a comment at the begining of the function with an example of the type hierarchy that you are referring to here, to make it more clear

if (pCandidateMT == NULL)
continue;

if (pCandidateMT->HasSameTypeDefAs(pCurMT))

Choose a reason for hiding this comment

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

if (pCandidateMT->HasSameTypeDefAs(pCurMT)) [](start = 24, length = 43)

Is there a case where we need to check first if pCandidateMT == pCurMT?

Choose a reason for hiding this comment

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

HasSameTypeDefAs will return true even when 2 instantiations are different, if the generic typedef is the same.


In reply to: 132596188 [](ancestors = 132596188)

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, changed to ==

if (!pBestCandidateMT->CanCastToInterface(pCurMT))
// pCurMT is a more specific choice
// If pCurMT is more specific than IFoo and IBar, only update first entry IFoo and null out IBar
if (!seenMoreSpecific)

Choose a reason for hiding this comment

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

seenMoreSpecific [](start = 33, length = 16)

Can there be an even more specific choice further in the array?

ex: if array[4] is more specific that array[2], in that case your code is clearing out array[4], and using array[2] as the 'more specific'

Copy link
Author

@yizhang82 yizhang82 Aug 11, 2017

Choose a reason for hiding this comment

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

The more specific is always the current pCurMT in this case (not array[4] or array[2]). So we effectively NULL-out all less specific entries and assign the first one to pCurMT. There is no point keeping array[4] or array[2] at all - only pCurMT "survives".

@fadimounir
Copy link

🕐

@fadimounir
Copy link

There are some code changes specific to the generic cases with variance. Please add some tests to cover the variant case to make sure it works (ex: see my comment on 'pCandidateMT == pCurMT'). There might be some bugs with variance (or not :) )

@yizhang82
Copy link
Author

I'll address refactoring in another PR.

@yizhang82 yizhang82 merged commit 3f0dd78 into dotnet:dev/defaultintf Aug 15, 2017
@karelz karelz modified the milestone: Future Aug 28, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants