File: System\Reflection\Metadata\Ecma335\Encoding\InstructionEncoder.cs
Web Access
Project: src\src\libraries\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj (System.Reflection.Metadata)
// 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
{
    /// <summary>
    /// Encodes instructions.
    /// </summary>
    public readonly struct InstructionEncoder
    {
        /// <summary>
        /// Underlying builder where encoded instructions are written to.
        /// </summary>
        public BlobBuilder CodeBuilder { get; }
 
        /// <summary>
        /// Builder tracking labels, branches and exception handlers.
        /// </summary>
        /// <remarks>
        /// If null the encoder doesn't support construction of control flow.
        /// </remarks>
        public ControlFlowBuilder? ControlFlowBuilder { get; }
 
        /// <summary>
        /// Creates an encoder backed by code and control-flow builders.
        /// </summary>
        /// <param name="codeBuilder">Builder to write encoded instructions to.</param>
        /// <param name="controlFlowBuilder">
        /// Builder tracking labels, branches and exception handlers.
        /// Must be specified to be able to use some of the control-flow factory methods of <see cref="InstructionEncoder"/>,
        /// such as <see cref="Branch(ILOpCode, LabelHandle)"/>, <see cref="DefineLabel"/>, <see cref="MarkLabel(LabelHandle)"/> etc.
        /// </param>
        public InstructionEncoder(BlobBuilder codeBuilder, ControlFlowBuilder? controlFlowBuilder = null)
        {
            if (codeBuilder == null)
            {
                Throw.BuilderArgumentNull();
            }
 
            CodeBuilder = codeBuilder;
            ControlFlowBuilder = controlFlowBuilder;
        }
 
        /// <summary>
        /// Offset of the next encoded instruction.
        /// </summary>
        public int Offset => CodeBuilder.Count;
 
        /// <summary>
        /// Encodes specified op-code.
        /// </summary>
        public void OpCode(ILOpCode code)
        {
            ControlFlowBuilder?.ValidateNotInSwitch();
            if (unchecked((byte)code) == (ushort)code)
            {
                CodeBuilder.WriteByte((byte)code);
            }
            else
            {
                // IL opcodes that occupy two bytes are written to
                // the byte stream with the high-order byte first,
                // in contrast to the little-endian format of the
                // numeric arguments and tokens.
                CodeBuilder.WriteUInt16BE((ushort)code);
            }
        }
 
        /// <summary>
        /// Encodes a token.
        /// </summary>
        public void Token(EntityHandle handle)
        {
            Token(MetadataTokens.GetToken(handle));
        }
 
        /// <summary>
        /// Encodes a token.
        /// </summary>
        public void Token(int token)
        {
            ControlFlowBuilder?.ValidateNotInSwitch();
            CodeBuilder.WriteInt32(token);
        }
 
        /// <summary>
        /// Encodes <code>ldstr</code> instruction and its operand.
        /// </summary>
        public void LoadString(UserStringHandle handle)
        {
            OpCode(ILOpCode.Ldstr);
            Token(MetadataTokens.GetToken(handle));
        }
 
        /// <summary>
        /// Encodes <code>call</code> instruction and its operand.
        /// </summary>
        public void Call(EntityHandle methodHandle)
        {
            if (methodHandle.Kind != HandleKind.MethodDefinition &&
                methodHandle.Kind != HandleKind.MethodSpecification &&
                methodHandle.Kind != HandleKind.MemberReference)
            {
                Throw.InvalidArgument_Handle(nameof(methodHandle));
            }
 
            OpCode(ILOpCode.Call);
            Token(methodHandle);
        }
 
        /// <summary>
        /// Encodes <code>call</code> instruction and its operand.
        /// </summary>
        public void Call(MethodDefinitionHandle methodHandle)
        {
            OpCode(ILOpCode.Call);
            Token(methodHandle);
        }
 
        /// <summary>
        /// Encodes <code>call</code> instruction and its operand.
        /// </summary>
        public void Call(MethodSpecificationHandle methodHandle)
        {
            OpCode(ILOpCode.Call);
            Token(methodHandle);
        }
 
        /// <summary>
        /// Encodes <code>call</code> instruction and its operand.
        /// </summary>
        public void Call(MemberReferenceHandle methodHandle)
        {
            OpCode(ILOpCode.Call);
            Token(methodHandle);
        }
 
        /// <summary>
        /// Encodes <code>calli</code> instruction and its operand.
        /// </summary>
        public void CallIndirect(StandaloneSignatureHandle signature)
        {
            OpCode(ILOpCode.Calli);
            Token(signature);
        }
 
        /// <summary>
        /// Encodes <see cref="int"/> constant load instruction.
        /// </summary>
        public void LoadConstantI4(int value)
        {
            ILOpCode code;
            switch (value)
            {
                case -1: code = ILOpCode.Ldc_i4_m1; break;
                case 0: code = ILOpCode.Ldc_i4_0; break;
                case 1: code = ILOpCode.Ldc_i4_1; break;
                case 2: code = ILOpCode.Ldc_i4_2; break;
                case 3: code = ILOpCode.Ldc_i4_3; break;
                case 4: code = ILOpCode.Ldc_i4_4; break;
                case 5: code = ILOpCode.Ldc_i4_5; break;
                case 6: code = ILOpCode.Ldc_i4_6; break;
                case 7: code = ILOpCode.Ldc_i4_7; break;
                case 8: code = ILOpCode.Ldc_i4_8; break;
 
                default:
                    if (unchecked((sbyte)value == value))
                    {
                        OpCode(ILOpCode.Ldc_i4_s);
                        CodeBuilder.WriteSByte((sbyte)value);
                    }
                    else
                    {
                        OpCode(ILOpCode.Ldc_i4);
                        CodeBuilder.WriteInt32(value);
                    }
 
                    return;
            }
 
            OpCode(code);
        }
 
        /// <summary>
        /// Encodes <see cref="long"/> constant load instruction.
        /// </summary>
        public void LoadConstantI8(long value)
        {
            OpCode(ILOpCode.Ldc_i8);
            CodeBuilder.WriteInt64(value);
        }
 
        /// <summary>
        /// Encodes <see cref="float"/> constant load instruction.
        /// </summary>
        public void LoadConstantR4(float value)
        {
            OpCode(ILOpCode.Ldc_r4);
            CodeBuilder.WriteSingle(value);
        }
 
        /// <summary>
        /// Encodes <see cref="double"/> constant load instruction.
        /// </summary>
        public void LoadConstantR8(double value)
        {
            OpCode(ILOpCode.Ldc_r8);
            CodeBuilder.WriteDouble(value);
        }
 
        /// <summary>
        /// Encodes local variable load instruction.
        /// </summary>
        /// <param name="slotIndex">Index of the local variable slot.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slotIndex"/> is negative.</exception>
        public void LoadLocal(int slotIndex)
        {
            switch (slotIndex)
            {
                case 0: OpCode(ILOpCode.Ldloc_0); break;
                case 1: OpCode(ILOpCode.Ldloc_1); break;
                case 2: OpCode(ILOpCode.Ldloc_2); break;
                case 3: OpCode(ILOpCode.Ldloc_3); break;
 
                default:
                    if (unchecked((uint)slotIndex) <= byte.MaxValue)
                    {
                        OpCode(ILOpCode.Ldloc_s);
                        CodeBuilder.WriteByte((byte)slotIndex);
                    }
                    else if (slotIndex > 0)
                    {
                        OpCode(ILOpCode.Ldloc);
                        CodeBuilder.WriteInt32(slotIndex);
                    }
                    else
                    {
                        Throw.ArgumentOutOfRange(nameof(slotIndex));
                    }
 
                    break;
            }
        }
 
        /// <summary>
        /// Encodes local variable store instruction.
        /// </summary>
        /// <param name="slotIndex">Index of the local variable slot.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slotIndex"/> is negative.</exception>
        public void StoreLocal(int slotIndex)
        {
            switch (slotIndex)
            {
                case 0: OpCode(ILOpCode.Stloc_0); break;
                case 1: OpCode(ILOpCode.Stloc_1); break;
                case 2: OpCode(ILOpCode.Stloc_2); break;
                case 3: OpCode(ILOpCode.Stloc_3); break;
 
                default:
                    if (unchecked((uint)slotIndex) <= byte.MaxValue)
                    {
                        OpCode(ILOpCode.Stloc_s);
                        CodeBuilder.WriteByte((byte)slotIndex);
                    }
                    else if (slotIndex > 0)
                    {
                        OpCode(ILOpCode.Stloc);
                        CodeBuilder.WriteInt32(slotIndex);
                    }
                    else
                    {
                        Throw.ArgumentOutOfRange(nameof(slotIndex));
                    }
 
                    break;
            }
        }
 
        /// <summary>
        /// Encodes local variable address load instruction.
        /// </summary>
        /// <param name="slotIndex">Index of the local variable slot.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="slotIndex"/> is negative.</exception>
        public void LoadLocalAddress(int slotIndex)
        {
            if (unchecked((uint)slotIndex) <= byte.MaxValue)
            {
                OpCode(ILOpCode.Ldloca_s);
                CodeBuilder.WriteByte((byte)slotIndex);
            }
            else if (slotIndex > 0)
            {
                OpCode(ILOpCode.Ldloca);
                CodeBuilder.WriteInt32(slotIndex);
            }
            else
            {
                Throw.ArgumentOutOfRange(nameof(slotIndex));
            }
        }
 
        /// <summary>
        /// Encodes argument load instruction.
        /// </summary>
        /// <param name="argumentIndex">Index of the argument.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="argumentIndex"/> is negative.</exception>
        public void LoadArgument(int argumentIndex)
        {
            switch (argumentIndex)
            {
                case 0: OpCode(ILOpCode.Ldarg_0); break;
                case 1: OpCode(ILOpCode.Ldarg_1); break;
                case 2: OpCode(ILOpCode.Ldarg_2); break;
                case 3: OpCode(ILOpCode.Ldarg_3); break;
 
                default:
                    if (unchecked((uint)argumentIndex) <= byte.MaxValue)
                    {
                        OpCode(ILOpCode.Ldarg_s);
                        CodeBuilder.WriteByte((byte)argumentIndex);
                    }
                    else if (argumentIndex > 0)
                    {
                        OpCode(ILOpCode.Ldarg);
                        CodeBuilder.WriteInt32(argumentIndex);
                    }
                    else
                    {
                        Throw.ArgumentOutOfRange(nameof(argumentIndex));
                    }
 
                    break;
            }
        }
 
        /// <summary>
        /// Encodes argument address load instruction.
        /// </summary>
        /// <param name="argumentIndex">Index of the argument.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="argumentIndex"/> is negative.</exception>
        public void LoadArgumentAddress(int argumentIndex)
        {
            if (unchecked((uint)argumentIndex) <= byte.MaxValue)
            {
                OpCode(ILOpCode.Ldarga_s);
                CodeBuilder.WriteByte((byte)argumentIndex);
            }
            else if (argumentIndex > 0)
            {
                OpCode(ILOpCode.Ldarga);
                CodeBuilder.WriteInt32(argumentIndex);
            }
            else
            {
                Throw.ArgumentOutOfRange(nameof(argumentIndex));
            }
        }
 
        /// <summary>
        /// Encodes argument store instruction.
        /// </summary>
        /// <param name="argumentIndex">Index of the argument.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="argumentIndex"/> is negative.</exception>
        public void StoreArgument(int argumentIndex)
        {
            if (unchecked((uint)argumentIndex) <= byte.MaxValue)
            {
                OpCode(ILOpCode.Starg_s);
                CodeBuilder.WriteByte((byte)argumentIndex);
            }
            else if (argumentIndex > 0)
            {
                OpCode(ILOpCode.Starg);
                CodeBuilder.WriteInt32(argumentIndex);
            }
            else
            {
                Throw.ArgumentOutOfRange(nameof(argumentIndex));
            }
        }
 
        /// <summary>
        /// Defines a label that can later be used to mark and refer to a location in the instruction stream.
        /// </summary>
        /// <returns>Label handle.</returns>
        /// <exception cref="InvalidOperationException"><see cref="ControlFlowBuilder"/> is null.</exception>
        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);
            }
        }
 
        /// <summary>
        /// Encodes a branch instruction.
        /// </summary>
        /// <param name="code">Branch instruction to encode.</param>
        /// <param name="label">Label of the target location in instruction stream.</param>
        /// <exception cref="ArgumentException"><paramref name="code"/> is not a branch instruction.</exception>
        /// <exception cref="ArgumentException"><paramref name="label"/> was not defined by this encoder.</exception>
        /// <exception cref="InvalidOperationException"><see cref="ControlFlowBuilder"/> is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="label"/> has default value.</exception>
        public void Branch(ILOpCode code, LabelHandle label)
        {
            // throws if code is not a branch:
            int operandSize = code.GetBranchOperandSize();
            // We want the offset before we add the opcode.
            int ilOffset = Offset;
 
            OpCode(code);
            LabelOperand(code, label, operandSize, ilOffset);
        }
 
        /// <summary>
        /// Starts encoding a switch instruction.
        /// </summary>
        /// <param name="branchCount">The number of branches the instruction will have.</param>
        /// <returns>A <see cref="SwitchInstructionEncoder"/> that will
        /// be used to emit the labels for the branches.</returns>
        /// <remarks>
        /// Before using this <see cref="InstructionEncoder"/> in any other way,
        /// the method <see cref="SwitchInstructionEncoder.Branch(LabelHandle)"/>
        /// must be called on the returned value exactly <paramref name="branchCount"/>
        /// times. Failure to do so will throw <see cref="InvalidOperationException"/>.
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="branchCount"/>
        /// less than or equal to zero.</exception>
        public SwitchInstructionEncoder Switch(int branchCount)
        {
            if (branchCount <= 0)
            {
                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);
        }
 
        /// <summary>
        /// Associates specified label with the current IL offset.
        /// </summary>
        /// <param name="label">Label to mark.</param>
        /// <remarks>
        /// A single label may be marked multiple times, the last offset wins.
        /// </remarks>
        /// <exception cref="InvalidOperationException"><see cref="ControlFlowBuilder"/> is null.</exception>
        /// <exception cref="ArgumentException"><paramref name="label"/> was not defined by this encoder.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="label"/> has default value.</exception>
        public void MarkLabel(LabelHandle label)
        {
            GetBranchBuilder().MarkLabel(Offset, label);
        }
 
        private ControlFlowBuilder GetBranchBuilder()
        {
            if (ControlFlowBuilder == null)
            {
                Throw.ControlFlowBuilderNotAvailable();
            }
 
            return ControlFlowBuilder;
        }
    }
}