Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ This document describes the concept of a version bubble, how to specify them to
## Behavior of code that shares a version bubble

1. Inlining is only permitted within a version bubble unless the method is marked with `System.Runtime.Versioning.NonVersionableAttribute`. If the inlinee is marked as NonVersionable, it may ALWAYS be inlined into the method being compiled.
2. Generic instantiations may only be ahead of time compiled into the application if the definition of the generic is defined within the version bubble as well as the instantiation arguments to the generic. As an exception to the requirement for the instantiation arguments to be defined within the bubble, the shared generic type `System.__Canon` will always be considered to be part of the version bubble. Also, the list of very well known types (object, string, int, uint, short, ushort, byte, sbyte, long, ulong, float, double, IntPtr, and UIntPtr) are also considered to be part of the version bubble as long as the generic is not constrained on an interface or class. For Example
2. Generic instantiations may only be ahead of time compiled into the application if the definition of the generic is defined within the version bubble as well as the instantiation arguments to the generic. As an exception to the requirement for the instantiation arguments to be defined within the bubble, the shared generic type `System.__Canon` will always be considered to be part of the version bubble. Also, the list of very well known types (object, string, int, uint, short, ushort, byte, sbyte, long, ulong, float, double, IntPtr, and UIntPtr) are also considered to be part of the version bubble as long as the generic is not constrained on an interface or class. The `struct` and `unmanaged` constraints, which C# encodes as a `System.ValueType` type constraint, do not count as class constraints for primitive instantiations. For Example

```csharp
class MyGeneric<T> {}
class ConstrainedGeneric<T> where T : IEquatable<T> {}
class StructGeneric<T> where T : struct {}
...
//MyGeneric<int> would be always be in the version bubble where MyGeneric was defined.
//ConstrainedGeneric<int> would only be in the version bubble of ConstrainedGeneric if ConstrainedGeneric shared a version bubble with System.Private.CoreLib.
//StructGeneric<int> would always be in the version bubble where StructGeneric was defined.
//MyGeneric<DateTime> would would only be in the version bubble of MyGeneric if MyGeneric shared a version bubble with System.Private.CoreLib.
//ConstrainedGeneric<DateTime> would would only be in the version bubble of ConstrainedGeneric if ConstrainedGeneric shared a version bubble with System.Private.CoreLib.
```
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

public struct StructConstrainedBox<T> where T : struct
{
private T _value;

public StructConstrainedBox(T value) => _value = value;

[MethodImpl(MethodImplOptions.NoInlining)]
public int GetValueHashCode() => _value.GetHashCode();
}

public static class StructConstrainedGenerics
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static int StructConstrained<T>(T value) where T : struct => value.GetHashCode();

[MethodImpl(MethodImplOptions.NoInlining)]
public static int UnmanagedConstrained<T>(T value) where T : unmanaged => value.GetHashCode();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineableStructConstrained<T>(T value) where T : struct => value is char ? 1 : 2;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineableUnmanagedConstrained<T>(T value) where T : unmanaged => value is char ? 1 : 2;

[MethodImpl(MethodImplOptions.NoInlining)]
public static int CallStructConstrained(int value) => StructConstrained(value);

[MethodImpl(MethodImplOptions.NoInlining)]
public static int CallUnmanagedConstrained(int value) => UnmanagedConstrained(value);

[MethodImpl(MethodImplOptions.NoInlining)]
public static int CallStructConstrainedBox(int value) => new StructConstrainedBox<int>(value).GetValueHashCode();

[MethodImpl(MethodImplOptions.NoInlining)]
public static int CallInlineableStructConstrained(char value) => InlineableStructConstrained(value);

[MethodImpl(MethodImplOptions.NoInlining)]
public static int CallInlineableUnmanagedConstrained(char value) => InlineableUnmanagedConstrained(value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,37 @@ static void Validate(ReadyToRunReader reader)
}
}

[Fact]
public void StructConstrainedPrimitiveInstantiations()
{
var structConstrainedGenerics = new CompiledAssembly
{
AssemblyName = nameof(StructConstrainedPrimitiveInstantiations),
SourceResourceNames = ["PrimitiveInstantiations/StructConstrainedGenerics.cs"],
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(StructConstrainedPrimitiveInstantiations),
[
new(nameof(StructConstrainedPrimitiveInstantiations), [new CrossgenAssembly(structConstrainedGenerics)])
{
Options = [Crossgen2Option.Optimize],
Validate = Validate,
},
]));

static void Validate(ReadyToRunReader reader)
{
string diag;

Assert.True(R2RAssert.HasCompiledMethod(reader, "StructConstrainedGenerics", "StructConstrained", out diag, ["int"]), diag);
Assert.True(R2RAssert.HasCompiledMethod(reader, "StructConstrainedGenerics", "UnmanagedConstrained", out diag, ["int"]), diag);
Assert.True(R2RAssert.HasCompiledMethod(reader, "StructConstrainedBox`1<int>", "GetValueHashCode", out diag), diag);
Assert.True(R2RAssert.HasInlinedMethod(reader, "CallInlineableStructConstrained", "InlineableStructConstrained", out diag), diag);
Assert.True(R2RAssert.HasInlinedMethod(reader, "CallInlineableUnmanagedConstrained", "InlineableUnmanagedConstrained", out diag), diag);
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,15 @@ private static bool ComputeInstantiationVersionsWithCode(Instantiation inst, Typ
{
if (genericParam.HasReferenceTypeConstraint)
return false;

// C# encodes the 'struct' and 'unmanaged' constraints as a System.ValueType type constraint.
// Every primitive satisfies it, and it exposes nothing beyond what an unconstrained
// type parameter already can, so only other (interface or class) constraints disqualify.
foreach (TypeDesc constraint in genericParam.TypeConstraints)
{
if (!constraint.IsWellKnownType(WellKnownType.ValueType))
return false;
}
}
else
{
Expand All @@ -955,11 +964,11 @@ private static bool ComputeInstantiationVersionsWithCode(Instantiation inst, Typ

if (instType.IsString && genericParam.HasDefaultConstructorConstraint)
return false;
}

// This checks to see if the type constraints list is empty
if (genericParam.TypeConstraints.GetEnumerator().MoveNext())
return false;
// This checks to see if the type constraints list is empty
if (genericParam.TypeConstraints.GetEnumerator().MoveNext())
return false;
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,21 @@ private string ResolveLocalMethod(uint rid, uint moduleIndex)
{
_localMethodMap ??= BuildLocalMethodMap();

return _localMethodMap.TryGetValue((moduleIndex, rid), out string name)
? name
: $"<MethodDef 0x{RidToMethodDef((int)rid):X8}>";
if (_localMethodMap.TryGetValue((moduleIndex, rid), out string name))
return name;

// Inlinees that have no compiled body of their own (e.g. AggressiveInlining helpers) are not in the
// map; name them from the image's own metadata when it isn't a composite.
if (!_r2r.Composite && moduleIndex == 0 &&
_r2r.GetGlobalMetadata()?.MetadataReader is MetadataReader mdReader &&
rid <= (uint)mdReader.GetTableRowCount(TableIndex.MethodDef))
{
MethodDefinition methodDef = mdReader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)rid));
TypeDefinition typeDef = mdReader.GetTypeDefinition(methodDef.GetDeclaringType());
return $"{mdReader.GetString(typeDef.Name)}.{mdReader.GetString(methodDef.Name)}";
}

return $"<MethodDef 0x{RidToMethodDef((int)rid):X8}>";
}

private ReadyToRunImportSection FindILBodyImportSection()
Expand Down
Loading