Skip to content

Commit

Permalink
cache 'GenericInstanceMethod' instantiations based on type arguments …
Browse files Browse the repository at this point in the history
…to avoid noise in the generated code (#266)
  • Loading branch information
adrianoc committed Mar 31, 2024
1 parent 1c9f831 commit 0d511d4
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 69 deletions.
32 changes: 32 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.RegularExpressions;
using NUnit.Framework;

namespace Cecilifier.Core.Tests.Tests.Unit
Expand Down Expand Up @@ -399,5 +400,36 @@ void Test<T, TU>(T value, IDisposable paramIDisp, object paramObj, TU ptu) where

Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expectedILSnippet));
}

[Test]
public void GenericInstanceMethods_AreCached()
{
var result = RunCecilifier("""
using System;

var sa1 = M<string>();
var sa2 = M<string>();
var ia1 = M<int>();

T M<T>() => default(T);
""");

var cecilifiedCode = result.GeneratedCode.ReadToEnd();

Assert.Multiple(() =>
{
var stringVersionCount = Regex.Matches(cecilifiedCode, """
\s+var gi_M_\d+ = new GenericInstanceMethod\(m_M_7\);
\s+gi_M_\d+.GenericArguments.Add\(assembly.MainModule.TypeSystem.String\);
""");
Assert.That(stringVersionCount.Count, Is.EqualTo(1), cecilifiedCode);
var intVersionCount = Regex.Matches(cecilifiedCode, """
\s+var gi_M_\d+ = new GenericInstanceMethod\(m_M_7\);
\s+gi_M_\d+.GenericArguments.Add\(assembly.MainModule.TypeSystem.Int32\);
""");
Assert.That(intVersionCount.Count, Is.EqualTo(1), cecilifiedCode);
});
}
}
}
7 changes: 3 additions & 4 deletions Cecilifier.Core/AST/EventDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private string AddAccessor(EventFieldDeclarationSyntax node, IEventSymbol eventS
IEnumerable<string> methodBodyExpressions = Array.Empty<string>();
if (!isInterfaceDef)
{
var localVarsExps = CreateLocalVarsForAddMethod(methodVar, backingFieldVar);
var localVarsExps = CreateLocalVarsForEventRegistrationMethods(methodVar, backingFieldVar);
methodBodyExpressions = methodBodyFactory(node, eventSymbol, backingFieldVar, methodVar)
.Concat(localVarsExps)
.Append($"{methodVar}.Body.InitLocals = true;");
Expand Down Expand Up @@ -133,7 +133,7 @@ private static string AccessModifiersForEventAccessors(MemberDeclarationSyntax n
return node.Modifiers.ModifiersForSyntheticMethod("MethodAttributes.SpecialName", typeSymbol);
}

private IEnumerable<string> CreateLocalVarsForAddMethod(string methodVar, string backingFieldVar)
private IEnumerable<string> CreateLocalVarsForEventRegistrationMethods(string methodVar, string backingFieldVar)
{
for (int i = 0; i < 3; i++)
yield return $"{methodVar}.Body.Variables.Add(new VariableDefinition({backingFieldVar}.FieldType));";
Expand Down Expand Up @@ -223,8 +223,7 @@ private IEnumerable<string> CompareExchangeMethodResolvingExps(string backingFie
var openCompExcVar = Context.Naming.MemberReference("openCompExc");
var exp1 = $"var {openCompExcVar} = {Utils.ImportFromMainModule("typeof(System.Threading.Interlocked).GetMethods().Single(m => m.Name == \"CompareExchange\" && m.IsGenericMethodDefinition)")};";

compExcVar = Context.Naming.MemberReference("compExc");
return new[] { exp1 }.Concat(openCompExcVar.MakeGenericInstanceMethod(compExcVar, [$"{backingFieldVar}.FieldType"]));
return new[] { exp1 }.Concat(openCompExcVar.MakeGenericInstanceMethod(Context, "compExp", [$"{backingFieldVar}.FieldType"], out compExcVar));
}

private IEnumerable<string> AddParameterTo(string methodVar, string fieldType)
Expand Down
22 changes: 7 additions & 15 deletions Cecilifier.Core/AST/InlineArrayProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,21 +160,13 @@ static string InlineArrayElementRefMethodFor(IVisitorContext context, ITypeSymbo

private static string PrivateImplementationInlineArrayGenericInstanceMethodFor(IVisitorContext context, string openGenericTypeVar, string methodName, ITypeSymbol inlineArrayType)
{
var varName = context.Naming.SyntheticVariable(methodName, ElementKind.GenericInstance);

var exps = openGenericTypeVar.MakeGenericInstanceMethod(
varName,
[
context.TypeResolver.Resolve(inlineArrayType), // TBuffer
context.TypeResolver.Resolve(InlineArrayElementTypeFrom(inlineArrayType)) // TElement
]);

foreach (var exp in exps)
{
context.WriteCecilExpression(exp);
context.WriteNewLine();
}

var varName = openGenericTypeVar.MakeGenericInstanceMethod(
context,
methodName,
[
context.TypeResolver.Resolve(inlineArrayType), // TBuffer
context.TypeResolver.Resolve(InlineArrayElementTypeFrom(inlineArrayType)) // TElement
]);
return varName;
}

Expand Down
4 changes: 1 addition & 3 deletions Cecilifier.Core/AST/SyntaxWalkerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ protected void AddMethodCall(string ilVar, IMethodSymbol method, bool isAccessOn
// If the generic method is an open one or if it is defined in the same assembly then the call need to happen in the generic instance method (note that for
// methods defined in the snippet being cecilified, even if 'method' represents a generic instance method, MethodResolverExpression() will return the open
// generic one instead).
var genInstVar = Context.Naming.GenericInstance(method);
Context.WriteCecilExpressions(operand.MakeGenericInstanceMethod(genInstVar, method.TypeArguments.Select(t => Context.TypeResolver.Resolve(t))));
operand = genInstVar;
operand = operand.MakeGenericInstanceMethod(Context, method.Name, method.TypeArguments.Select(t => Context.TypeResolver.Resolve(t)).ToArray());
}

if (Context.TryGetFlag(Constants.ContextFlags.MemberReferenceRequiresConstraint, out var constrainedType))
Expand Down
4 changes: 1 addition & 3 deletions Cecilifier.Core/Cecilifier.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using Cecilifier.Core.AST;
using Cecilifier.Core.Extensions;
using Cecilifier.Core.Mappings;
Expand All @@ -17,7 +15,7 @@ public sealed class Cecilifier
{
internal const int CecilifierProgramPreambleLength = 25; // The # of lines before the 1st cecilified line of code (see CecilifierExtensions.AsCecilApplication())

private const LanguageVersion CurrentLanguageVersion = LanguageVersion .CSharp12;
private const LanguageVersion CurrentLanguageVersion = LanguageVersion.CSharp12;
public static readonly int SupportedCSharpVersion = int.Parse(CurrentLanguageVersion.ToString().Substring("CSharp".Length));

public static CecilifierResult Process(Stream content, CecilifierOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,17 @@ internal static string GetOrEmmitInlineArrayAsSpanMethod(IVisitorContext context
return context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemSpan).MakeGenericInstanceType(spanTypeParameter);
});

context.WriteCecilExpressions(methodExpressions);

var tBufferVar = ResolveOwnedGenericParameter(context, "TBuffer", methodTypeQualifiedName);
var tElementVar = ResolveOwnedGenericParameter(context, "TElement", methodTypeQualifiedName);

var unsafeAsVar = context.Naming.SyntheticVariable("unsafeAs", ElementKind.GenericInstance);
var unsafeAsExps = GetUnsafeAsMethod(context).MethodResolverExpression(context).MakeGenericInstanceMethod(unsafeAsVar, [tBufferVar, tElementVar]);

var memoryMarshalCreateSpanVar = context.Naming.SyntheticVariable("createSpan", ElementKind.GenericInstance);
var memoryMarshalCreateSpanExps = GetMemoryMarshalCreateSpanMethod(context).MethodResolverExpression(context).MakeGenericInstanceMethod(memoryMarshalCreateSpanVar, [tElementVar]);
context.WriteComment("Unsafe.As() generic instance method");
var unsafeAsVar = GetUnsafeAsMethod(context).MethodResolverExpression(context).MakeGenericInstanceMethod(context, "unsafeAs", [tBufferVar, tElementVar]);

context.WriteComment("MemoryMarshal.CreateSpan() generic instance method");
var memoryMarshalCreateSpanVar = GetMemoryMarshalCreateSpanMethod(context).MethodResolverExpression(context).MakeGenericInstanceMethod(context, "createSpan", [tElementVar]);

var methodBodyExpressions = CecilDefinitionsFactory.MethodBody(
methodVar,
[
Expand All @@ -91,17 +93,7 @@ internal static string GetOrEmmitInlineArrayAsSpanMethod(IVisitorContext context
OpCodes.Ret
]);

var finalExps = methodExpressions
.Append("")
.Append("// Unsafe.As() generic instance method")
.Concat(unsafeAsExps)
.Append("")
.Append("// MemoryMarshal.CreateSpan() generic instance method")
.Concat(memoryMarshalCreateSpanExps)
.Append("")
.Concat(methodBodyExpressions)
.Append($"{privateImplementationDetailsVar.VariableName}.Methods.Add({methodVar});");

var finalExps = methodBodyExpressions.Append($"{privateImplementationDetailsVar.VariableName}.Methods.Add({methodVar});");
context.WriteCecilExpressions(finalExps);

return methodVar;
Expand Down Expand Up @@ -156,13 +148,14 @@ public static string GetOrEmmitInlineArrayFirstElementRefMethod(IVisitorContext
return spanTypeParameter.MakeByReferenceType();
});

var tbufferTypeParameter = ResolveOwnedGenericParameter(context, "TBuffer", methodTypeQualifiedName);
var telementTypeParameter = ResolveOwnedGenericParameter(context, "TElement", methodTypeQualifiedName);
context.WriteCecilExpressions(methodExpressions);

var tBufferTypeParameter = ResolveOwnedGenericParameter(context, "TBuffer", methodTypeQualifiedName);
var tElementTypeParameter = ResolveOwnedGenericParameter(context, "TElement", methodTypeQualifiedName);

var unsafeAsVarName = context.Naming.SyntheticVariable("unsafeAs", ElementKind.GenericInstance);
var unsafeAsExps = GetUnsafeAsMethod(context)
var unsafeAsVarName = GetUnsafeAsMethod(context)
.MethodResolverExpression(context)
.MakeGenericInstanceMethod(unsafeAsVarName, [tbufferTypeParameter, telementTypeParameter]);
.MakeGenericInstanceMethod(context, "unsafeAs", [tBufferTypeParameter, tElementTypeParameter]);

var methodBodyExpressions = CecilDefinitionsFactory.MethodBody(
methodVar,
Expand All @@ -172,11 +165,7 @@ public static string GetOrEmmitInlineArrayFirstElementRefMethod(IVisitorContext
OpCodes.Ret
]);

foreach (var exp in methodExpressions.Concat(unsafeAsExps).Concat(methodBodyExpressions))
{
context.WriteCecilExpression(exp);
context.WriteNewLine();
}
context.WriteCecilExpressions(methodBodyExpressions);

context.WriteCecilExpression($"{privateImplementationDetailsVar.VariableName}.Methods.Add({methodVar});");
context.WriteNewLine();
Expand Down Expand Up @@ -215,18 +204,18 @@ public static string GetOrEmmitInlineArrayElementRefMethod(IVisitorContext conte
return spanTypeParameter.MakeByReferenceType();
});

context.WriteCecilExpressions(methodExpressions);

var tbufferTypeParameter = ResolveOwnedGenericParameter(context, "TBuffer", methodTypeQualifiedName);
var telementTypeParameter = ResolveOwnedGenericParameter(context, "TElement", methodTypeQualifiedName);

var unsafeAsVarName = context.Naming.SyntheticVariable("unsafeAs", ElementKind.GenericInstance);
var unsafeAsExps = GetUnsafeAsMethod(context)
var unsafeAsVarName = GetUnsafeAsMethod(context)
.MethodResolverExpression(context)
.MakeGenericInstanceMethod(unsafeAsVarName, [tbufferTypeParameter, telementTypeParameter]);
.MakeGenericInstanceMethod(context, "unsafeAs", [tbufferTypeParameter, telementTypeParameter]);

var unsafeAddVarName = context.Naming.SyntheticVariable("unsafeAdd", ElementKind.GenericInstance);
var unsafeAddExps = GetUnsafeAddMethod(context)
var unsafeAddVarName = GetUnsafeAddMethod(context)
.MethodResolverExpression(context)
.MakeGenericInstanceMethod(unsafeAddVarName, [telementTypeParameter]);
.MakeGenericInstanceMethod(context, "unsafeAdd", [telementTypeParameter]);

var methodBodyExpressions = CecilDefinitionsFactory.MethodBody(
methodVar,
Expand All @@ -238,12 +227,7 @@ public static string GetOrEmmitInlineArrayElementRefMethod(IVisitorContext conte
OpCodes.Ret
]);

foreach (var exp in methodExpressions.Concat(unsafeAsExps).Concat(unsafeAddExps).Concat(methodBodyExpressions))
{
context.WriteCecilExpression(exp);
context.WriteNewLine();
}

context.WriteCecilExpressions(methodBodyExpressions);
context.WriteCecilExpression($"{privateImplementationDetailsVar.VariableName}.Methods.Add({methodVar});");
context.WriteNewLine();
context.WriteComment("-------------------------------");
Expand Down
37 changes: 31 additions & 6 deletions Cecilifier.Core/Extensions/MethodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using Cecilifier.Core.AST;
using Cecilifier.Core.Naming;
using Cecilifier.Core.Services;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -214,14 +215,38 @@ public static string ModifiersForSyntheticMethod(this SyntaxTokenList modifiers,

public static bool HasCovariantReturnType(this IMethodSymbol method) => method is { IsOverride: true } && !SymbolEqualityComparer.Default.Equals(method.ReturnType, method.OverriddenMethod?.ReturnType);

public static IEnumerable<string> MakeGenericInstanceMethod(this string methodReferenceVariable, string targetVarName, IEnumerable<string> resolvedTypeArguments)
public static IEnumerable<string> MakeGenericInstanceMethod(this string methodReferenceVariable, IVisitorContext context, string methodName, IReadOnlyList<string> resolvedTypeArguments, out string varName)
{
var expressions = new List<string>();
expressions.Add($"var {targetVarName} = new GenericInstanceMethod({methodReferenceVariable});");
var hash = new HashCode();
hash.Add(methodReferenceVariable);
hash.Add(resolvedTypeArguments.Count);
foreach (var t in resolvedTypeArguments)
expressions.Add($"{targetVarName}.GenericArguments.Add({t});");

return expressions;
hash.Add(t);


List<string> exps = new();
varName = context.Services.Get<GenericInstanceMethodCacheService<int, string>>().GetOrCreate(hash.ToHashCode(), (context, methodName, resolvedTypeArguments, methodReferenceVariable, exps),
static (hashCode, state) =>
{
var genericInstanceVarName = state.context.Naming.SyntheticVariable(state.methodName, ElementKind.GenericInstance);
state.exps.Add($"var {genericInstanceVarName} = new GenericInstanceMethod({state.methodReferenceVariable});");
foreach (var t in state.resolvedTypeArguments)
{
state.exps.Add($"{genericInstanceVarName}.GenericArguments.Add({t});");
}
return genericInstanceVarName;
});

return exps;
}

public static string MakeGenericInstanceMethod(this string methodReferenceVariable, IVisitorContext context, string methodName, IReadOnlyList<string> resolvedTypeArguments)
{
var exps = methodReferenceVariable.MakeGenericInstanceMethod(context, methodName, resolvedTypeArguments, out var genericInstanceVarName);
context.WriteCecilExpressions(exps);

return genericInstanceVarName;
}

private static bool IsExplicitMethodImplementation(this IMethodSymbol methodSymbol)
Expand Down

0 comments on commit 0d511d4

Please sign in to comment.