Skip to content

Behavior of constrained callvirt should be documented with better clarity #12359

@GeeLaw

Description

@GeeLaw

Type of issue

Missing information

Description

In the documentation of constrained opcode, it says for constrained.thisType callvirt.method:

  • The following should have been pushed onto the evaluation stack, in order: ptr, managed pointer to thisType; then arg1 to argN of the arguments to method.
  • If thisType is reference type, then ptr is dereferenced and passed as this to method.
  • If thisType is value type and implements method, then ptr is passed as this to method.
  • If thisType is value type and does not implement method (from Object, ValueType, Enum, or an interface with default implementation), then ptr is dereferenced, boxed, and passed as this to method.

From the text, it is unclear when ptr is dereferenced (and the referent potentially boxed):

  1. Is it dereferenced when it is pushed onto the evaluation stack?
    • Note that this choice is easily possible, thanks to conservative verification rules and generics sharing rules --- pushing of ptr and constrained. callvirt. must appear in the method body of a single method and generics are not shared for different value types, which allows JIT to know whether ptr is eventually dereferenced when compiling each (shared) instantiation of a method.
  2. Is it dereferenced at the point of constrained. callvirt.?

The two options are not equivalent, because the referent of ptr could be changed after ptr has been pushed before reaching constrained. callvirt..

  • If thisType is reference type, this could change the this inside method.
  • If thisType is value type and implements method, there is no difference.
  • If thisType is value type and does not implement method, this could change what value is being boxed before entering method.

After some simple testing, I found on Windows 11 version 25H2, x64, .NET 10.0:

  • If thisType is reference type, then ptr is dereferenced when it's pushed, which could be long before constrained. callvirt..
  • If thisType is value type and does not implement method, then ptr is dereferenced and boxed at constrained. callvirt., which could be long after it's pushed.

Note that the timings of dereference are different for value types (not implementing method) and reference types.

The most typical use of constrained. callvirt. is C# generics, so naturally this behavior should easily enable C# spec 12.6.6.1 semantics. (Which, unfortunately, is also unclear on the timing of reading from a variable...)

Page URL

https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained?view=net-10.0#system-reflection-emit-opcodes-constrained

Content source URL

https://github.com/dotnet/dotnet-api-docs/blob/main/xml/System.Reflection.Emit/OpCodes.xml

Document Version Independent Id

9831712a-63bd-b93d-be05-0535facb360a

Platform Id

530be95c-469c-3b4c-3a24-431f3aa53d66

Article author

@dotnet-bot

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions