Skip to content
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

RequireMethodImplToRemainInEffectAttribute #35315

Closed
fadimounir opened this issue Apr 22, 2020 · 12 comments
Closed

RequireMethodImplToRemainInEffectAttribute #35315

fadimounir opened this issue Apr 22, 2020 · 12 comments
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Milestone

Comments

@fadimounir
Copy link
Contributor

fadimounir commented Apr 22, 2020

This new attribute will be used by the covariant return types and records C# language features posed for C# 9.0.

Today when we have an explicit override to a method (using MethodImpls), this override gets a new vtable slot on the type, and the slot for the overriden method gets updated to point to the new overriding method. Example:

// Pseudo code with some IL
class A {
    void VirtualFunction() { }
}
class B : A {
    void VirtualFunction() { .override A.VirtualFunction }
}

// VTable for type A:
// [0]: A.VirtualFunction

// VTable for type B:
// [0]: B.VirtualFunction
// [1]: B.VirtualFunction

Now when a new derived type explicitly overrides A.VirtualFunction, it would also get a new vtable slot, and only the vtable slot of the method being overriden gets updated with the new override. Example:

class C : B {
    void VirtualFunction() { .override A.VirtualFunction }
}

// VTable for type A:
// [0]: A.VirtualFunction
//
// VTable for type B:
// [0]: B.VirtualFunction
// [1]: B.VirtualFunction
//
// VTable for type C:
// [0]: C.VirtualFunction
// [1]: B.VirtualFunction   -> This slot does not get overriden
// [2]: C.VirtualFunction

With the new records and covariant return feature, a change of behavior is required to vtable slot overrides when a method gets explictly overriden. In the example above, the second slot on type C would also need to be updated to point to the latest override of the method:

// VTable for type C:
// [0]: C.VirtualFunction
// [1]: C.VirtualFunction   -> This slot needs to be updated as well
// [2]: C.VirtualFunction

The reason for this change is to ensure that any virtual call to the method, whether it uses the base signature or derived signature of the method, we always execute the most derived override:

// Given an object of type C:
callvirt A.VirtualFunction -> Executes C.VirtualFunction
callvirt B.VirtualFunction -> Executes C.VirtualFunction
callvirt C.VirtualFunction -> Executes C.VirtualFunction

To achieve this, a new attribute will be introduced and applied to the MethodImpls that need to propagate to all applicable vtable slots: RequireMethodImplToRemainInEffect attribute. Example:

class A {
    void VirtualFunction() { }
}
class B : A {
    void VirtualFunction() { .override A.VirtualFunction }
}
class C : B {
    [RequireMethodImplToRemainInEffect]
    void VirtualFunction() { .override A.VirtualFunction }
}

This attribute will only be applicable to methods with explicit overrides (MethodImpls), and takes no arguments. The proposed namespace for this attribute is the System.Runtime.CompilerServices namespace. Here is the proposed implementation of the attribute:

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public sealed class RequireMethodImplToRemainInEffectAttribute : Attribute
    {
    }
}
@fadimounir fadimounir added api-ready-for-review blocking Marks issues that we want to fast track in order to unblock other important work labels Apr 22, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Runtime untriaged New issue has not been triaged by the area owner labels Apr 22, 2020
@fadimounir
Copy link
Contributor Author

Quick clarification as well: we do not expect any developer to directly use this attribute. It will be placed by the Roslyn compiler appropriately on methods.

@fadimounir fadimounir changed the title ValidateMethodImplRemainsInEffectAttribute RequireMethodImplToRemainInEffectAttribute Apr 28, 2020
@fadimounir
Copy link
Contributor Author

cc @dotnet/crossgen-contrib

@davidwrighton
Copy link
Member

@vargaz @lambdageek should be looped in on this issue.

@fadimounir
Copy link
Contributor Author

cc @janvorli

@lambdageek
Copy link
Member

We will need to teach the IL linker to keep this attribute /cc @vitek-karas

@MichalStrehovsky
Copy link
Member

We will need to teach the IL linker to keep this attribute /cc @vitek-karas

IL Linker will not strip attributes it knows nothing about because it would be a bug farm. dotnet/linker#952 (comment)

@terrajobst terrajobst added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-ready-for-review labels May 26, 2020
@terrajobst
Copy link
Member

terrajobst commented May 26, 2020

Video

A few questions:

  • We're not entirely sure what this attribute does:
    • The issue talks about covariant returns but all samples use void
    • None of the methdos use virtual functions
    • What are the behavior differences of with and without the attribute? If this can't be expressed in C#, then ILDASM code would help too
  • RequireMethodImplToRemainInEffectAttribute this name seems a bit abstract. Can make this a bit clearer, like PreserveBaseOverridesAttribute?
  • Does it have to be a new attribute or could be a new flag on MethodImplAttributes?
  • How does this attribute play with default interface methods?
  • How does this play with versioning where overrides are added to the middle of the hierarchy later?

@davidwrighton
Copy link
Member

  • This is a feature to adjust the behavior of MethodImpl records, but really this is designed to support the covariant scenario. The reason that the examples are written in terms of void is to emphasize that this is an independent feature at the runtime level from covariant returns, although it is likely that in the near term, it will only be used by the C# compiler when compiling covariant return overrides.
  • The name was written as a placeholder as I am notoriously terrible at naming stuff. I find the PreserveBaseOverridesAttribute naming to be reasonable as well, and if that makes more sense to the api review board, I'm all for it.
  • It does not have to be a new attribute. A new flag MethodImpl would also work if the question were merely about changes in the file format. However, we need to be slightly careful with that as it needs to be hidden from the C# developer, as the flag is NOT something that is acceptable to adjust by setting a flag on the attribute from the C# language. It would need to be set by the compiler itself. Also, using a MethodImpl flag would imply that we need to update ildasm, and ilasm, which otherwise do not require adjustment. Overall, a MethodImpl flag would be "fine".
  • This only applies to MethodImpl records used to override a class virtual method with another class virtual method. It does not interact with default virtual methods.
  • This method actually enhances that experience. It allows there to be considered a single slot that defines how the override works, and changes the override logic so that the covariance will be guaranteed to exist thus preventing a non-covariant override by a further derived type. For instance, consider the following. The new attribute is used to cause the runtime to produce a type load exception instead of allowing the TestClassDerived2 to override the original Method() on TestClassWithVirtualMethod.
class BaseType {}
class DerivedType : BaseType {}

class TestClassWithVirtualMethod
{
    public virtual BaseType Method() {}
}
class TestClassDerived1 : TestClassWithVirtualMethod
{
    public override DerivedType Method() {}
}
class TestClassDerived2 : TestClassDerived1
{
// ERROR!, TypeLoadException!!
    public override BaseType Method() {}
}

@jkotas
Copy link
Member

jkotas commented May 26, 2020

MethodImpl flag

If we choose to encode this in metadata, it should be a bit in the MethodAttributes column where the existing bits that control virtual method resolution live (Virtual, NewSlot, Strict, etc.). MethodImplAttributes does not sound right for this.

@MichalStrehovsky
Copy link
Member

If we choose to encode this in metadata, it should be a bit in the MethodAttributes column

We don't have bits in the MethodAttribute column anymore, unless we want to do creative combinations (mdVirtual + mdUnmanagedExport?), or rev the file format version and reinterpret legacy useless bits differently (mdHasSecurity or mdRequireSecObject).

However, we need to be slightly careful with that as it needs to be hidden from the C# developer, as the flag is NOT something that is acceptable to adjust by setting a flag on the attribute from the C# language. It would need to be set by the compiler itself

Doesn't the same concern apply to the custom attribute? Roslyn already enfoces that user can't set bits in the MethodImpl flag (e.g. you can't set bit 1 and say the method body is native code).

Also, using a MethodImpl flag would imply that we need to update ildasm, and ilasm, which otherwise do not require adjustment.

We'll need to touch ILASM/ILDASM for covariant returns anyway: #37045.

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation blocking Marks issues that we want to fast track in order to unblock other important work labels May 29, 2020
@terrajobst
Copy link
Member

terrajobst commented May 29, 2020

Video

Let's go with PreserveBaseOverridesAttribute then.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    public sealed class PreserveBaseOverridesAttribute : Attribute
    {
    }
}

lambdageek added a commit to lambdageek/runtime that referenced this issue May 29, 2020
from the previous name.

Following latest comments in dotnet#35315
monojenkins pushed a commit to monojenkins/mono that referenced this issue May 29, 2020
Our normal custom attribute lookup method may trigger loading.  This is undesirable during class initialization, for example.  For this reason, we have `mono_class_metadata_foreach_custom_attr` that scans a class for custom attributes without triggering loading.

This PR adds `mono_method_metadata_foreach_custom_attr` that does the same thing - looks for custom attributes on methods without causing any loader activity.

This PR also reorganizes the code in `class-init.c` that uses these metadata custom attribute lookups to be a bit easier to use.

Finally we add a convenience method (unused so far) to look for `PreserveBaseOverridesAttribute` which is defined in dotnet/runtime#35315 and which will be necessary to implement covariant returns.
lambdageek added a commit to mono/mono that referenced this issue Jun 1, 2020
)

Our normal custom attribute lookup method may trigger loading.  This is undesirable during class initialization, for example.  For this reason, we have `mono_class_metadata_foreach_custom_attr` that scans a class for custom attributes without triggering loading.

This PR adds `mono_method_metadata_foreach_custom_attr` that does the same thing - looks for custom attributes on methods without causing any loader activity.

This PR also reorganizes the code in `class-init.c` that uses these metadata custom attribute lookups to be a bit easier to use.

Finally we add a convenience method (unused so far) to look for `PreserveBaseOverridesAttribute` which is defined in dotnet/runtime#35315 and which will be necessary to implement covariant returns.

Co-authored-by: lambdageek <lambdageek@users.noreply.github.com>
lambdageek added a commit that referenced this issue Jun 1, 2020
)

* [metadata] Add mono_method_metadata_foreach_custom_attr

Iterate over the custom attributes on a method in a way that's safe to use
during class initialization.

* [class-init] Generalize search for well-known attributes

Just on classes or methods for now.

Mark `method_has_require_methodimpl_to_remain_in_effect_attribute` with
`G_GNUC_UNUSED` for now - it will be needed for covariant returns

* [class-init] Fix debug message - it's not an iface override

We're going over the virtual methods of the base class looking for ones that
match the subclass.  These are plain old virtual methods, not interface methods

* rename to PreserveBaseOverridesAttribute

from the previous name.

Following latest comments in #35315
@joperezr joperezr removed the untriaged New issue has not been triaged by the area owner label Jul 6, 2020
@joperezr joperezr added this to the Future milestone Jul 6, 2020
@jkotas
Copy link
Member

jkotas commented Jul 29, 2020

Fixed by #35308

@jkotas jkotas closed this as completed Jul 29, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Projects
None yet
Development

No branches or pull requests

8 participants