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 5a3d51f9b55f0..ee3400d6c3f79 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 55961d53cd542..410b7d28ea88d 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 e7ff640dd4c62..69ff5cf8de56f 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);$(NetCoreAppPrevious);$(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 99545a020291e..56d61b1a097cd 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 1cb387116b0ca..c161eee8beb05 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 f6155a2f4d2c0..18ff0fd9d1947 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 1d2249c180051..9ec51718c7619 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 0000000000000..f788ce9e3a0e6 --- /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 43be6cc595f77..674a050d36ed6 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 93e3ab418286c..37b7ec13dd193 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 063ad0766ad58..782ff670980ca 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 3096da72771a9..5a60d1210af0c 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);