Skip to content

Commit

Permalink
Add Label support in new ILGenerator (dotnet#93565)
Browse files Browse the repository at this point in the history
* Emit Label/Branching IL

* Throw when OpCode is not Switch

Co-authored-by: Jan Kotas <[email protected]>

* Add protected factory to ILGenerator instead of making the Label constructor public

* Update test to assert label.Id instead of label.GetHashCode

---------

Co-authored-by: Jan Kotas <[email protected]>
  • Loading branch information
2 people authored and liveans committed Nov 9, 2023
1 parent a4d66c5 commit 82fb01f
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 9 deletions.
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);
}
}
}
}

0 comments on commit 82fb01f

Please sign in to comment.