diff --git a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs
index 3a97d5bfcb23a9..07f8e50aa70a70 100644
--- a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs
+++ b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs
@@ -2689,6 +2689,7 @@ public void MarkLabel(System.Reflection.Metadata.Ecma335.LabelHandle label) { }
public void OpCode(System.Reflection.Metadata.ILOpCode code) { }
public void StoreArgument(int argumentIndex) { }
public void StoreLocal(int slotIndex) { }
+ public System.Reflection.Metadata.Ecma335.SwitchInstructionEncoder Switch(int branchCount) { throw null; }
public void Token(int token) { }
public void Token(System.Reflection.Metadata.EntityHandle handle) { }
}
@@ -3069,6 +3070,10 @@ public void UInt64() { }
public void UIntPtr() { }
public void VoidPointer() { }
}
+ public readonly struct SwitchInstructionEncoder
+ {
+ public void Branch(System.Reflection.Metadata.Ecma335.LabelHandle label) { }
+ }
public enum TableIndex : byte
{
Module = (byte)0,
diff --git a/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx b/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx
index 55961d53cd5422..410b7d28ea88da 100644
--- a/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx
+++ b/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx
@@ -1,4 +1,4 @@
-
+
@@ -405,4 +405,10 @@
Unexpected value '{0}' of unknown type.
-
+
+ The SwitchInstructionEncoder.Branch method was invoked too few times.
+
+
+ The SwitchInstructionEncoder.Branch method was invoked too many times.
+
+
\ No newline at end of file
diff --git a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
index 73a99e80c14fed..c5056ba5438ccb 100644
--- a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
+++ b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
@@ -1,4 +1,4 @@
-
+
$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)
true
@@ -31,6 +31,7 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
+
@@ -89,7 +90,7 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
-
+
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/EnumerableExtensions.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/EnumerableExtensions.cs
index 99545a020291e3..56d61b1a097cd0 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/EnumerableExtensions.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/EnumerableExtensions.cs
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
namespace System.Reflection.Internal
{
@@ -13,19 +11,6 @@ namespace System.Reflection.Internal
///
internal static class EnumerableExtensions
{
- public static T? FirstOrDefault(this ImmutableArray collection, Func predicate)
- {
- foreach (var item in collection)
- {
- if (predicate(item))
- {
- return item;
- }
- }
-
- return default;
- }
-
// used only in debugger display so we needn't get fancy with optimizations.
public static IEnumerable Select(this IEnumerable source, Func selector)
{
@@ -35,11 +20,6 @@ public static IEnumerable Select(this IEnumerable(this ImmutableArray.Builder source)
- {
- return source[source.Count - 1];
- }
-
public static IEnumerable OrderBy(this List source, Comparison comparison)
{
// Produce an iterator that represents a stable sort of source.
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs
index 1cb387116b0caf..c161eee8beb05c 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs
@@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Diagnostics;
-using System.Reflection.Internal;
namespace System.Reflection.Metadata.Ecma335
{
@@ -13,20 +11,34 @@ public sealed class ControlFlowBuilder
// internal for testing:
internal readonly struct BranchInfo
{
- internal readonly int ILOffset;
+ // The offset to the label operand inside the instruction.
+ internal readonly int OperandOffset;
internal readonly LabelHandle Label;
- private readonly byte _opCode;
+ // Label offsets are calculated from the end of the instruction that contains them.
+ // This value contains the displacement from the start of the label operand
+ // to the end of the instruction. It is equal to one on short branches,
+ // four on long branches and bigger on the switch instruction.
+ private readonly int _instructionEndDisplacement;
+
+ // The following two fields are used for error reporting and tests.
+
+ // The offset to the start of the instruction.
+ internal readonly int ILOffset;
+ internal readonly ILOpCode OpCode;
- internal ILOpCode OpCode => (ILOpCode)_opCode;
+ internal bool IsShortBranch => _instructionEndDisplacement == 1;
+ internal int OperandSize => Math.Min(_instructionEndDisplacement, 4);
- internal BranchInfo(int ilOffset, LabelHandle label, ILOpCode opCode)
+ internal BranchInfo(int operandOffset, LabelHandle label, int instructionEndDisplacement, int ilOffset, ILOpCode opCode)
{
- ILOffset = ilOffset;
+ OperandOffset = operandOffset;
Label = label;
- _opCode = (byte)opCode;
+ _instructionEndDisplacement = instructionEndDisplacement;
+ ILOffset = ilOffset;
+ OpCode = opCode;
}
- internal int GetBranchDistance(ImmutableArray.Builder labels, ILOpCode branchOpCode, int branchILOffset, bool isShortBranch)
+ internal int GetBranchDistance(List labels)
{
int labelTargetOffset = labels[Label.Id - 1];
if (labelTargetOffset < 0)
@@ -34,16 +46,15 @@ internal int GetBranchDistance(ImmutableArray.Builder labels, ILOpCode bran
Throw.InvalidOperation_LabelNotMarked(Label.Id);
}
- int branchInstructionSize = 1 + (isShortBranch ? sizeof(sbyte) : sizeof(int));
- int distance = labelTargetOffset - (ILOffset + branchInstructionSize);
+ int distance = labelTargetOffset - (OperandOffset + _instructionEndDisplacement);
- if (isShortBranch && unchecked((sbyte)distance) != distance)
+ if (IsShortBranch && unchecked((sbyte)distance) != distance)
{
// We could potentially implement algorithm that automatically fixes up branch instructions to accommodate for bigger distances (short vs long),
// however an optimal algorithm would be rather complex (something like: calculate topological ordering of crossing branch instructions
// and then use fixed point to eliminate cycles). If the caller doesn't care about optimal IL size they can use long branches whenever the
// distance is unknown upfront. If they do they probably implement more sophisticated algorithm for IL layout optimization already.
- throw new InvalidOperationException(SR.Format(SR.DistanceBetweenInstructionAndLabelTooBig, branchOpCode, branchILOffset, distance));
+ throw new InvalidOperationException(SR.Format(SR.DistanceBetweenInstructionAndLabelTooBig, OpCode, ILOffset, distance));
}
return distance;
@@ -75,14 +86,14 @@ public ExceptionHandlerInfo(
}
}
- private readonly ImmutableArray.Builder _branches;
- private readonly ImmutableArray.Builder _labels;
- private ImmutableArray.Builder? _lazyExceptionHandlers;
+ private readonly List _branches;
+ private readonly List _labels;
+ private List? _lazyExceptionHandlers;
public ControlFlowBuilder()
{
- _branches = ImmutableArray.CreateBuilder();
- _labels = ImmutableArray.CreateBuilder();
+ _branches = new List();
+ _labels = new List();
}
///
@@ -93,25 +104,42 @@ public void Clear()
_branches.Clear();
_labels.Clear();
_lazyExceptionHandlers?.Clear();
+ RemainingSwitchBranches = 0;
}
internal LabelHandle AddLabel()
{
+ ValidateNotInSwitch();
_labels.Add(-1);
return new LabelHandle(_labels.Count);
}
- internal void AddBranch(int ilOffset, LabelHandle label, ILOpCode opCode)
+ internal void AddBranch(int operandOffset, LabelHandle label, int instructionEndDisplacement, int ilOffset, ILOpCode opCode)
{
- Debug.Assert(ilOffset >= 0);
- Debug.Assert(_branches.Count == 0 || ilOffset > _branches.Last().ILOffset);
+ Debug.Assert(operandOffset >= 0);
+ Debug.Assert(_branches.Count == 0 || operandOffset > _branches[_branches.Count - 1].OperandOffset);
ValidateLabel(label, nameof(label));
- _branches.Add(new BranchInfo(ilOffset, label, opCode));
+#if DEBUG
+ switch (instructionEndDisplacement)
+ {
+ case 1:
+ Debug.Assert(opCode.GetBranchOperandSize() == 1);
+ break;
+ case 4:
+ Debug.Assert(opCode == ILOpCode.Switch || opCode.GetBranchOperandSize() == 4);
+ break;
+ default:
+ Debug.Assert(instructionEndDisplacement > 4 && instructionEndDisplacement % 4 == 0 && opCode == ILOpCode.Switch);
+ break;
+ }
+#endif
+ _branches.Add(new BranchInfo(operandOffset, label, instructionEndDisplacement, ilOffset, opCode));
}
internal void MarkLabel(int ilOffset, LabelHandle label)
{
Debug.Assert(ilOffset >= 0);
+ ValidateNotInSwitch();
ValidateLabel(label, nameof(label));
_labels[label.Id - 1] = ilOffset;
}
@@ -214,8 +242,9 @@ private void AddExceptionRegion(
ValidateLabel(tryEnd, nameof(tryEnd));
ValidateLabel(handlerStart, nameof(handlerStart));
ValidateLabel(handlerEnd, nameof(handlerEnd));
+ ValidateNotInSwitch();
- _lazyExceptionHandlers ??= ImmutableArray.CreateBuilder();
+ _lazyExceptionHandlers ??= new List();
_lazyExceptionHandlers.Add(new ExceptionHandlerInfo(kind, tryStart, tryEnd, handlerStart, handlerEnd, filterStart, catchType));
}
@@ -230,6 +259,25 @@ private void AddExceptionRegion(
internal int ExceptionHandlerCount => _lazyExceptionHandlers?.Count ?? 0;
+ internal int RemainingSwitchBranches { get; set; }
+
+ internal void ValidateNotInSwitch()
+ {
+ if (RemainingSwitchBranches > 0)
+ {
+ Throw.InvalidOperation(SR.SwitchInstructionEncoderTooFewBranches);
+ }
+ }
+
+ internal void SwitchBranchAdded()
+ {
+ if (RemainingSwitchBranches == 0)
+ {
+ Throw.InvalidOperation(SR.SwitchInstructionEncoderTooManyBranches);
+ }
+ RemainingSwitchBranches--;
+ }
+
///
internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder)
{
@@ -252,7 +300,7 @@ internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBu
while (true)
{
// copy bytes preceding the next branch, or till the end of the blob:
- int chunkSize = Math.Min(branch.ILOffset - srcOffset, srcBlob.Length - srcBlobOffset);
+ int chunkSize = Math.Min(branch.OperandOffset - srcOffset, srcBlob.Length - srcBlobOffset);
dstBuilder.WriteBytes(srcBlob.Buffer, srcBlobOffset, chunkSize);
srcOffset += chunkSize;
srcBlobOffset += chunkSize;
@@ -264,22 +312,17 @@ internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBu
break;
}
- Debug.Assert(srcBlob.Buffer[srcBlobOffset] == (byte)branch.OpCode);
-
- int operandSize = branch.OpCode.GetBranchOperandSize();
- bool isShortInstruction = operandSize == 1;
+ int operandSize = branch.OperandSize;
+ bool isShortInstruction = branch.IsShortBranch;
// Note: the 4B operand is contiguous since we wrote it via BlobBuilder.WriteInt32()
Debug.Assert(
- srcBlobOffset + 1 == srcBlob.Length ||
+ srcBlobOffset == srcBlob.Length ||
(isShortInstruction ?
- srcBlob.Buffer[srcBlobOffset + 1] == 0xff :
- BitConverter.ToUInt32(srcBlob.Buffer, srcBlobOffset + 1) == 0xffffffff));
-
- // write branch opcode:
- dstBuilder.WriteByte(srcBlob.Buffer[srcBlobOffset]);
+ srcBlob.Buffer[srcBlobOffset] == 0xff :
+ BitConverter.ToUInt32(srcBlob.Buffer, srcBlobOffset) == 0xffffffff));
- int branchDistance = branch.GetBranchDistance(_labels, branch.OpCode, srcOffset, isShortInstruction);
+ int branchDistance = branch.GetBranchDistance(_labels);
// write branch operand:
if (isShortInstruction)
@@ -291,13 +334,16 @@ internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBu
dstBuilder.WriteInt32(branchDistance);
}
- srcOffset += sizeof(byte) + operandSize;
+ srcOffset += operandSize;
// next branch:
branchIndex++;
if (branchIndex == _branches.Count)
{
- branch = new BranchInfo(int.MaxValue, label: default, opCode: default);
+ // We have processed all branches. The MaxValue will cause the rest
+ // of the IL stream to be directly copied to the destination blob.
+ branch = new BranchInfo(operandOffset: int.MaxValue, label: default,
+ instructionEndDisplacement: default, ilOffset: default, opCode: default);
}
else
{
@@ -311,8 +357,8 @@ internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBu
break;
}
- // skip fake branch instruction:
- srcBlobOffset += sizeof(byte) + operandSize;
+ // skip fake branch operand:
+ srcBlobOffset += operandSize;
}
}
}
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs
index f6155a2f4d2c0d..18ff0fd9d1947d 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs
@@ -53,6 +53,7 @@ public InstructionEncoder(BlobBuilder codeBuilder, ControlFlowBuilder? controlFl
///
public void OpCode(ILOpCode code)
{
+ ControlFlowBuilder?.ValidateNotInSwitch();
if (unchecked((byte)code) == (ushort)code)
{
CodeBuilder.WriteByte((byte)code);
@@ -80,6 +81,7 @@ public void Token(EntityHandle handle)
///
public void Token(int token)
{
+ ControlFlowBuilder?.ValidateNotInSwitch();
CodeBuilder.WriteInt32(token);
}
@@ -389,6 +391,23 @@ public LabelHandle DefineLabel()
return GetBranchBuilder().AddLabel();
}
+ internal void LabelOperand(ILOpCode code, LabelHandle label, int instructionEndDisplacement, int ilOffset)
+ {
+ GetBranchBuilder().AddBranch(Offset, label, instructionEndDisplacement, ilOffset, code);
+
+ // -1 points in the middle of the branch instruction and is thus invalid.
+ // We want to produce invalid IL so that if the caller doesn't patch the branches
+ // the branch instructions will be invalid in an obvious way.
+ if (instructionEndDisplacement == 1)
+ {
+ CodeBuilder.WriteSByte(-1);
+ }
+ else
+ {
+ CodeBuilder.WriteInt32(-1);
+ }
+ }
+
///
/// Encodes a branch instruction.
///
@@ -401,23 +420,49 @@ public LabelHandle DefineLabel()
public void Branch(ILOpCode code, LabelHandle label)
{
// throws if code is not a branch:
- int size = code.GetBranchOperandSize();
+ int operandSize = code.GetBranchOperandSize();
+ // We want the offset before we add the opcode.
+ int ilOffset = Offset;
- GetBranchBuilder().AddBranch(Offset, label, code);
OpCode(code);
+ LabelOperand(code, label, operandSize, ilOffset);
+ }
- // -1 points in the middle of the branch instruction and is thus invalid.
- // We want to produce invalid IL so that if the caller doesn't patch the branches
- // the branch instructions will be invalid in an obvious way.
- if (size == 1)
- {
- CodeBuilder.WriteSByte(-1);
- }
- else
+ ///
+ /// Starts encoding a switch instruction.
+ ///
+ /// The number of branches the instruction will have.
+ /// A that will
+ /// be used to emit the labels for the branches.
+ ///
+ /// Before using this in any other way,
+ /// the method
+ /// must be called on the returned value exactly
+ /// times. Failure to do so will throw .
+ ///
+ ///
+ /// less than or equal to zero.
+ public SwitchInstructionEncoder Switch(int branchCount)
+ {
+ if (branchCount <= 0)
{
- Debug.Assert(size == 4);
- CodeBuilder.WriteInt32(-1);
+ Throw.ArgumentOutOfRange(nameof(branchCount));
}
+ ControlFlowBuilder branchBuilder = GetBranchBuilder();
+
+ // We want the offset before we add the opcode.
+ int ilOffset = Offset;
+
+ OpCode(ILOpCode.Switch);
+ branchBuilder.RemainingSwitchBranches = branchCount;
+ CodeBuilder.WriteUInt32((uint)branchCount);
+
+ // We calculate the offset where the instruction will end.
+ // The Offset property now accounts for the opcode byte and
+ // the four bits of the count, and we also add four bytes for
+ // each branch we are expecting from the user to emit.
+ int instructionEnd = Offset + 4 * branchCount;
+ return new SwitchInstructionEncoder(this, ilOffset, instructionEnd);
}
///
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs
index 1d2249c180051d..9ec51718c76190 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs
@@ -189,6 +189,8 @@ public int AddMethodBody(
Throw.ArgumentOutOfRange(nameof(instructionEncoder), SR.TooManyExceptionRegions);
}
+ flowBuilder?.ValidateNotInSwitch();
+
// Note (see also https://github.com/dotnet/runtime/issues/24948)
//
// We could potentially automatically determine whether a tiny method with no variables and InitLocals flag set
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/SwitchInstructionEncoder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/SwitchInstructionEncoder.cs
new file mode 100644
index 00000000000000..f788ce9e3a0e69
--- /dev/null
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/SwitchInstructionEncoder.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Reflection.Metadata.Ecma335
+{
+ ///
+ /// Encodes the branches of an IL switch instruction.
+ ///
+ ///
+ /// See for usage guidelines.
+ ///
+ public readonly struct SwitchInstructionEncoder
+ {
+ private readonly InstructionEncoder _encoder;
+
+ private readonly int _ilOffset, _instructionEnd;
+
+ internal SwitchInstructionEncoder(InstructionEncoder encoder, int ilOffset, int instructionEnd)
+ {
+ Debug.Assert(encoder.ControlFlowBuilder is not null);
+ _encoder = encoder;
+ _ilOffset = ilOffset;
+ _instructionEnd = instructionEnd;
+ }
+
+ ///
+ /// Encodes a branch that is part of a switch instruction.
+ ///
+ ///
+ /// See for usage guidelines.
+ ///
+ public void Branch(LabelHandle label)
+ {
+ _encoder.ControlFlowBuilder!.SwitchBranchAdded();
+ _encoder.LabelOperand(ILOpCode.Switch, label, _instructionEnd - _encoder.Offset, _ilOffset);
+ }
+ }
+}
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs
index eda470b5bd7137..0519004f8880d9 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs
@@ -8,6 +8,7 @@
using System.Reflection.Metadata;
using System.Runtime.ExceptionServices;
using System.Threading;
+using ImmutableArrayExtensions = System.Linq.ImmutableArrayExtensions;
namespace System.Reflection.PortableExecutable
{
@@ -726,7 +727,7 @@ public bool TryOpenAssociatedPortablePdb(string peImagePath, Func e.IsPortableCodeView);
+ var codeViewEntry = ImmutableArrayExtensions.FirstOrDefault(entries, e => e.IsPortableCodeView);
if (codeViewEntry.DataSize != 0 &&
TryOpenCodeViewPortablePdb(codeViewEntry, peImageDirectory!, pdbFileStreamProvider, out pdbReaderProvider, out pdbPath, ref errorToReport))
{
@@ -734,7 +735,7 @@ public bool TryOpenAssociatedPortablePdb(string peImagePath, Func e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb);
+ var embeddedPdbEntry = ImmutableArrayExtensions.FirstOrDefault(entries, e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb);
if (embeddedPdbEntry.DataSize != 0)
{
bool openedEmbeddedPdb = false;
diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs
index 93e3ab418286c9..37b7ec13dd1938 100644
--- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs
+++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs
@@ -361,7 +361,7 @@ public void Branch_LongInstruction_LongDistance()
AssertEx.Equal(new byte[]
{
- 0x13, 0x30, 0x08, 0x00, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// header
+ 0x13, 0x30, 0x08, 0x00, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // header
(byte)ILOpCode.Br, 0x04, 0x01, 0x00, 0x00,
(byte)ILOpCode.Call, 0x01, 0x00, 0x00, 0x06,
(byte)ILOpCode.Call, 0x01, 0x00, 0x00, 0x06,
@@ -419,6 +419,50 @@ public void Branch_LongInstruction_LongDistance()
}, builder.ToArray());
}
+ [Fact]
+ public void Switch()
+ {
+ var code = new BlobBuilder();
+ var il = new InstructionEncoder(code, new ControlFlowBuilder());
+
+ var lStart = il.DefineLabel();
+ var l1 = il.DefineLabel();
+ var l2 = il.DefineLabel();
+ var l3 = il.DefineLabel();
+
+ il.OpCode(ILOpCode.Nop);
+ il.MarkLabel(lStart);
+ var switchEncoder = il.Switch(4);
+ switchEncoder.Branch(l1);
+ switchEncoder.Branch(l2);
+ switchEncoder.Branch(l3);
+ switchEncoder.Branch(lStart);
+
+ il.MarkLabel(l1);
+ il.OpCode(ILOpCode.Nop);
+ il.MarkLabel(l2);
+ il.OpCode(ILOpCode.Nop);
+ il.MarkLabel(l3);
+ il.OpCode(ILOpCode.Nop);
+
+ var builder = new BlobBuilder();
+ new MethodBodyStreamEncoder(builder).AddMethodBody(il);
+
+ AssertEx.Equal(new byte[]
+ {
+ 0x66, // header
+ (byte)ILOpCode.Nop,
+ (byte)ILOpCode.Switch, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0xeb, 0xff, 0xff, 0xff,
+ (byte)ILOpCode.Nop,
+ (byte)ILOpCode.Nop,
+ (byte)ILOpCode.Nop
+ }, builder.ToArray());
+ }
+
[Fact]
public void Clear()
{
diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs
index 063ad0766ad582..782ff670980ca1 100644
--- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs
+++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs
@@ -475,6 +475,36 @@ public void Branch()
}, builder.ToArray());
}
+ [Fact]
+ public void Switch()
+ {
+ var builder = new BlobBuilder();
+ var controlFlowBuilder = new ControlFlowBuilder();
+ var il = new InstructionEncoder(builder, controlFlowBuilder);
+ var l = il.DefineLabel();
+ var switchEncoder = il.Switch(4);
+ Assert.Throws(() => il.OpCode(ILOpCode.Nop));
+ Assert.Throws(() => il.Token(0));
+ Assert.Throws(() => il.DefineLabel());
+ Assert.Throws(() => il.MarkLabel(l));
+ Assert.Throws(() => controlFlowBuilder.AddFinallyRegion(l, l, l, l));
+ Assert.Throws(() => new MethodBodyStreamEncoder(new BlobBuilder()).AddMethodBody(il));
+ switchEncoder.Branch(l);
+ switchEncoder.Branch(l);
+ switchEncoder.Branch(l);
+ switchEncoder.Branch(l);
+ Assert.Throws(() => switchEncoder.Branch(l));
+
+ AssertEx.Equal(new byte[]
+ {
+ (byte)ILOpCode.Switch, 0x04, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff
+ }, builder.ToArray());
+ }
+
[Fact]
public void BranchAndLabel_Errors()
{
diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs
index 3096da72771a9b..5a60d1210af0cb 100644
--- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs
+++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs
@@ -251,6 +251,7 @@ public unsafe void TinyBody()
var brInfo = flowBuilder.Branches.Single();
Assert.Equal(61, brInfo.ILOffset);
+ Assert.Equal(62, brInfo.OperandOffset);
Assert.Equal(l1, brInfo.Label);
Assert.Equal(ILOpCode.Br_s, brInfo.OpCode);
@@ -321,6 +322,7 @@ public unsafe void FatBody()
var brInfo = flowBuilder.Branches.Single();
Assert.Equal(62, brInfo.ILOffset);
+ Assert.Equal(63, brInfo.OperandOffset);
Assert.Equal(l1, brInfo.Label);
Assert.Equal(ILOpCode.Br_s, brInfo.OpCode);
@@ -489,13 +491,13 @@ public void Branches1()
flowBuilder.MarkLabel(64, l64);
flowBuilder.MarkLabel(255, l255);
- flowBuilder.AddBranch(0, l255, ILOpCode.Bge);
- flowBuilder.AddBranch(16, l0, ILOpCode.Bge_un_s); // blob boundary
- flowBuilder.AddBranch(33, l255, ILOpCode.Ble); // blob boundary
- flowBuilder.AddBranch(38, l0, ILOpCode.Ble_un_s); // branches immediately next to each other
- flowBuilder.AddBranch(40, l255, ILOpCode.Blt); // branches immediately next to each other
- flowBuilder.AddBranch(46, l64, ILOpCode.Blt_un_s);
- flowBuilder.AddBranch(254, l0, ILOpCode.Brfalse); // long branch at the end
+ flowBuilder.AddBranch(1, l255, 4, 0, ILOpCode.Bge);
+ flowBuilder.AddBranch(17, l0, 1, 16, ILOpCode.Bge_un_s); // blob boundary
+ flowBuilder.AddBranch(34, l255, 4, 33, ILOpCode.Ble); // blob boundary
+ flowBuilder.AddBranch(39, l0, 1, 38, ILOpCode.Ble_un_s); // branches immediately next to each other
+ flowBuilder.AddBranch(41, l255, 4, 40, ILOpCode.Blt); // branches immediately next to each other
+ flowBuilder.AddBranch(47, l64, 1, 46, ILOpCode.Blt_un_s);
+ flowBuilder.AddBranch(255, l0, 4, 254, ILOpCode.Brfalse); // long branch at the end
var dstBuilder = new BlobBuilder();
var srcBuilder = new BlobBuilder(capacity: 17);