mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-09 17:44:48 +09:00
Update docs for ByRefLike with generics for work in .NET 10 (#103318)
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
This commit is contained in:
parent
053d1a7449
commit
7134e1e6a4
6 changed files with 451 additions and 117 deletions
|
@ -7,22 +7,26 @@ Using ByRefLike types in Generic parameters is possible by building upon support
|
|||
|
||||
## Runtime impact
|
||||
|
||||
Supporting ByRefLike type as Generic parameters will impact the following IL instructions:
|
||||
Supporting ByRefLike types as Generic parameters will impact the following IL instructions.
|
||||
|
||||
- `box` – Types with ByRefLike parameters used in fields cannot be boxed.
|
||||
The `constrained. callvirt` sequence is valid if a ByRefLike type is provided. A `NotSupportedException` will be thrown at the call-site, if the target resolves to a method implemented on `object` or a default interface method.
|
||||
|
||||
Throws `InvalidProgramException` when passed a ByRefLike type:
|
||||
- `box` – ByRefLike types cannot be allocated on the heap.
|
||||
|
||||
Throws `TypeLoadException` when passed a ByRefLike type:
|
||||
- `stsfld` / `ldsfld` – Type fields of a ByRefLike parameter cannot be marked `static`.
|
||||
- `newarr` / `stelem` / `ldelem` / `ldelema` – Arrays are not able to contain ByRefLike types.
|
||||
- `newobj` – For multi-dimensional array construction.
|
||||
- `constrained.callvirt` – If this IL sequence resolves to a method implemented on `object` or default interface method, an error will occur during the attempt to box the instance.
|
||||
|
||||
If any of the above instructions are attempted to be used with a ByRefLike type, the runtime will throw an `InvalidProgramException`. Sequences involving some of the above instructions are considered optimizations and represent cases that will remain valid regardless of a `T` being ByRefLike. See "Special IL Sequences" section below for details.
|
||||
|
||||
The following instructions are already set up to support this feature since their behavior will fail as currently defined due to the inability to box a ByRefLike type.
|
||||
|
||||
- `throw` – Requires an object reference to be on stack, which can never be a ByRefLike type.
|
||||
- `unbox` / `unbox.any` – Requires an object reference to be on stack, which can never be a ByRefLike type.
|
||||
- `isinst` – Will always place `null` on stack.
|
||||
- `castclass` – Will always throw `InvalidCastException`.
|
||||
- `throw`
|
||||
- `unbox` / `unbox.any`
|
||||
- `isinst`
|
||||
- `castclass`
|
||||
|
||||
**NOTE** There are sequences involving some of the above instructions that may remain valid regardless of a `T` being ByRefLike—see ["Options for invalid IL" section](#invalid_il_options) below for details.
|
||||
|
||||
The expansion of ByRefLike types as Generic parameters does not relax restrictions on where ByRefLike types can be used. When `T` is ByRefLike, the use of `T` as a field will require the enclosing type to be ByRefLike.
|
||||
|
||||
|
@ -110,23 +114,123 @@ throw
|
|||
|
||||
Adding `gpAcceptByRefLike` to the metadata of a Generic parameter will be considered a non-breaking binary change.
|
||||
|
||||
Enumerating of constructors/methods on `Span<T>` and `ReadOnlySpan<T>` may throw `TypeLoadException` if `T` is a ByRefLike type. See "Troublesome APIs" above for the list of APIs that cause this condition.
|
||||
Enumerating of constructors/methods on `Span<T>` and `ReadOnlySpan<T>` may throw `TypeLoadException` if `T` is a ByRefLike type. See "Troublesome API mitigation" above for the list of APIs that cause this condition.
|
||||
|
||||
## Special IL Sequences
|
||||
## <a name="invalid_il_options"></a> Options for invalid IL
|
||||
|
||||
The following are IL sequences involving the `box` instruction. They are used for common C# language constructs and shall continue to be valid, even with ByRefLike types, in cases where the result can be computed at JIT time and elided safely. These sequences must now be elided when the target type is ByRefLike. The conditions where each sequence is elided are described below and each condition will be added to the ECMA-335 addendum.
|
||||
There are two potential options below for how to address this issue. Based on communication with the Roslyn team, option (1) is the current plan of record for .NET 10.
|
||||
|
||||
`box` ; `unbox.any` – The box target type is equal to the unboxed target type.
|
||||
The first indented IL sequences below represents the `is-type` sequence. Combining the first with the second indented section represents the "type pattern matching" scenario in C#. The below sequence performs a type check and then, if successful, consumes the unboxed instance.
|
||||
|
||||
`box` ; `br_true/false` – The box target type is non-`Nullable<T>`.
|
||||
```IL
|
||||
// Type check
|
||||
ldarg.0
|
||||
box <Source>
|
||||
isinst <Target>
|
||||
brfalse.s NOT_INST
|
||||
|
||||
`box` ; `isinst` ; `unbox.any` – The box, `isint`, and unbox target types are all equal.
|
||||
// Unbox and store unboxed instance
|
||||
ldarg.0
|
||||
box <Source>
|
||||
isinst <Target>
|
||||
unbox.any <Target>
|
||||
stloc.X
|
||||
|
||||
`box` ; `isinst` ; `br_true/false` – The box target type is equal to the unboxed target type or the box target type is `Nullable<T>` and target type equalities can be computed.
|
||||
NOT_INST:
|
||||
ret
|
||||
```
|
||||
|
||||
With the above IL composition implemented, the following C# describes the following "type pattern matching" scenarios and what one might expect given current C# semantics.
|
||||
|
||||
```csharp
|
||||
struct S {}
|
||||
struct S<T> {}
|
||||
ref struct RS {}
|
||||
ref struct RS<T> {}
|
||||
interface I {}
|
||||
class C {}
|
||||
class C<T> {}
|
||||
|
||||
// Not currently valid C#
|
||||
void M<T, U>(T t) where T: allows ref struct
|
||||
{
|
||||
// Valid
|
||||
if (t is int i)
|
||||
|
||||
if (t is S s)
|
||||
if (t is S<char> sc)
|
||||
if (t is S<U> su)
|
||||
|
||||
if (t is RS rs)
|
||||
if (t is RS<char> rsc)
|
||||
if (t is RS<U> rsu)
|
||||
|
||||
if (t is string str)
|
||||
if (t is C c)
|
||||
if (t is C<I> ci)
|
||||
if (t is C<U> cu)
|
||||
|
||||
// Can be made to work in IL.
|
||||
if (t is I itf) // A new local "I" would not be used for ByRefLike scenarios.
|
||||
// The local would be the ByRefLike type, not "I".
|
||||
|
||||
// Invalid
|
||||
if (t is object o) // ByRefLike types evaluate "true" for object.
|
||||
if (t is U u)
|
||||
}
|
||||
```
|
||||
|
||||
### Option 1) Compiler helpers
|
||||
|
||||
The following two helper functions could be introduced and would replace currently invalid `is-type` IL sequences when ByRefLike types are involved. Their behavior would broadly be defined to operate as if the ByRefLike aspect of either the `TFrom` and `TTo` is not present. An alternative approach would be consult with the Roslyn team and define the semantics of these functions to adhere to C# language rules.
|
||||
|
||||
```csharp
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
public static class RuntimeHelpers
|
||||
{
|
||||
// Replacement for the [box; isinst; brfalse/true] sequence.
|
||||
public static bool IsInstanceOf<TFrom, TTo>(TFrom source)
|
||||
where TFrom: allows ref struct
|
||||
where TTo: allows ref struct;
|
||||
|
||||
// Replacement for the [box; isinst; unbox.any] sequence.
|
||||
// Would throw InvalidCastException for invalid use at run-time.
|
||||
// For example:
|
||||
// TFrom: RS, TTo: object => always throws
|
||||
// TFrom: RS, TTo: <interface> => always throws
|
||||
public static TTo CastTo<TFrom, TTo>(TFrom source)
|
||||
where TFrom: allows ref struct
|
||||
where TTo: allows ref struct;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example usage of the above methods.
|
||||
|
||||
```csharp
|
||||
TTo result;
|
||||
if (RuntimeHelpers.IsInstanceOf<TFrom, TTo>(source))
|
||||
{
|
||||
result = RuntimeHelpers.CastTo<TFrom, TTo>(source);
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2) Special IL sequences
|
||||
|
||||
The following are IL sequences involving the `box` instruction. They are used for common C# language constructs and would continue to be valid, even with ByRefLike types. These sequences would be **required** to be valid when the target type is ByRefLike. Each sequence would be added to the ECMA-335 addendum.
|
||||
|
||||
`box` ; `isinst` ; `br_true/false` – Passing a ByRefLike type as the argument to the `box` instruction is permitted to accomplish a type check, in C# `x is Y`. **Note** ByRefLike types would evaluate to `true` when compared against `System.Object`.
|
||||
|
||||
`box` ; `isinst` ; `unbox.any` – In order to permit "type pattern matching", in C# `x is Y y`, this sequence will permit use of a ByRefLike type on any instruction, but does not permit the use of generic parameters being exposed to `isinst` or `unbox.any`.
|
||||
|
||||
`box` ; `unbox.any` – Valid to use ByRefLike types.
|
||||
|
||||
`box` ; `br_true/false` – Valid to use ByRefLike types.
|
||||
|
||||
## Examples
|
||||
|
||||
Below are valid and invalid examples of ByRefLike as Generic parameters. All examples use the **not official** syntax, `allows ref struct`, for indicating the Generic permits ByRefLike types.
|
||||
Below are currently (.NET 9) valid and invalid examples of ByRefLike as Generic parameters.
|
||||
|
||||
**1) Valid**
|
||||
```csharp
|
||||
|
|
|
@ -14,7 +14,8 @@ This is a list of additions and edits to be made in ECMA-335 specifications. It
|
|||
- [Covariant Return Types](#covariant-return-types)
|
||||
- [Function Pointer Type Identity](#function-pointer-type-identity)
|
||||
- [Unsigned data conversion with overflow detection](#unsigned-data-conversion-with-overflow-detection)
|
||||
- [Ref field support](#ref-fields)
|
||||
- [Ref fields support](#ref-fields)
|
||||
- [ByRefLike types in generics](#byreflike-generics)
|
||||
- [Rules for IL rewriters](#rules-for-il-rewriters)
|
||||
- [Checked user-defined operators](#checked-user-defined-operators)
|
||||
- [Atomic reads and writes](#atomic-reads-and-writes)
|
||||
|
@ -1026,6 +1027,31 @@ Changes to signatures:
|
|||
- Add a bullet point
|
||||
- Managed pointers which point at null, the address just past the end of an object, or the address where an element just past the end of an array would be stored, are permitted but not dereferenceable.
|
||||
|
||||
## <a name="byreflike-generics"></a> ByRefLike types in generics
|
||||
|
||||
ByRefLike types, defined in C# with the `ref struct` syntax, represent types that cannot escape to the managed heap and must remain on the stack. It is possible for these types to be used as generic parameters, but in order to improve utility certain affordances are required.
|
||||
|
||||
### II.10.1.7
|
||||
An additional IL keyword, `byreflike`, is introduced to indicate use of ByRefLike types is permitted. This expands the set of permissible types used by this parameters, but limits the potential instructions that can be used on instances of this generic parameter type.
|
||||
|
||||
### II.23.1.7
|
||||
Update the `SpecialConstraintMask` flag value and description, and add a new flag, `AllowByRefLike`.
|
||||
|
||||
| Flag | Value | Description |
|
||||
| --- | ----- | ----------- |
|
||||
| `SpecialConstraintMask` | `0x3C` | These 4 bits contain one of the following values: |
|
||||
| ... | ... | ... |
|
||||
| `AllowByRefLike` | `0x20` | The generic parameter is allowed to be ByRefLike |
|
||||
|
||||
### III.2.1
|
||||
The following case is added as the **third** cases in the "if _thisType_" sequence.
|
||||
|
||||
> If _thisType_ is ByRefLike and _thisType_ does not implement _method_ then; a `NotSupportedException` is thrown at the callsite.
|
||||
|
||||
The following is added to the paragraph starting with "This last case can only occur when _method_ was defined on `System.Object`, `System.ValueType`, or `System.Enum`".
|
||||
|
||||
> The third case can only occur when _method_ was defined on `System.Object` or is a Default Interface Method.
|
||||
|
||||
## Rules for IL Rewriters
|
||||
|
||||
There are apis such as `System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan<T>(...)` which require that the PE file have a particular structure. In particular, that api requires that the associated RVA of a FieldDef which is used to create a span must be naturally aligned over the data type that `CreateSpan` is instantiated over. There are 2 major concerns.
|
||||
|
|
|
@ -414,10 +414,10 @@ namespace System.Runtime
|
|||
[RuntimeExport("RhTypeCast_CheckCastClassSpecial")]
|
||||
private static unsafe object CheckCastClassSpecial(MethodTable* pTargetType, object obj)
|
||||
{
|
||||
Debug.Assert(!pTargetType->IsParameterizedType, "CheckCastClass called with parameterized MethodTable");
|
||||
Debug.Assert(!pTargetType->IsFunctionPointer, "CheckCastClass called with function pointer MethodTable");
|
||||
Debug.Assert(!pTargetType->IsInterface, "CheckCastClass called with interface MethodTable");
|
||||
Debug.Assert(!pTargetType->HasGenericVariance, "CheckCastClass with variant MethodTable");
|
||||
Debug.Assert(!pTargetType->IsParameterizedType, "CheckCastClassSpecial called with parameterized MethodTable");
|
||||
Debug.Assert(!pTargetType->IsFunctionPointer, "CheckCastClassSpecial called with function pointer MethodTable");
|
||||
Debug.Assert(!pTargetType->IsInterface, "CheckCastClassSpecial called with interface MethodTable");
|
||||
Debug.Assert(!pTargetType->HasGenericVariance, "CheckCastClassSpecial with variant MethodTable");
|
||||
|
||||
MethodTable* mt = obj.GetMethodTable();
|
||||
Debug.Assert(mt != pTargetType, "The check for the trivial cases should be inlined by the JIT");
|
||||
|
|
|
@ -93,14 +93,6 @@
|
|||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig
|
||||
instance object BoxAsObject(!T) cil managed
|
||||
{
|
||||
ldarg.1
|
||||
box !T
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig
|
||||
instance bool BoxUnboxAny(!T) cil managed
|
||||
{
|
||||
|
@ -230,7 +222,7 @@
|
|||
// Begin sequence
|
||||
box !T
|
||||
isinst ByRefLikeType
|
||||
unbox.any !T
|
||||
unbox.any ByRefLikeType
|
||||
// End sequence
|
||||
pop
|
||||
ldc.i4.1
|
||||
|
@ -334,22 +326,27 @@
|
|||
}
|
||||
|
||||
.method public hidebysig
|
||||
instance bool BoxIsinstBranch_UsingTypeConstraints<byreflike (InvalidCSharp.EmptyInterface) U>(class InvalidCSharp.EmptyInterface) cil managed
|
||||
instance bool BoxIsinstBranch_CheckForSimpleInterface<byreflike U>(!!U) cil managed
|
||||
{
|
||||
.locals init (
|
||||
[0] !!U
|
||||
)
|
||||
|
||||
ldarg.1
|
||||
isinst !!U
|
||||
brfalse.s NOT_U
|
||||
box !!U
|
||||
isinst InvalidCSharp.SimpleInterface
|
||||
brtrue.s IS_SIMPLEINTERFACE
|
||||
|
||||
ldstr "All types should implement SimpleInterface"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
IS_SIMPLEINTERFACE:
|
||||
ldarg.1
|
||||
isinst !!U
|
||||
unbox.any !!U
|
||||
stloc.0
|
||||
ldc.i4.0
|
||||
ret
|
||||
NOT_U:
|
||||
ldc.i4.1
|
||||
ldloca.s 0
|
||||
constrained. !!U
|
||||
callvirt instance int32 InvalidCSharp.SimpleInterface::Method()
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -363,17 +360,6 @@
|
|||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig
|
||||
instance bool AllocMultiDimArrayOfT() cil managed
|
||||
{
|
||||
ldc.i4.1
|
||||
ldc.i4.1
|
||||
newobj instance void !T[0..., 0...]::.ctor(int32, int32)
|
||||
ldnull
|
||||
cgt.un
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig
|
||||
instance bool InstanceOfT(
|
||||
object o
|
||||
|
@ -465,27 +451,81 @@
|
|||
)
|
||||
}
|
||||
|
||||
.class interface public auto ansi abstract InvalidCSharp.EmptyInterface
|
||||
{
|
||||
}
|
||||
|
||||
.class public sequential ansi sealed beforefieldinit InvalidCSharp.ByRefLikeTypeWithInterface
|
||||
.class public sequential ansi sealed beforefieldinit ByRefLikeType`1<T>
|
||||
extends [System.Runtime]System.ValueType
|
||||
implements InvalidCSharp.EmptyInterface
|
||||
{
|
||||
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
|
||||
01 00 00 00
|
||||
)
|
||||
}
|
||||
|
||||
.class interface public auto ansi abstract InvalidCSharp.SimpleInterface
|
||||
{
|
||||
.method public hidebysig newslot abstract virtual
|
||||
instance int32 Method() cil managed
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
.class public sequential ansi sealed beforefieldinit ByRefLikeTypeWithInterface
|
||||
extends [System.Runtime]System.ValueType
|
||||
implements InvalidCSharp.SimpleInterface
|
||||
{
|
||||
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
|
||||
01 00 00 00
|
||||
)
|
||||
|
||||
.method public final hidebysig newslot virtual
|
||||
instance int32 Method() cil managed
|
||||
{
|
||||
ldc.i4.1
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
.class interface public auto ansi abstract InvalidCSharp.DefaultInterface
|
||||
{
|
||||
.method public hidebysig newslot virtual
|
||||
instance int32 Method() cil managed
|
||||
{
|
||||
ldc.i4.0
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
.class public sequential ansi sealed beforefieldinit RS_DI1
|
||||
extends [System.Runtime]System.ValueType
|
||||
implements InvalidCSharp.DefaultInterface
|
||||
{
|
||||
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
|
||||
01 00 00 00
|
||||
)
|
||||
}
|
||||
|
||||
.class public sequential ansi sealed beforefieldinit RS_DI2
|
||||
extends [System.Runtime]System.ValueType
|
||||
implements InvalidCSharp.DefaultInterface
|
||||
{
|
||||
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
|
||||
01 00 00 00
|
||||
)
|
||||
|
||||
.method public hidebysig newslot virtual
|
||||
instance int32 Method() cil managed
|
||||
{
|
||||
ldc.i4.1
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
.class public sequential ansi sealed beforefieldinit RegularValueType
|
||||
extends [System.Runtime]System.ValueType
|
||||
{
|
||||
}
|
||||
|
||||
.class public auto ansi beforefieldinit InvalidCSharp.ClassWithInterface
|
||||
.class public auto ansi beforefieldinit ClassWithInterface
|
||||
extends [System.Runtime]System.Object
|
||||
implements InvalidCSharp.EmptyInterface
|
||||
implements InvalidCSharp.SimpleInterface
|
||||
{
|
||||
.method public hidebysig specialname rtspecialname
|
||||
instance void .ctor () cil managed
|
||||
|
@ -494,6 +534,13 @@
|
|||
call instance void [System.Runtime]System.Object::.ctor()
|
||||
ret
|
||||
}
|
||||
|
||||
.method public final hidebysig newslot virtual
|
||||
instance int32 Method() cil managed
|
||||
{
|
||||
ldc.i4.0
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
// Generic substitution of allow-byreflike with allow-byreflike
|
||||
|
@ -662,14 +709,14 @@
|
|||
.method public hidebysig static
|
||||
class [System.Runtime]System.Type GenericByRefLike_ConstraintsAreIndependent_Interface_ByRefLike_Invalid() cil managed
|
||||
{
|
||||
newobj instance void class InvalidCSharp.GenericClass_IndependentConstraints`2<class InvalidCSharp.EmptyInterface, valuetype InvalidCSharp.ByRefLikeTypeWithInterface>::.ctor()
|
||||
newobj instance void class InvalidCSharp.GenericClass_IndependentConstraints`2<class InvalidCSharp.SimpleInterface, valuetype ByRefLikeTypeWithInterface>::.ctor()
|
||||
callvirt instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType()
|
||||
ret
|
||||
}
|
||||
.method public hidebysig static
|
||||
class [System.Runtime]System.Type GenericByRefLike_ConstraintsAreIndependent_ByRefLike_ByRefLike_Invalid() cil managed
|
||||
{
|
||||
newobj instance void class InvalidCSharp.GenericClass_IndependentConstraints`2<valuetype InvalidCSharp.ByRefLikeTypeWithInterface, valuetype InvalidCSharp.ByRefLikeTypeWithInterface>::.ctor()
|
||||
newobj instance void class InvalidCSharp.GenericClass_IndependentConstraints`2<valuetype ByRefLikeTypeWithInterface, valuetype ByRefLikeTypeWithInterface>::.ctor()
|
||||
callvirt instance class [System.Runtime]System.Type [System.Runtime]System.Object::GetType()
|
||||
ret
|
||||
}
|
||||
|
@ -693,23 +740,15 @@
|
|||
}
|
||||
|
||||
.method public hidebysig static
|
||||
object BoxAsObject() cil managed
|
||||
object BoxAsObject<byreflike Y>(!!Y) cil managed noinlining
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
)
|
||||
|
||||
ldloca.s 0
|
||||
initobj valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
ldloca.s 0
|
||||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance object valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxAsObject(!0)
|
||||
ldarg.0
|
||||
box !!Y
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
bool BoxUnboxAny() cil managed
|
||||
void BoxUnboxAny() cil managed
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
|
@ -721,15 +760,21 @@
|
|||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxUnboxAny(!0)
|
||||
brtrue.s NEXT_1
|
||||
|
||||
ldstr "Failed: BoxUnboxAny for ByRefLikeType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_1:
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
bool BoxBranch() cil managed
|
||||
void BoxBranch() cil managed
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>,
|
||||
[1] bool
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
)
|
||||
|
||||
ldloca.s 0
|
||||
|
@ -739,30 +784,51 @@
|
|||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxBranch(!0)
|
||||
pop
|
||||
brtrue.s NEXT_1
|
||||
|
||||
ldstr "Failed: BoxBranch for ByRefLikeType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_1:
|
||||
ldloca.s 0
|
||||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxBranchToOther<valuetype InvalidCSharp.ByRefLikeTypeWithInterface>(!0)
|
||||
pop
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxBranchToOther<valuetype ByRefLikeTypeWithInterface>(!0)
|
||||
brtrue.s NEXT_2
|
||||
|
||||
ldstr "Failed: BoxBranchToOther for ByRefLikeTypeWithInterface"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_2:
|
||||
ldloca.s 0
|
||||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxBranchToOther<valuetype RegularValueType>(!0)
|
||||
pop
|
||||
brtrue.s NEXT_3
|
||||
|
||||
ldstr "Failed: BoxBranchToOther for RegularValueType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_3:
|
||||
ldloca.s 0
|
||||
ldloca.s 0
|
||||
ldflda !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxBranch_WithSideEffects(!0&)
|
||||
brtrue.s NEXT_4
|
||||
|
||||
ldstr "Failed: BoxBranch_WithSideEffects for ByRefLikeType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_4:
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
bool BoxIsinstUnboxAny() cil managed
|
||||
void BoxIsinstUnboxAny() cil managed
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
|
@ -774,6 +840,13 @@
|
|||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxIsinstUnboxAny(!0)
|
||||
brtrue.s NEXT_1
|
||||
|
||||
ldstr "Failed: BoxIsinstUnboxAny for ByRefLikeType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_1:
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -794,10 +867,11 @@
|
|||
}
|
||||
|
||||
.method public hidebysig static
|
||||
bool BoxIsinstBranch() cil managed
|
||||
void BoxIsinstBranchVarious() cil managed
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>,
|
||||
[1] valuetype ByRefLikeTypeWithInterface
|
||||
)
|
||||
|
||||
ldloca.s 0
|
||||
|
@ -807,48 +881,87 @@
|
|||
ldloc 0
|
||||
ldfld !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxIsinstBranch(!0)
|
||||
pop
|
||||
brtrue.s NEXT_1
|
||||
|
||||
ldstr "Failed: BoxIsinstBranch for ByRefLikeType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_1:
|
||||
ldloca.s 0
|
||||
ldloca.s 0
|
||||
ldflda !0 valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::Field
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxIsinstBranch_WithSideEffects(!0&)
|
||||
pop
|
||||
brtrue.s NEXT_2
|
||||
|
||||
ldstr "Failed: BoxIsinstBranch_WithSideEffects for ByRefLikeType"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_2:
|
||||
ldloca.s 0
|
||||
newobj instance void InvalidCSharp.ClassWithInterface::.ctor()
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::BoxIsinstBranch_UsingTypeConstraints<valuetype InvalidCSharp.ByRefLikeTypeWithInterface>(class InvalidCSharp.EmptyInterface)
|
||||
ldloca.s 1
|
||||
initobj valuetype ByRefLikeTypeWithInterface
|
||||
ldloc.1
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::
|
||||
BoxIsinstBranch_CheckForSimpleInterface<valuetype ByRefLikeTypeWithInterface>(!!0)
|
||||
brtrue.s NEXT_3
|
||||
|
||||
ldstr "Failed: BoxIsinstBranch_CheckForSimpleInterface for ByRefLikeTypeWithInterface"
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_3:
|
||||
ldloca.s 0
|
||||
newobj instance void ClassWithInterface::.ctor()
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::
|
||||
BoxIsinstBranch_CheckForSimpleInterface<class ClassWithInterface>(!!0)
|
||||
brfalse.s NEXT_4
|
||||
|
||||
ldstr "The above is expected to be false since ClassWithInterface's interface implementation return 0."
|
||||
newobj instance void [System.Runtime]System.Exception::.ctor(string)
|
||||
throw
|
||||
|
||||
NEXT_4:
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
void AllocArrayOfT_Invalid() cil managed
|
||||
bool BoxIsinstBranch<byreflike T, byreflike U>(!!T) cil managed noinlining
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
)
|
||||
ldarg.0
|
||||
// Begin sequence
|
||||
box !!T
|
||||
isinst !!U
|
||||
brfalse.s IS_FALSE
|
||||
// End sequence
|
||||
|
||||
ldloca.s 0
|
||||
initobj valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
ldloca.s 0
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::AllocArrayOfT()
|
||||
pop
|
||||
ldc.i4.1
|
||||
ret
|
||||
|
||||
IS_FALSE:
|
||||
ldc.i4.0
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
void AllocMultiDimArrayOfT_Invalid() cil managed
|
||||
bool AllocArray<byreflike Y>() cil managed noinlining
|
||||
{
|
||||
.locals init (
|
||||
[0] valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
)
|
||||
ldc.i4.1
|
||||
newarr !!Y
|
||||
ldnull
|
||||
cgt.un
|
||||
ret
|
||||
}
|
||||
|
||||
ldloca.s 0
|
||||
initobj valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>
|
||||
ldloca.s 0
|
||||
call instance bool valuetype InvalidCSharp.GenericByRefLike_Over`1<valuetype ByRefLikeType>::AllocMultiDimArrayOfT()
|
||||
pop
|
||||
.method public hidebysig static
|
||||
bool AllocMultiDimArray<byreflike Y>() cil managed noinlining
|
||||
{
|
||||
ldc.i4.1
|
||||
ldc.i4.1
|
||||
newobj instance void !!Y[0..., 0...]::.ctor(int32, int32)
|
||||
ldnull
|
||||
cgt.un
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -861,6 +974,39 @@
|
|||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
string ConstrainedCallVirtToString<byreflike T>(!!T t) cil managed noinlining
|
||||
{
|
||||
ldarga.s 0
|
||||
constrained. !!T
|
||||
callvirt instance string [System.Runtime]System.Object::ToString()
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
int32 ConstrainedCallVirtMethod<byreflike (InvalidCSharp.DefaultInterface) T>(!!T t) cil managed noinlining
|
||||
{
|
||||
ldarga.s 0
|
||||
constrained. !!T
|
||||
callvirt instance int32 InvalidCSharp.DefaultInterface::Method()
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
int32 ConstrainedCallVirtMethod<byreflike (InvalidCSharp.DefaultInterface) T>(!!T t, bool skipCall) cil managed noinlining
|
||||
{
|
||||
ldarg.1
|
||||
brfalse.s CALL
|
||||
ldc.i4.m1
|
||||
ret
|
||||
|
||||
CALL:
|
||||
ldarga.s 0
|
||||
constrained. !!T
|
||||
callvirt instance int32 InvalidCSharp.DefaultInterface::Method()
|
||||
ret
|
||||
}
|
||||
|
||||
.method public hidebysig static
|
||||
bool InstanceOfT(object) cil managed
|
||||
{
|
||||
|
|
|
@ -56,15 +56,67 @@ public class Validate
|
|||
Assert.Throws<InvalidCastException>(() => { Exec.UnboxToT(new object()); });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Validate_RecognizedOpCodeSequences_Scenarios()
|
||||
{
|
||||
Console.WriteLine($"{nameof(Validate_RecognizedOpCodeSequences_Scenarios)}...");
|
||||
interface I1 { }
|
||||
|
||||
Assert.True(Exec.BoxUnboxAny());
|
||||
Assert.True(Exec.BoxBranch());
|
||||
Assert.True(Exec.BoxIsinstUnboxAny());
|
||||
Assert.True(Exec.BoxIsinstBranch());
|
||||
struct S {}
|
||||
struct S<T> {}
|
||||
struct S_I1 : I1 {}
|
||||
struct S_I1<T> : I1 {}
|
||||
struct S_DI1 : InvalidCSharp.DefaultInterface {}
|
||||
struct S_DI2 : InvalidCSharp.DefaultInterface { public int Method() => 1; }
|
||||
|
||||
ref struct RS { }
|
||||
ref struct RS<T> { }
|
||||
ref struct RS_I1 : I1 { }
|
||||
ref struct RS_I1<T> : I1 { }
|
||||
// ref struct RS_DI1 - See InvalidCSharp.il
|
||||
// ref struct RS_DI2 - See InvalidCSharp.il
|
||||
|
||||
sealed class Ignored { }
|
||||
|
||||
[Fact]
|
||||
public static void Validate_RecognizedOpCodeSequences()
|
||||
{
|
||||
Console.WriteLine($"{nameof(Validate_RecognizedOpCodeSequences)}...");
|
||||
|
||||
Exec.BoxUnboxAny();
|
||||
Exec.BoxBranch();
|
||||
Exec.BoxIsinstUnboxAny();
|
||||
|
||||
// Exec.BoxIsinstBranchVarious();
|
||||
|
||||
Assert.True(Exec.BoxIsinstBranch<int, object>(default));
|
||||
Assert.False(Exec.BoxIsinstBranch<int, I1>(default));
|
||||
Assert.False(Exec.BoxIsinstBranch<object, I1>(default));
|
||||
|
||||
Assert.True(Exec.BoxIsinstBranch<S, object>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S<int>, object>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S<object>, object>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S, S>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S<int>, S<int>>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S<object>, S<object>>(default));
|
||||
Assert.False(Exec.BoxIsinstBranch<S, I1>(default));
|
||||
Assert.False(Exec.BoxIsinstBranch<S<int>, I1>(default));
|
||||
Assert.False(Exec.BoxIsinstBranch<S<object>, I1>(default));
|
||||
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1, object>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1<int>, object>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1<object>, object>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1, S_I1>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1<int>, S_I1<int>>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1<object>, S_I1<object>>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1, I1>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1<int>, I1>(default));
|
||||
Assert.True(Exec.BoxIsinstBranch<S_I1<object>, I1>(default));
|
||||
|
||||
Assert.Equal($"{nameof(Validate)}+{nameof(S)}", Exec.ConstrainedCallVirtToString<S>(new S()));
|
||||
Assert.Equal(0, Exec.ConstrainedCallVirtMethod<S_DI1>(new S_DI1()));
|
||||
Assert.Equal(1, Exec.ConstrainedCallVirtMethod<S_DI2>(new S_DI2()));
|
||||
Assert.Equal(1, Exec.ConstrainedCallVirtMethod<RS_DI2>(new RS_DI2()));
|
||||
|
||||
Assert.Equal(-1, Exec.ConstrainedCallVirtMethod<S_DI1>(new S_DI1(), skipCall: true));
|
||||
Assert.Equal(-1, Exec.ConstrainedCallVirtMethod<S_DI2>(new S_DI2(), skipCall: true));
|
||||
Assert.Equal(-1, Exec.ConstrainedCallVirtMethod<RS_DI2>(new RS_DI2(), skipCall: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -85,12 +137,18 @@ public class Validate
|
|||
// These methods uses opcodes that are not able to handle ByRefLike type operands.
|
||||
// The TypeLoader prevents these invalid types from being constructed. We rely on
|
||||
// the failure to construct these invalid types to block opcode usage.
|
||||
Assert.Throws<TypeLoadException>(() => { Exec.AllocArrayOfT_Invalid(); });
|
||||
Assert.Throws<TypeLoadException>(() => { Exec.AllocMultiDimArrayOfT_Invalid(); });
|
||||
Assert.Throws<TypeLoadException>(() => { Exec.AllocArray<RS>(); });
|
||||
Assert.Throws<TypeLoadException>(() => { Exec.AllocMultiDimArray<RS>(); });
|
||||
Assert.Throws<TypeLoadException>(() => { Exec.GenericClassWithStaticField_Invalid(); });
|
||||
|
||||
// Test that explicitly tries to box a ByRefLike type.
|
||||
Assert.Throws<InvalidProgramException>(() => { Exec.BoxAsObject(); });
|
||||
Assert.Throws<InvalidProgramException>(() => { Exec.BoxAsObject<RS>(new RS()); });
|
||||
|
||||
// Test that implicitly tries to box a ByRefLike type.
|
||||
// Assert.Throws<InvalidProgramException>(() => { Exec.ConstrainedCallVirtToString<RS>(new RS()); });
|
||||
// Assert.Throws<InvalidProgramException>(() => { Exec.ConstrainedCallVirtMethod<RS_DI1>(new RS_DI1()); });
|
||||
// Assert.Throws<InvalidProgramException>(() => { Exec.ConstrainedCallVirtMethod<RS_DI1>(new RS_DI1(), skipCall: false); });
|
||||
// Assert.Throws<InvalidProgramException>(() => { Exec.ConstrainedCallVirtMethod<RS_DI1>(new RS_DI1(), skipCall: true); });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue