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
4 changes: 4 additions & 0 deletions docs/design/features/typemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ An entry in an External Type Map is included when the "trim target" type is refe

Many of these instructions can be passed a generic parameter. In that case, the trimming tool should consider type arguments of instantiations of that type as having met one of these rules and include any entries with those types as "trim target" types.

For pointer, byref, and array types as trim targets, the entries are preserved if the element type meets the requirements above to keep the entry. A trimming tool is free to remove entries if it can prove that the pointer, byref, or array type cannot be represented at runtime.

### Proxy Type Map

An entry in the Proxy Type Map is included when the "source type" is referenced in one of the following ways:
Expand All @@ -220,3 +222,5 @@ If the type is an interface type and the user could possibly see a `RuntimeTypeH
- The owning type of the method argument to `callvirt`, or `ldvirtftn`.

Finally, if the trimming tool determines that it is impossible to retrieve a `System.Type` instance the represents the "source type" at runtime, then the entry may be omitted from the Proxy Type Map as its existence is unobservable.

For pointer, byref, and array types as source types, the entries are preserved if the element type meets the requirements above to keep the entry. A trimming tool is free to remove entries if it can prove that the pointer, byref, or array type cannot be represented at runtime.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,32 @@ public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDep
context.NativeLayout.TemplateTypeLayout(canonTrimmingType),
"External type map trim target that could be loaded at runtime");
}
else if (effectiveTrimTargetType is ArrayType arrayType)
{
// Some arrays don't have array templates (e.g. multidimensional arrays, arrays of pointers).
// If the element type is template-loadable, the runtime can still construct the array type.
TypeDesc effectiveElementType = GetEffectiveTrimTargetType(arrayType.ElementType);
TypeDesc canonElementType = effectiveElementType.ConvertToCanonForm(CanonicalFormKind.Specific);
if (canonElementType != effectiveElementType && GenericTypesTemplateMap.IsEligibleToHaveATemplate(canonElementType))
{
yield return new CombinedDependencyListEntry(
context.NecessaryTypeSymbol(effectiveTrimTargetType),
context.NativeLayout.TemplateTypeLayout(canonElementType),
"External type map array trim target with template-loadable element type");
}

Comment thread
MichalStrehovsky marked this conversation as resolved.
// Array types that aren't eligible for templates (MdArrays, pointer/fnptr-element SzArrays)
// can be constructed at runtime from just the element type's MethodTable using hardcoded
// templates (typeof(object[,]) for MdArrays, typeof(char*[]) for pointer arrays).
// If the element type is reachable, consider the array type reachable as well.
if (!GenericTypesTemplateMap.IsArrayTypeEligibleForTemplate(arrayType))
{
yield return new CombinedDependencyListEntry(
context.NecessaryTypeSymbol(effectiveTrimTargetType),
context.NecessaryTypeSymbol(effectiveElementType),
"Array without template can be constructed at runtime from element type");
}
}
}
}
}
Expand Down Expand Up @@ -100,12 +126,12 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer
}
}

// Strip parameterized type wrappers (arrays, pointers, byrefs) to get the effective
// type for trimming purposes. If the trim target is Foo[], the TypeMap entry should be
// included when Foo is reachable, matching ILLink's TypeMapHandler stripping behavior.
// Strip non-array parameterized wrappers (pointers, byrefs) to get the effective
// trimming target type. Arrays are preserved so trim dependencies can be conditioned
// on array existence rather than just element type reachability.
private static TypeDesc GetEffectiveTrimTargetType(TypeDesc trimmingTargetType)
{
while (trimmingTargetType is ParameterizedType parameterized)
while (trimmingTargetType is ParameterizedType parameterized && !trimmingTargetType.IsArray)
trimmingTargetType = parameterized.ParameterType;
return trimmingTargetType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
[assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("E", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target5), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget5[]))]
[assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("F", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target6), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget6[]))]
[assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("G", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target7), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget7[]))]
[assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("H", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target8), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget8[]))]
[assembly: TypeMap<DeadCodeElimination.TestInteropMapArrayTrimming>("I", typeof(DeadCodeElimination.TestInteropMapArrayTrimming.Target9), typeof(DeadCodeElimination.TestInteropMapArrayTrimming.TrimTarget9[,]))]
Comment thread
MichalStrehovsky marked this conversation as resolved.

class DeadCodeElimination
{
Expand Down Expand Up @@ -1387,13 +1389,17 @@ public struct TrimTarget4<T>;
public struct TrimTarget5;
public struct TrimTarget6;
public struct TrimTarget7;
public struct TrimTarget8;
public class TrimTarget9;
public class Target1;
public class Target2;
public class Target3;
public class Target4;
public class Target5;
public class Target6;
public class Target7;
public class Target8;
public class Target9;
public class Atom;

public static unsafe object[] MakeGenerics<T>()
Expand All @@ -1414,18 +1420,36 @@ public static unsafe object[] MakeGenerics<T>()
[MethodImpl(MethodImplOptions.NoInlining)]
static object GetUnknown() => null;

[MethodImpl(MethodImplOptions.NoInlining)]
public static object MakeSharedGeneric<T>()
=> new T[1,1];

[MethodImpl(MethodImplOptions.NoInlining)]
public static bool TestSharedGeneric<T>()
=> MakeSharedGeneric<T>() is T[,];

[MethodImpl(MethodImplOptions.NoInlining)]
static Type GetTrimTarget9() => typeof(TrimTarget9);

public static void Run()
{
if (GetUnknown() is TrimTarget7[])
{
Console.WriteLine("Unexpected");
}
if (GetUnknown() is TrimTarget8)
{
Console.WriteLine("Unexpected");
}

typeof(TestInteropMapArrayTrimming).GetMethod(nameof(MakeGenerics)).MakeGenericMethod([GetAtom()]).Invoke(null, []);

typeof(TestInteropMapArrayTrimming).GetMethod(nameof(TestSharedGeneric)).MakeGenericMethod([GetTrimTarget9()]).Invoke(null, []);

var map = TypeMapping.GetOrCreateExternalTypeMapping<TestInteropMapArrayTrimming>();

// A, B, C, F, G: trim target element type is reachable — entries must be present
// I: trim target can be constructed at runtime using type loader
Comment thread
MichalStrehovsky marked this conversation as resolved.
if (!map.TryGetValue("A", out Type typeA) || typeA.Name != nameof(Target1))
Comment thread
MichalStrehovsky marked this conversation as resolved.
throw new Exception("Expected entry A");
ThrowIfUsableMethodTable(typeA);
Expand All @@ -1446,11 +1470,17 @@ public static void Run()
throw new Exception("Expected entry G");
ThrowIfUsableMethodTable(typeG);

// D, E: element type is unreachable — entries must be absent
if (!map.TryGetValue("I", out Type typeI) || typeI.Name != nameof(Target9))
throw new Exception("Expected entry I");
ThrowIfUsableMethodTable(typeI);

// D, E: trim target element type is unreachable. H: element is reachable, but TrimTarget8[] is unreachable.
if (map.TryGetValue("D", out _))
throw new Exception("Unexpected entry D");
if (map.TryGetValue("E", out _))
throw new Exception("Unexpected entry E");
if (map.TryGetValue("H", out _))
throw new Exception("Unexpected entry H");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ static void ConstrainedStaticCall<T>(T t) where T : IStaticInterface

Console.WriteLine(new ArrayElement[1]);

Console.WriteLine(new ArrayTypeTrimTargetClass());
Console.WriteLine(new ArrayTypeTrimTargetClass[1] { new ArrayTypeTrimTargetClass() });

Console.WriteLine(new ConstructedNoTypeCheckNoBoxStruct(42).Value);

Expand Down
Loading