Skip to content

Commit 180428a

Browse files
Merge pull request #3380 from ds5678/runtimehelpers-createspan-array-initialization
Replace `RuntimeHelpers.CreateSpan<T>(LdMemberToken)` with `new T[] { }`
2 parents 53522c4 + 3a13d5a commit 180428a

File tree

3 files changed

+96
-24
lines changed

3 files changed

+96
-24
lines changed

ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ private static TypeC TestC()
447447
#endif
448448
#if CS110 && !NET40
449449
public static ReadOnlySpan<byte> UTF8Literal => "Hello, world!"u8;
450+
public static ReadOnlySpan<byte> UTF8LiteralWithNullTerminator => "Hello, world!\0"u8;
450451
#endif
451452
#endregion
452453

@@ -805,6 +806,19 @@ private static void OutOfMemory()
805806
array[0] = 1;
806807
Console.WriteLine(array.Length);
807808
}
809+
810+
#if !NET40 && CS70
811+
public static ReadOnlySpan<byte> ReadOnlySpanInitializer_ByteArray()
812+
{
813+
return new byte[3] { 1, 2, 3 };
814+
}
815+
816+
public static ReadOnlySpan<int> ReadOnlySpanInitializer_Int32Array()
817+
{
818+
return new int[3] { 1, 2, 3 };
819+
}
820+
#endif
821+
808822
#endregion
809823

810824
#region Object initializers

ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ protected internal override void VisitCall(Call inst)
289289
replacement.AcceptVisitor(this);
290290
return;
291291
}
292+
if (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization(inst, context, out var replacement2))
293+
{
294+
context.Step("TransformRuntimeHelpersCreateSpanInitialization: single-dim", inst);
295+
inst.ReplaceWith(replacement2);
296+
return;
297+
}
292298
base.VisitCall(inst);
293299
TransformAssignment.HandleCompoundAssign(inst, context);
294300
}

ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -119,28 +119,57 @@ internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTra
119119
replacement = null;
120120
if (!context.Settings.ArrayInitializers)
121121
return false;
122-
if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
122+
if (!MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size))
123+
return false;
124+
if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
125+
return false;
126+
var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
127+
replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size);
128+
return replacement != null;
129+
}
130+
131+
internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement)
132+
{
133+
replacement = null;
134+
if (!context.Settings.ArrayInitializers)
135+
return false;
136+
if (!MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field))
137+
return false;
138+
if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
139+
return false;
140+
if (IsSubPatternOfCpblkInitializer(inst))
141+
return false;
142+
var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
143+
var elementTypeSize = elementType.GetSize();
144+
if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0)
145+
return false;
146+
var size = initialValue.Length / elementTypeSize;
147+
replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size);
148+
return replacement != null;
149+
}
150+
151+
private static bool IsSubPatternOfCpblkInitializer(Call inst)
152+
{
153+
if (inst.Parent is not AddressOf { Parent: Call { Parent: Cpblk cpblk } get_Item })
154+
return false;
155+
return MatchGetStaticFieldAddress(get_Item, out _);
156+
}
157+
158+
private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size)
159+
{
160+
if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte)
161+
&& DecodeUTF8String(initialValue, size, out string text))
123162
{
124-
if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA))
125-
{
126-
var valuesList = new List<ILInstruction>();
127-
var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem);
128-
if (context.Settings.Utf8StringLiterals &&
129-
elementType.IsKnownType(KnownTypeCode.Byte) &&
130-
DecodeUTF8String(initialValue, size, out string text))
131-
{
132-
replacement = new LdStrUtf8(text);
133-
return true;
134-
}
135-
if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
136-
{
137-
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
138-
replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
139-
return true;
140-
}
141-
}
163+
return new LdStrUtf8(text);
142164
}
143-
return false;
165+
var valuesList = new List<ILInstruction>();
166+
if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList))
167+
{
168+
var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType));
169+
return BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray());
170+
}
171+
172+
return null;
144173
}
145174

146175
private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text)
@@ -153,9 +182,13 @@ private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out strin
153182
for (int i = 0; i < size; i++)
154183
{
155184
byte val = blob.CurrentPointer[i];
156-
// If the string has control characters, it's probably binary data and not a string.
157-
if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t'))
185+
if (val == 0 && i == size - 1 && size > 1)
186+
{
187+
// Allow explicit null-termination character.
188+
}
189+
else if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t'))
158190
{
191+
// If the string has control characters, it's probably binary data and not a string.
159192
text = null;
160193
return false;
161194
}
@@ -187,6 +220,25 @@ static bool MatchSpanTCtorWithPointerAndSize(NewObj newObj, StatementTransformCo
187220
return true;
188221
}
189222

223+
static bool MatchRuntimeHelpersCreateSpan(Call inst, StatementTransformContext context, out IType elementType, out FieldDefinition field)
224+
{
225+
field = default;
226+
elementType = null;
227+
if (!IsRuntimeHelpers(inst.Method.DeclaringType))
228+
return false;
229+
if (inst.Arguments.Count != 1)
230+
return false;
231+
if (inst.Method is not { Name: "CreateSpan", TypeArguments: [var type] })
232+
return false;
233+
elementType = type;
234+
if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member))
235+
return false;
236+
if (member.MetadataToken.IsNil)
237+
return false;
238+
field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken);
239+
return true;
240+
}
241+
190242
bool DoTransformMultiDim(ILFunction function, Block body, int pos)
191243
{
192244
if (pos >= body.Instructions.Count - 2)
@@ -334,7 +386,7 @@ bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out
334386
return true;
335387
}
336388

337-
bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
389+
static bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
338390
{
339391
if (input.MatchLdsFlda(out field))
340392
return true;
@@ -357,7 +409,7 @@ bool MatchGetStaticFieldAddress(ILInstruction input, out IField field)
357409
return field != null;
358410
}
359411

360-
static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices" };
412+
static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices", TypeParameterCount: 0 };
361413

362414
unsafe bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove)
363415
{

0 commit comments

Comments
 (0)