-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
598 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using NUnit.Framework; | ||
|
||
namespace Cecilifier.Core.Tests.Tests.Unit; | ||
|
||
[TestFixture] | ||
public class InlineArrayTests : CecilifierUnitTestBase | ||
{ | ||
[Test] | ||
public void Instantiating_InlineArray_EmitsInitObj() | ||
{ | ||
var result = RunCecilifier(""" | ||
var b = new IntBuffer(); | ||
[System.Runtime.CompilerServices.InlineArray(10)] | ||
public struct IntBuffer | ||
{ | ||
private int _element0; | ||
} | ||
"""); | ||
|
||
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match( | ||
@"m_topLevelStatements_\d+.Body.Variables.Add\((?<ia_var>l_b_\d+)\);\s+"+ // local variable *b* | ||
@"(?<emit>il_topLevelMain_\d+.Emit\(OpCodes\.)Ldloca_S, \k<ia_var>\);\s+" + // Loads *b* address | ||
@"\k<emit>Initobj, st_intBuffer_\d+\);")); // Execute *initobj* on *b* | ||
} | ||
|
||
[TestCase("System.Span<int> span = l;", TestName = "Local variable initialization")] | ||
[TestCase("scoped System.Span<int> span; span = l;", TestName = "Local Variable assignment")] | ||
[TestCase("Consume(l);", TestName = "Local passed as argument")] | ||
[TestCase("Consume(p);", TestName = "Parameter passed as argument")] | ||
public void Assigning_InlineArrayToSpan_EmitsPrivateImplementationDetailsType(string triggeringStatements) | ||
{ | ||
var result = RunCecilifier($$""" | ||
void TestMethod(IntBuffer p) | ||
{ | ||
var l = new IntBuffer(); | ||
// This will trigger the emission of <PrivateImplementationDetails>.InlineArrayAsSpan() method | ||
{{triggeringStatements}} | ||
} | ||
void Consume(System.Span<int> span) {} | ||
[System.Runtime.CompilerServices.InlineArray(10)] | ||
public struct IntBuffer | ||
{ | ||
private int _element0; | ||
} | ||
"""); | ||
|
||
var cecilifiedCode = result.GeneratedCode.ReadToEnd(); | ||
Assert.That(cecilifiedCode, Does.Match("""new TypeDefinition\("", "<PrivateImplementationDetails>", .+\)""")); | ||
} | ||
|
||
[Test] | ||
public void AccessToFirstElement_MapsTo_PrivateImplementationDetailsInlineArrayFirstElementRefMethod() | ||
{ | ||
var result = RunCecilifier(""" | ||
var buffer = new IntBuffer(); | ||
buffer[0] = 42; | ||
[System.Runtime.CompilerServices.InlineArray(10)] | ||
public struct IntBuffer | ||
{ | ||
private int _element0; | ||
} | ||
"""); | ||
|
||
var cecilified = result.GeneratedCode.ReadToEnd(); | ||
|
||
// assert that the inline array address is being pushed to the stack... | ||
Assert.That(cecilified, Does.Match(""" | ||
il_topLevelMain_\d+\.Emit\(OpCodes\.Ldloca, l_buffer_\d+\); | ||
\s+//<PrivateImplementationDetails> class. | ||
""")); | ||
|
||
// and later <PrivateImplementationDetails>.InlineArrayFirstElementRef() static method is being invoked | ||
// and the value 42 stored in the address at the top of the stack. | ||
Assert.That(cecilified, Does.Match(""" | ||
(il_topLevelMain_\d+\.Emit\(OpCodes\.)Call, gi_inlineArrayFirstElementRef_\d+\); | ||
\s+\1Ldc_I4, 42\); | ||
\s+\1Stind_I4 | ||
""")); | ||
} | ||
|
||
// Access to not first element | ||
// | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using Cecilifier.Core.CodeGeneration; | ||
using Cecilifier.Core.Extensions; | ||
using Cecilifier.Core.Naming; | ||
using Cecilifier.Core.Variables; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Mono.Cecil.Cil; | ||
|
||
namespace Cecilifier.Core.AST; | ||
public class InlineArrayProcessor | ||
{ | ||
internal static bool HandleInlineArrayConversionToSpan(IVisitorContext context, string ilVar, ITypeSymbol fromType, SyntaxNode fromNode, OpCode opcode, string name, VariableMemberKind memberKind, string parentName = null) | ||
{ | ||
int inlineArrayLength = InlineArrayLengthFrom(fromType); | ||
if (inlineArrayLength == -1) | ||
return false; | ||
|
||
if (!IsNodeUsedToInitializeSpanLocalVariable(context, fromNode) | ||
&& !IsNodeAssignedToLocalVariable(context, fromNode) | ||
&& !IsNodeUsedAsSpanArgument(context, fromNode)) | ||
return false; | ||
|
||
// ldloca.s address of fromNode. | ||
// ldci4 fromNode.Length (size of the inline array) | ||
context.EmitCilInstruction(ilVar, opcode, context.DefinitionVariables.GetVariable(name, memberKind, parentName).VariableName); | ||
context.EmitCilInstruction(ilVar, OpCodes.Ldc_I4, inlineArrayLength); | ||
context.EmitCilInstruction(ilVar, OpCodes.Call, InlineArrayAsSpanMethodFor(context, fromType)); | ||
return true; | ||
|
||
static bool IsNodeAssignedToLocalVariable(IVisitorContext context, SyntaxNode nodeToCheck) | ||
{ | ||
if (nodeToCheck.Parent is not AssignmentExpressionSyntax assignmentExpression) | ||
return false; | ||
|
||
var lhs = context.SemanticModel.GetSymbolInfo(assignmentExpression.Left); | ||
if (lhs.Symbol == null) | ||
return false; | ||
|
||
return SymbolEqualityComparer.Default.Equals(lhs.Symbol.GetMemberType().OriginalDefinition, context.RoslynTypeSystem.SystemSpan); | ||
} | ||
|
||
static bool IsNodeUsedAsSpanArgument(IVisitorContext context, SyntaxNode nodeToCheck) | ||
{ | ||
if (nodeToCheck.Parent is not ArgumentSyntax argumentSyntax) | ||
return false; | ||
|
||
var argumentIndex = ((ArgumentListSyntax) argumentSyntax.Parent).Arguments.IndexOf(argumentSyntax); | ||
var invocation = argumentSyntax.FirstAncestorOrSelf<InvocationExpressionSyntax>(); | ||
var associatedParameterSymbol = ((IMethodSymbol) context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol).Parameters.ElementAtOrDefault(argumentIndex); | ||
|
||
return SymbolEqualityComparer.Default.Equals(associatedParameterSymbol.Type.OriginalDefinition, context.RoslynTypeSystem.SystemSpan); | ||
} | ||
|
||
static bool IsNodeUsedToInitializeSpanLocalVariable(IVisitorContext context, SyntaxNode nodeToCheck) | ||
{ | ||
var parent = nodeToCheck.Parent; | ||
if (!parent.IsKind(SyntaxKind.EqualsValueClause) || !parent.Parent.IsKind(SyntaxKind.VariableDeclarator)) | ||
return false; | ||
|
||
var variableDeclaration = (VariableDeclarationSyntax) parent.Parent.Parent!; | ||
var declaredVariableType = ModelExtensions.GetTypeInfo(context.SemanticModel, variableDeclaration.Type); | ||
|
||
return SymbolEqualityComparer.Default.Equals(declaredVariableType.Type?.OriginalDefinition, context.RoslynTypeSystem.SystemSpan); | ||
} | ||
|
||
static string InlineArrayAsSpanMethodFor(IVisitorContext context, ITypeSymbol inlineArrayType) | ||
{ | ||
return PrivateImplementationInlineArrayGenericInstanceMethodFor( | ||
context, | ||
PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayAsSpanMethod(context), | ||
"InlineArrayAsSpan", | ||
inlineArrayType); | ||
} | ||
} | ||
|
||
internal static bool HandleInlineArrayElementAccess(IVisitorContext context, string ilVar, ElementAccessExpressionSyntax elementAccess) | ||
{ | ||
if (elementAccess.Expression.IsKind(SyntaxKind.ElementAccessExpression)) | ||
return false; | ||
|
||
var expSymbol = context.SemanticModel.GetSymbolInfo(elementAccess.Expression).Symbol.EnsureNotNull(); | ||
if (expSymbol.GetMemberType().TryGetAttribute<InlineArrayAttribute>(out _)) | ||
{ | ||
ExpressionVisitor.Visit(context, ilVar, elementAccess.Expression); | ||
Debug.Assert(elementAccess.ArgumentList.Arguments.Count == 1); | ||
|
||
var method = string.Empty; | ||
if (elementAccess.ArgumentList.Arguments[0].Expression.TryGetLiteralValueFor(out int index) && index == 0) | ||
{ | ||
method = InlineArrayFirstElementRefMethodFor(context, expSymbol.GetMemberType()); | ||
} | ||
else | ||
{ | ||
context.EmitCilInstruction(ilVar, OpCodes.Ldc_I4, index); | ||
method = InlineArrayElementRefMethodFor(context, expSymbol.GetMemberType()); | ||
} | ||
context.EmitCilInstruction(ilVar, OpCodes.Call, method); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
|
||
static string InlineArrayFirstElementRefMethodFor(IVisitorContext context, ITypeSymbol inlineArrayType) | ||
{ | ||
return PrivateImplementationInlineArrayGenericInstanceMethodFor( | ||
context, | ||
PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayFirstElementRefMethod(context), | ||
"InlineArrayFirstElementRef", | ||
inlineArrayType); | ||
} | ||
|
||
static string InlineArrayElementRefMethodFor(IVisitorContext context, ITypeSymbol inlineArrayType) | ||
{ | ||
return PrivateImplementationInlineArrayGenericInstanceMethodFor( | ||
context, | ||
PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayElementRefMethod(context), | ||
"InlineArrayElementRef", | ||
inlineArrayType); | ||
} | ||
} | ||
|
||
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(((IFieldSymbol) inlineArrayType.GetMembers().First()).Type) // TElement | ||
]); | ||
|
||
foreach (var exp in exps) | ||
{ | ||
context.WriteCecilExpression(exp); | ||
context.WriteNewLine(); | ||
} | ||
|
||
return varName; | ||
} | ||
|
||
private static int InlineArrayLengthFrom(ITypeSymbol rhsType) | ||
{ | ||
return rhsType.TryGetAttribute<InlineArrayAttribute>(out var inlineArrayAttribute) | ||
? (int) inlineArrayAttribute.ConstructorArguments.First().Value | ||
: -1; | ||
} | ||
} |
Oops, something went wrong.