Skip to content

Commit

Permalink
Initial work for IL trimming
Browse files Browse the repository at this point in the history
  • Loading branch information
sakno committed Jan 28, 2024
1 parent eb50f25 commit 9916610
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<IsTrimmable>false</IsTrimmable>
<IsTrimmable>true</IsTrimmable>
<Features>nullablePublicOnly</Features>
<VersionPrefix>5.0.1</VersionPrefix>
<VersionPrefix>5.0.2</VersionPrefix>
<VersionSuffix></VersionSuffix>
<Authors>.NET Foundation</Authors>
<Product>.NEXT Family of Libraries</Product>
Expand Down
27 changes: 20 additions & 7 deletions src/DotNext.Metaprogramming/Linq/Expressions/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ public static BinaryExpression NotEqual(this Expression left, Expression right)
/// </remarks>
/// <param name="operand">The operand.</param>
/// <returns><see langword="null"/> check operation.</returns>
[RequiresUnreferencedCode("Dynamic access to properties of <operand>.")]
public static Expression IsNull(this Expression operand)
{
// handle nullable value type
Expand All @@ -277,6 +278,7 @@ public static Expression IsNull(this Expression operand)
/// </remarks>
/// <param name="operand">The operand.</param>
/// <returns><see langword="null"/> check operation.</returns>
[RequiresUnreferencedCode("Dynamic access to properties of <operand>.")]
public static Expression IsNotNull(this Expression operand)
{
// handle nullable value type
Expand Down Expand Up @@ -723,6 +725,7 @@ public static MethodCallExpression Call(this Expression instance, MethodInfo met
/// <param name="methodName">The name of the method to be called.</param>
/// <param name="arguments">The method arguments.</param>
/// <returns>The method call expression.</returns>
[RequiresUnreferencedCode("Dynamic access to the method identified by <methodName> parameter.")]
public static MethodCallExpression Call(this Expression instance, string methodName, params Expression[] arguments)
=> instance.Call(instance.Type, methodName, arguments);

Expand All @@ -739,7 +742,7 @@ public static MethodCallExpression Call(this Expression instance, string methodN
/// <param name="methodName">The name of the method in the interface or base class to be called.</param>
/// <param name="arguments">The method arguments.</param>
/// <returns>The method call expression.</returns>
public static MethodCallExpression Call(this Expression instance, Type interfaceType, string methodName, params Expression[] arguments)
public static MethodCallExpression Call(this Expression instance, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type interfaceType, string methodName, params Expression[] arguments)
{
if (!interfaceType.IsAssignableFrom(instance.Type))
throw new ArgumentException(ExceptionMessages.InterfaceNotImplemented(instance.Type, interfaceType));
Expand All @@ -756,7 +759,7 @@ public static MethodCallExpression Call(this Expression instance, Type interface
/// <param name="methodName">The name of the static method.</param>
/// <param name="arguments">The arguments to be passed into static method.</param>
/// <returns>An expression representing static method call.</returns>
public static MethodCallExpression CallStatic(this Type type, string methodName, params Expression[] arguments)
public static MethodCallExpression CallStatic([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] this Type type, string methodName, params Expression[] arguments)
{
return type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly, null, Array.ConvertAll(arguments, GetType), null) is { } method
? Expression.Call(method, arguments)
Expand Down Expand Up @@ -799,7 +802,7 @@ public static IndexExpression Property(this Expression instance, PropertyInfo pr
/// <param name="interfaceType">The interface or base class declaring property.</param>
/// <param name="propertyName">The name of the instance property.</param>
/// <returns>Property access expression.</returns>
public static MemberExpression Property(this Expression instance, Type interfaceType, string propertyName)
public static MemberExpression Property(this Expression instance, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type interfaceType, string propertyName)
{
return interfaceType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance) is { } property
? Property(instance, property)
Expand All @@ -818,7 +821,7 @@ public static MemberExpression Property(this Expression instance, Type interface
/// <param name="index0">The first index.</param>
/// <param name="indicies">The rest of the indexer arguments.</param>
/// <returns>Property access expression.</returns>
public static IndexExpression Property(this Expression instance, Type interfaceType, string propertyName, Expression index0, params Expression[] indicies)
public static IndexExpression Property(this Expression instance, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type interfaceType, string propertyName, Expression index0, params Expression[] indicies)
{
return interfaceType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance) is { } property
? Property(instance, property, index0, indicies)
Expand All @@ -834,6 +837,7 @@ public static IndexExpression Property(this Expression instance, Type interfaceT
/// <param name="instance"><c>this</c> argument.</param>
/// <param name="propertyName">The name of the instance property.</param>
/// <returns>Property access expression.</returns>
[RequiresUnreferencedCode("Dynamic access to the property identified by <propertyName> parameter.")]
public static MemberExpression Property(this Expression instance, string propertyName)
=> Expression.Property(instance, propertyName);

Expand All @@ -848,6 +852,7 @@ public static MemberExpression Property(this Expression instance, string propert
/// <param name="index0">The first index.</param>
/// <param name="indicies">The rest of the indexer arguments.</param>
/// <returns>Property access expression.</returns>
[RequiresUnreferencedCode("Dynamic access to the indexer identified by <propertyName> parameter.")]
public static IndexExpression Property(this Expression instance, string propertyName, Expression index0, params Expression[] indicies)
=> Expression.Property(instance, propertyName, [index0, .. indicies]);

Expand All @@ -872,6 +877,7 @@ public static MemberExpression Field(this Expression instance, FieldInfo field)
/// <param name="instance"><c>this</c> argument.</param>
/// <param name="fieldName">The name of the instance field.</param>
/// <returns>Field access expression.</returns>
[RequiresUnreferencedCode("Dynamic access to the field identified by <fieldName> parameter.")]
public static MemberExpression Field(this Expression instance, string fieldName)
=> Expression.Field(instance, fieldName);

Expand Down Expand Up @@ -937,10 +943,12 @@ public static UnaryExpression ArrayLength(this Expression array)
/// </remarks>
/// <param name="collection">The expression representing collection.</param>
/// <returns>The expression providing access to the appropriate property indicating the number of items in the collection.</returns>
[RequiresUnreferencedCode("Dynamic access to implemented interfaces and public properties of <collection>.")]
public static MemberExpression Count(this Expression collection)
{
if (collection.Type == typeof(string) || collection.Type == typeof(StringBuilder))
return Expression.Property(collection, nameof(string.Length));

var interfaceType = collection.Type.GetImplementedCollection() ?? throw new ArgumentException(ExceptionMessages.CollectionImplementationExpected);
return Expression.Property(collection, interfaceType, nameof(Count));
}
Expand All @@ -950,6 +958,7 @@ public static MemberExpression Count(this Expression collection)
/// </summary>
/// <param name="obj">The object to be converted into string.</param>
/// <returns>The expression representing <c>ToString()</c> method call.</returns>
[RequiresUnreferencedCode("Dynamic access to public methods of <obj>.")]
public static MethodCallExpression AsString(this Expression obj) => Call(obj, nameof(ToString));

/// <summary>
Expand Down Expand Up @@ -1136,12 +1145,12 @@ public static ConditionalExpression Condition<TResult>(this Expression test, Exp
/// <param name="type">The type to be instantiated.</param>
/// <param name="args">The list of arguments to be passed into constructor.</param>
/// <returns>Instantiation expression.</returns>
public static NewExpression New(this Type type, params Expression[] args)
public static NewExpression New([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type, params Expression[] args)
{
if (args.LongLength is 0L)
return Expression.New(type);

return type.GetConstructor(Array.ConvertAll(args, static arg => arg.Type)) is { } ctor
return type.GetConstructor(Array.ConvertAll(args, GetType)) is { } ctor
? Expression.New(ctor, args)
: throw new MissingMethodException(type.FullName, ConstructorInfo.ConstructorName);
}
Expand All @@ -1155,6 +1164,7 @@ public static NewExpression New(this Type type, params Expression[] args)
/// <param name="expression">An expression representing object construction.</param>
/// <param name="bindings">A collection of members to initialize.</param>
/// <returns>Initialization expression.</returns>
[RequiresUnreferencedCode("Dynamic access to public properties of <expression>.")]
public static MemberInitExpression Init(this NewExpression expression, MemberBindings bindings)
=> Expression.MemberInit(expression, bindings.Bind(expression.Type));

Expand Down Expand Up @@ -1203,7 +1213,7 @@ internal static Expression AddEpilogue(this Expression expression, bool inferTyp
/// <param name="type">The expression representing the type to be instantiated.</param>
/// <param name="args">The list of arguments to be passed into constructor.</param>
/// <returns>Instantiation expression.</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(Activator))]
[RequiresUnreferencedCode("Dynamic access to public constructors of a type represented by <type>.")]
public static MethodCallExpression New(this Expression type, params Expression[] args)
{
var activate = typeof(Activator).GetMethod(nameof(Activator.CreateInstance), [typeof(Type), typeof(object[])]);
Expand All @@ -1217,6 +1227,7 @@ public static MethodCallExpression New(this Expression type, params Expression[]
/// <param name="collection">The collection to iterate through.</param>
/// <param name="body">A delegate that is used to construct the body of the loop.</param>
/// <returns>The constructed loop.</returns>
[RequiresUnreferencedCode("Dynamic access to GetEnumerator method and IEnumerable<T> interfaces.")]
public static ForEachExpression ForEach(this Expression collection, ForEachExpression.Statement body)
=> ForEachExpression.Create(collection, body);

Expand Down Expand Up @@ -1453,6 +1464,7 @@ public static Expression AsOptional(this Expression expression)
/// </summary>
/// <param name="expression">The compound expression.</param>
/// <returns>The expression of type <see cref="Result{T}"/>.</returns>
[RequiresUnreferencedCode("Dynamic access to DotNext.Result<> data type")]
public static Expression AsResult(this Expression expression)
{
var exception = Expression.Parameter(typeof(Exception));
Expand All @@ -1465,6 +1477,7 @@ public static Expression AsResult(this Expression expression)
Expression.Catch(exception, Expression.New(fallbackCtor, exception)));
}

[RequiresUnreferencedCode("Dynamic access to indexer of <target>.")]
internal static IndexExpression MakeIndex(Expression target, Expression[] args)
{
// handle case for array
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;

Expand All @@ -13,6 +14,7 @@ namespace DotNext.Linq.Expressions;
/// Represents iteration over collection elements as expression.
/// </summary>
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/foreach-in">foreach Statement</seealso>
[RequiresUnreferencedCode("Dynamic access to GetEnumerator method and IEnumerable<T> interfaces.")]
public sealed class ForEachExpression : CustomExpression, ILoopLabels
{
private const string EnumeratorVarName = "enumerator";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using Debug = System.Diagnostics.Debug;
Expand Down Expand Up @@ -64,6 +65,8 @@ public ItemIndexExpression(Expression value, bool fromEnd = false)
/// </summary>
public override Type Type => typeof(Index);

[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(Index))]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Dependency of Index is declared explicitly")]
internal static Expression GetOffset(Expression index, Expression count)
=> index is ItemIndexExpression { IsFromEnd: false } itemIndex ? itemIndex.Value : Call(index, nameof(Index.GetOffset), null, count);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;

Expand All @@ -21,7 +22,7 @@ public MemberBindings()
/// </summary>
/// <param name="target">The target type with the declared members.</param>
/// <returns>A list of bindings.</returns>
public IReadOnlyList<MemberAssignment> Bind(Type target)
public IReadOnlyList<MemberAssignment> Bind([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target)
{
const MemberTypes memberTypes = MemberTypes.Field | MemberTypes.Property;
const BindingFlags memberFlags = BindingFlags.Public | BindingFlags.Instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace DotNext.Runtime.CompilerServices;
/// 2. Construct state holder type
/// 3. Replace all local variables with fields from state holder type.
/// </remarks>
[RequiresUnreferencedCode("Dynamic access to DotNext.Metaprogramming internal types, Task<T> and ValueTask<T>.")]
internal sealed class AsyncStateMachineBuilder : ExpressionVisitor, IDisposable
{
private static readonly UserDataSlot<int> ParameterPositionSlot = new();
Expand Down Expand Up @@ -57,7 +58,7 @@ internal AsyncStateMachineBuilder(Type taskType, IReadOnlyList<ParameterExpressi
}

context = new VisitorContext(out AsyncMethodEnd);
stateSwitchTable = new StateTransitionTable();
stateSwitchTable = new();
}

private static void MarkAsParameter(ParameterExpression parameter, int position)
Expand Down Expand Up @@ -432,7 +433,8 @@ public void Dispose()
}
}

internal sealed class AsyncStateMachineBuilder<TDelegate> : ExpressionVisitor, IDisposable
[RequiresUnreferencedCode("Dynamic access to Transition and IAsyncStateMachine internal types.")]
internal sealed class AsyncStateMachineBuilder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TDelegate> : ExpressionVisitor, IDisposable
where TDelegate : Delegate
{
private readonly AsyncStateMachineBuilder methodBuilder;
Expand Down Expand Up @@ -509,10 +511,7 @@ internal StateMachineBuilder(Type returnType, bool usePooling)
this.usePooling = usePooling;
}

[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AsyncStateMachine<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AsyncStateMachine<,>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PoolingAsyncStateMachine<>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PoolingAsyncStateMachine<,>))]
[RequiresUnreferencedCode("Dynamic access to AsyncStateMachine and PoolingAsyncStateMachine internal types.")]
internal MemberExpression Build(Type stateType)
{
Type stateMachineType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace DotNext.Runtime.CompilerServices;

[RequiresUnreferencedCode("Dynamic access to StrongBox<T> type.")]
internal sealed class ClosureAnalyzer : ExpressionVisitor
{
private static readonly UserDataSlot<bool> ClosureVariableSlot = new();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -40,6 +41,8 @@ internal Segment(Type argumentType, string? format, int alignment)
this.argumentType = argumentType;
}

[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BufferWriterSlimInterpolatedStringHandler))]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "DynamicDependencyAttribute is applied")]
internal void WriteStatement(IList<Expression> statements, ParameterExpression provider, ParameterExpression handler, out ParameterExpression? inputVar)
{
Debug.Assert(provider.Type == typeof(IFormatProvider));
Expand Down Expand Up @@ -145,11 +148,19 @@ public void AppendFormatted(Type type, string? format = null)
/// the renderer of the interpolated string.
/// </summary>
/// <returns>The lambda expression that encapsulates the rendering logic.</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PreallocatedCharBuffer))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BufferWriterSlim<char>))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BufferWriterSlimInterpolatedStringHandler))]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "DynamicDependencyAttribute is applied")]
public readonly LambdaExpression Build()
{
var preallocatedBufferLocal = Expression.Variable(typeof(PreallocatedCharBuffer), "buffer");
var writerLocal = Expression.Variable(typeof(BufferWriterSlim<char>), "writer");
var handlerLocal = Expression.Variable(typeof(BufferWriterSlimInterpolatedStringHandler), "handler");

var bufferWriterSlimType = typeof(BufferWriterSlim<char>);
var writerLocal = Expression.Variable(bufferWriterSlimType, "writer");

var stringHandlerType = typeof(BufferWriterSlimInterpolatedStringHandler);
var handlerLocal = Expression.Variable(stringHandlerType, "handler");
var providerParameter = Expression.Parameter(typeof(IFormatProvider), "provider");
var allocatorParameter = Expression.Parameter(typeof(MemoryAllocator<char>), "allocator");

Expand All @@ -161,7 +172,7 @@ public readonly LambdaExpression Build()
var statements = new List<Expression>();

// instantiate buffer writer
var ctor = writerLocal.Type.GetConstructor([typeof(Span<char>), allocatorParameter.Type]);
var ctor = bufferWriterSlimType.GetConstructor([typeof(Span<char>), allocatorParameter.Type]);
Debug.Assert(ctor is not null);
Expression expr = Expression.New(
ctor,
Expand All @@ -170,7 +181,7 @@ public readonly LambdaExpression Build()
statements.Add(Expression.Assign(writerLocal, expr));

// instantiate handler
ctor = handlerLocal.Type.GetConstructor([typeof(int), typeof(int), writerLocal.Type.MakeByRefType(), providerParameter.Type]);
ctor = stringHandlerType.GetConstructor([typeof(int), typeof(int), writerLocal.Type.MakeByRefType(), providerParameter.Type]);
Debug.Assert(ctor is not null);
expr = Expression.New(
ctor,
Expand Down

0 comments on commit 9916610

Please sign in to comment.