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 InstructionEncoder.Switch. #76526

Merged
merged 15 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) { }
}
Expand Down Expand Up @@ -3069,6 +3070,10 @@ public void UInt64() { }
public void UIntPtr() { }
public void VoidPointer() { }
}
public readonly struct SwitchInstructionEncoder
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
public void Branch(System.Reflection.Metadata.Ecma335.LabelHandle label) { }
}
public enum TableIndex : byte
{
Module = (byte)0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,64 @@
<root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
teo-tsirpanis marked this conversation as resolved.
Show resolved Hide resolved

Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
Expand Down Expand Up @@ -405,4 +465,10 @@
<data name="UnexpectedValueUnknownType" xml:space="preserve">
<value>Unexpected value '{0}' of unknown type.</value>
</data>
</root>
<data name="SwitchInstructionEncoderTooFewBranches" xml:space="preserve">
<value>The SwitchInstructionEncoder.Branch method has not been called enough times.</value>
teo-tsirpanis marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="SwitchInstructionEncoderTooManyBranches" xml:space="preserve">
<value>The SwitchInstructionEncoder.Branch method has been called too many times.</value>
teo-tsirpanis marked this conversation as resolved.
Show resolved Hide resolved
</data>
</root>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down Expand Up @@ -31,6 +31,7 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
<Compile Include="System\Reflection\Metadata\Ecma335\Encoding\FunctionPointerAttributes.cs" />
<Compile Include="System\Reflection\Metadata\Ecma335\Encoding\MethodBodyStreamEncoder.cs" />
<Compile Include="System\Reflection\Metadata\Ecma335\Encoding\MethodBodyAttributes.cs" />
<Compile Include="System\Reflection\Metadata\Ecma335\Encoding\SwitchInstructionEncoder.cs" />
<Compile Include="System\Reflection\Metadata\Ecma335\MetadataBuilder.cs" />
<Compile Include="System\Reflection\Metadata\Ecma335\MetadataRootBuilder.cs" />
<Compile Include="System\Reflection\Metadata\Ecma335\SerializedMetadataHeaps.cs" />
Expand Down Expand Up @@ -89,7 +90,7 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
<Compile Include="System\Reflection\PortableExecutable\ResourceSectionBuilder.cs" />
<Compile Include="System\Reflection\PortableExecutable\SectionLocation.cs" />
<Compile Include="System\Reflection\Internal\Utilities\BlobUtilities.cs" />
<None Include="README.md" Pack="true" PackagePath="\"/>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace System.Reflection.Internal
{
Expand All @@ -15,15 +15,7 @@ internal static class EnumerableExtensions
{
public static T? FirstOrDefault<T>(this ImmutableArray<T> collection, Func<T, bool> predicate)
teo-tsirpanis marked this conversation as resolved.
Show resolved Hide resolved
{
foreach (var item in collection)
{
if (predicate(item))
{
return item;
}
}

return default;
return ImmutableArrayExtensions.FirstOrDefault(collection, predicate);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
}

// used only in debugger display so we needn't get fancy with optimizations.
Expand All @@ -35,11 +27,6 @@ public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSo
}
}

public static T Last<T>(this ImmutableArray<T>.Builder source)
{
return source[source.Count - 1];
}

public static IEnumerable<T> OrderBy<T>(this List<T> source, Comparison<T> comparison)
{
// Produce an iterator that represents a stable sort of source.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -13,37 +11,50 @@ 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<int>.Builder labels, ILOpCode branchOpCode, int branchILOffset, bool isShortBranch)
internal int GetBranchDistance(List<int> labels)
{
int labelTargetOffset = labels[Label.Id - 1];
if (labelTargetOffset < 0)
{
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;
Expand Down Expand Up @@ -75,14 +86,14 @@ public ExceptionHandlerInfo(
}
}

private readonly ImmutableArray<BranchInfo>.Builder _branches;
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
private readonly ImmutableArray<int>.Builder _labels;
private ImmutableArray<ExceptionHandlerInfo>.Builder? _lazyExceptionHandlers;
private readonly List<BranchInfo> _branches;
private readonly List<int> _labels;
private List<ExceptionHandlerInfo>? _lazyExceptionHandlers;
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved

public ControlFlowBuilder()
{
_branches = ImmutableArray.CreateBuilder<BranchInfo>();
_labels = ImmutableArray.CreateBuilder<int>();
_branches = new List<BranchInfo>();
_labels = new List<int>();
}

/// <summary>
Expand All @@ -93,6 +104,7 @@ public void Clear()
_branches.Clear();
_labels.Clear();
_lazyExceptionHandlers?.Clear();
RemainingSwitchBranches = 0;
}

internal LabelHandle AddLabel()
Expand All @@ -101,12 +113,26 @@ internal LabelHandle AddLabel()
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)
Expand Down Expand Up @@ -214,8 +240,9 @@ private void AddExceptionRegion(
ValidateLabel(tryEnd, nameof(tryEnd));
ValidateLabel(handlerStart, nameof(handlerStart));
ValidateLabel(handlerEnd, nameof(handlerEnd));
AssertNotInSwitch();

_lazyExceptionHandlers ??= ImmutableArray.CreateBuilder<ExceptionHandlerInfo>();
_lazyExceptionHandlers ??= new List<ExceptionHandlerInfo>();

_lazyExceptionHandlers.Add(new ExceptionHandlerInfo(kind, tryStart, tryEnd, handlerStart, handlerEnd, filterStart, catchType));
}
Expand All @@ -230,6 +257,25 @@ private void AddExceptionRegion(

internal int ExceptionHandlerCount => _lazyExceptionHandlers?.Count ?? 0;

internal int RemainingSwitchBranches { get; set; }

internal void AssertNotInSwitch()
teo-tsirpanis marked this conversation as resolved.
Show resolved Hide resolved
{
if (RemainingSwitchBranches > 0)
{
Throw.InvalidOperation(SR.SwitchInstructionEncoderTooFewBranches);
}
}

internal void SwitchBranchAdded()
{
if (RemainingSwitchBranches == 0)
{
Throw.InvalidOperation(SR.SwitchInstructionEncoderTooManyBranches);
}
RemainingSwitchBranches--;
}

/// <exception cref="InvalidOperationException" />
internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder)
{
Expand All @@ -252,7 +298,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;
Expand All @@ -264,22 +310,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)
Expand All @@ -291,13 +332,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
{
Expand All @@ -311,8 +355,8 @@ internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBu
break;
}

// skip fake branch instruction:
srcBlobOffset += sizeof(byte) + operandSize;
// skip fake branch operand:
srcBlobOffset += operandSize;
}
}
}
Expand Down
Loading