Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Label support in new ILGenerator #93565

Merged
merged 13 commits into from
Oct 26, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private int GetLabelPos(Label lbl)
// Gets the position in the stream of a particular label.
// Verifies that the label exists and that it has been given a value.

int index = lbl.GetLabelValue();
int index = lbl.Id;

if (index < 0 || index >= m_labelCount || m_labelList is null)
throw new ArgumentException(SR.Argument_BadLabel);
Expand Down Expand Up @@ -308,7 +308,7 @@ private void AddFixup(Label lbl, int pos, int instSize)
m_fixupInstSize = instSize
};

int labelIndex = lbl.GetLabelValue();
int labelIndex = lbl.Id;
if (labelIndex < 0 || labelIndex >= m_labelCount || m_labelList is null)
throw new ArgumentException(SR.Argument_BadLabel);

Expand Down Expand Up @@ -974,7 +974,7 @@ public override void EndExceptionBlock()
// Check if we've already set this label.
// The only reason why we might have set this is if we have a finally block.

Label label = m_labelList![endLabel.GetLabelValue()].m_pos != -1
Label label = m_labelList![endLabel.Id].m_pos != -1
? current.m_finallyEndLabel
: endLabel;

Expand Down Expand Up @@ -1115,7 +1115,7 @@ public override void MarkLabel(Label loc)
// Defines a label by setting the position where that label is found within the stream.
// Does not allow a label to be defined more than once.

int labelIndex = loc.GetLabelValue();
int labelIndex = loc.Id;

// This should only happen if a label from another generator is used with this one.
if (m_labelList is null || labelIndex < 0 || labelIndex >= m_labelList.Length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ protected ILGenerator()
{
}

/// <summary>
/// Creates a <see cref="Label"/> with the given id.
/// </summary>
/// <param name="id">The unique id for the label.</param>
/// <returns>The <see cref="Label"/> created.</returns>
protected static Label CreateLabel(int id) => new Label(id);

#region Public Members

#region Emit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ namespace System.Reflection.Emit

internal Label(int label) => m_label = label;

internal int GetLabelValue() => m_label;
/// <summary>
/// Gets the label unique id assigned by the ILGenerator.
/// </summary>
public int Id => m_label;

public override int GetHashCode() => m_label;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ protected ILGenerator() { }
public abstract void BeginFaultBlock();
public abstract void BeginFinallyBlock();
public abstract void BeginScope();
protected static Label CreateLabel(int id) { throw null; }
public virtual System.Reflection.Emit.LocalBuilder DeclareLocal(System.Type localType) { throw null; }
public abstract System.Reflection.Emit.LocalBuilder DeclareLocal(System.Type localType, bool pinned);
public abstract System.Reflection.Emit.Label DefineLabel();
Expand Down Expand Up @@ -62,6 +63,7 @@ public virtual void ThrowException([System.Diagnostics.CodeAnalysis.DynamicallyA
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; }
public bool Equals(System.Reflection.Emit.Label obj) { throw null; }
public override int GetHashCode() { throw null; }
public int Id { get { throw null; } }
public static bool operator ==(System.Reflection.Emit.Label a, System.Reflection.Emit.Label b) { throw null; }
public static bool operator !=(System.Reflection.Emit.Label a, System.Reflection.Emit.Label b) { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;

namespace System.Reflection.Emit.Tests
{
public class LabelId
{
[Fact]
public void LabelId_DefaultConstuctor_ReturnsZero()
{
Label label1 = new Label();
Label label2 = new Label();

Assert.Equal(0, label1.Id);
Assert.Equal(label2.Id, label1.Id);
}

[Fact]
public void LabelId_CreatedByILGenerator_ReturnsId()
{
TypeBuilder type = Helpers.DynamicType(TypeAttributes.NotPublic);
MethodBuilder method = type.DefineMethod("Method", MethodAttributes.Public);
ILGenerator ilGenerator = method.GetILGenerator();
for (int i = 0; i < 100; i++)
{
Label label = ilGenerator.DefineLabel();
Assert.Equal(i, label.Id);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,10 @@
<data name="InvalidOperation_ShouldNotHaveMethodBody" xml:space="preserve">
<value>Method body should not exist.</value>
</data>
<data name="Argument_InvalidLabel" xml:space="preserve">
<value>Invalid Label.</value>
</data>
<data name="Argument_MustBeSwitchOpCode" xml:space="preserve">
<value>Only 'OpCode.Switch' can be used.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
Expand All @@ -16,6 +17,7 @@ internal sealed class ILGeneratorImpl : ILGenerator
private bool _hasDynamicStackAllocation;
private int _maxStackSize;
private int _currentStack;
private Dictionary<Label, LabelHandle> _labelTable = new(2);

internal ILGeneratorImpl(MethodBuilder methodBuilder, int size)
{
Expand All @@ -39,8 +41,14 @@ internal ILGeneratorImpl(MethodBuilder methodBuilder, int size)
public override void BeginFinallyBlock() => throw new NotImplementedException();
public override void BeginScope() => throw new NotImplementedException();
public override LocalBuilder DeclareLocal(Type localType, bool pinned) => throw new NotImplementedException();
public override Label DefineLabel() => throw new NotImplementedException();

public override Label DefineLabel()
{
LabelHandle metadataLabel = _il.DefineLabel();
Label emitLabel = CreateLabel(metadataLabel.Id);
_labelTable.Add(emitLabel, metadataLabel);
return emitLabel;
}
private void UpdateStackSize(OpCode opCode)
{
_currentStack += opCode.EvaluationStackDelta;
Expand Down Expand Up @@ -190,8 +198,36 @@ public override void Emit(OpCode opcode, string str)
}

public override void Emit(OpCode opcode, ConstructorInfo con) => throw new NotImplementedException();
public override void Emit(OpCode opcode, Label label) => throw new NotImplementedException();
public override void Emit(OpCode opcode, Label[] labels) => throw new NotImplementedException();

public override void Emit(OpCode opcode, Label label)
{
if (_labelTable.TryGetValue(label, out LabelHandle labelHandle))
{
_il.Branch((ILOpCode)opcode.Value, labelHandle);
UpdateStackSize(opcode);
}
else
{
throw new ArgumentException(SR.Argument_InvalidLabel);
}
}

public override void Emit(OpCode opcode, Label[] labels)
{
if (!opcode.Equals(OpCodes.Switch))
{
throw new ArgumentException(SR.Argument_MustBeSwitchOpCode, nameof(opcode));
}

SwitchInstructionEncoder switchEncoder = _il.Switch(labels.Length);
UpdateStackSize(opcode);

foreach (Label label in labels)
{
switchEncoder.Branch(_labelTable[label]);
}
}

public override void Emit(OpCode opcode, LocalBuilder local) => throw new NotImplementedException();
public override void Emit(OpCode opcode, SignatureHelper signature) => throw new NotImplementedException();
public override void Emit(OpCode opcode, FieldInfo field) => throw new NotImplementedException();
Expand All @@ -202,7 +238,19 @@ public override void Emit(OpCode opcode, string str)
public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes) => throw new NotImplementedException();
public override void EndExceptionBlock() => throw new NotImplementedException();
public override void EndScope() => throw new NotImplementedException();
public override void MarkLabel(Label loc) => throw new NotImplementedException();

public override void MarkLabel(Label loc)
{
if (_labelTable.TryGetValue(loc, out LabelHandle labelHandle))
{
_il.MarkLabel(labelHandle);
}
else
{
throw new ArgumentException(SR.Argument_InvalidLabel);
}
}

public override void UsingNamespace(string usingNamespace) => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.IO;
using System.Linq;
using System.Text;
using Xunit;

namespace System.Reflection.Emit.Tests
Expand Down Expand Up @@ -244,5 +245,121 @@ private MethodInfo LoadILGenerator_GetMaxStackSizeMethod()
Type ilgType = Type.GetType("System.Reflection.Emit.ILGeneratorImpl, System.Reflection.Emit", throwOnError: true)!;
return ilgType.GetMethod("GetMaxStackSize", BindingFlags.NonPublic | BindingFlags.Instance, Type.EmptyTypes);
}

[Fact]
public void Label_ConditionalBranching()
{
using (TempFile file = TempFile.Create())
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod);
MethodBuilder methodBuilder = type.DefineMethod("Method1", MethodAttributes.Public, typeof(int), new[] { typeof(int), typeof(int) });
ILGenerator il = methodBuilder.GetILGenerator();
Label failed = il.DefineLabel();
Label endOfMethod = il.DefineLabel();

// public int Method1(int P_0, int P_1) => (P_0 > 100 || P_1 > 100) ? (-1) : (P_0 + P_1);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4_S, 100);
il.Emit(OpCodes.Bgt_S, failed);

il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldc_I4_S, 100);
il.Emit(OpCodes.Bgt_S, failed);

il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Br_S, endOfMethod);

il.MarkLabel(failed);
il.Emit(OpCodes.Ldc_I4_M1);
il.MarkLabel(endOfMethod);
il.Emit(OpCodes.Ret);

saveMethod.Invoke(ab, new object[] { file.Path });

MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod();
Assert.Equal(2, getMaxStackSizeMethod.Invoke(il, new object[0]));

Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path);
Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType");
byte[]? bodyBytes = typeFromDisk.GetMethod("Method1").GetMethodBody().GetILAsByteArray();
Assert.Equal(
[
(byte)OpCodes.Ldarg_1.Value, (byte)OpCodes.Ldc_I4_S.Value, 100, 0, 0, 0,
(byte)OpCodes.Bgt_S.Value, 13,
(byte)OpCodes.Ldarg_2.Value, (byte)OpCodes.Ldc_I4_S.Value, 100, 0, 0, 0,
(byte)OpCodes.Bgt_S.Value, 5,
(byte)OpCodes.Ldarg_1.Value, (byte)OpCodes.Ldarg_2.Value, (byte)OpCodes.Add.Value,
(byte)OpCodes.Br_S.Value, (byte)OpCodes.Break.Value,
(byte)OpCodes.Ldc_I4_M1.Value, (byte)OpCodes.Ret.Value
], bodyBytes);
}
}

[Fact]
public void Label_SwitchCase()
{
using (TempFile file = TempFile.Create())
{
AssemblyBuilder ab = AssemblySaveTools.PopulateAssemblyBuilderTypeBuilderAndSaveMethod(out TypeBuilder type, out MethodInfo saveMethod);
MethodBuilder methodBuilder = type.DefineMethod("Method1", MethodAttributes.Public, typeof(string), new[] { typeof(int) });
ILGenerator il = methodBuilder.GetILGenerator();
Label defaultCase = il.DefineLabel();
Label endOfMethod = il.DefineLabel();
Label[] jumpTable = [ il.DefineLabel(), il.DefineLabel(), il.DefineLabel(), il.DefineLabel(), il.DefineLabel() ];

// public string Method1(int P_0) => P_0 switch ...
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Switch, jumpTable);

// Branch on default case
il.Emit(OpCodes.Br_S, defaultCase);

// Case P_0 = 0
il.MarkLabel(jumpTable[0]);
il.Emit(OpCodes.Ldstr, "no bananas");
il.Emit(OpCodes.Br_S, endOfMethod);

// Case P_0 = 1
il.MarkLabel(jumpTable[1]);
il.Emit(OpCodes.Ldstr, "one banana");
il.Emit(OpCodes.Br_S, endOfMethod);

// Case P_0 = 2
il.MarkLabel(jumpTable[2]);
il.Emit(OpCodes.Ldstr, "two bananas");
il.Emit(OpCodes.Br_S, endOfMethod);

// Case P_0 = 3
il.MarkLabel(jumpTable[3]);
il.Emit(OpCodes.Ldstr, "three bananas");
il.Emit(OpCodes.Br_S, endOfMethod);

// Case P_0 = 4
il.MarkLabel(jumpTable[4]);
il.Emit(OpCodes.Ldstr, "four bananas");
il.Emit(OpCodes.Br_S, endOfMethod);

// Default case
il.MarkLabel(defaultCase);
il.Emit(OpCodes.Ldstr, "many bananas");
il.MarkLabel(endOfMethod);
il.Emit(OpCodes.Ret);

saveMethod.Invoke(ab, new object[] { file.Path });

MethodInfo getMaxStackSizeMethod = LoadILGenerator_GetMaxStackSizeMethod();
Assert.Equal(6, getMaxStackSizeMethod.Invoke(il, new object[0]));

Assembly assemblyFromDisk = AssemblySaveTools.LoadAssemblyFromPath(file.Path);
Type typeFromDisk = assemblyFromDisk.Modules.First().GetType("MyType");
byte[]? bodyBytes = typeFromDisk.GetMethod("Method1").GetMethodBody().GetILAsByteArray();
Assert.Equal((byte)OpCodes.Ldarg_1.Value, bodyBytes[0]);
Assert.Equal((byte)OpCodes.Switch.Value, bodyBytes[1]);
Assert.Equal(5, bodyBytes[2]); // case count
Assert.Equal(69, bodyBytes.Length);
}
}
}
}
Loading