File: Actions\GrammarActions.Instructions.cs
Web Access
Project: ILAssembler.csproj (ILAssembler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using System.Text;
using Antlr4.Runtime;
using LabelHandle = ILAssembler.MethodBodyWriter.Label;

namespace ILAssembler;

internal sealed partial class GrammarActions
{
    internal void EmitNoOperandInstruction(IToken opcodeToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        instruction.Method.Definition.MethodBody.OpCode(instruction.OpCode);
    }

    internal void EmitVariableIndexInstruction(IToken opcodeToken, IToken indexToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        WriteVariableIndex(instruction.Method, instruction.OpCode, ParseInt32(indexToken));
    }

    internal void EmitVariableNameInstruction(IToken opcodeToken, IToken nameToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        CurrentMethodContext method = instruction.Method;
        ILOpCode opcode = instruction.OpCode;
        string instructionName = opcode.ToString();
        string variableName = ParseIdentifier(nameToken);
        int? index = null;

        if (instructionName.Contains("arg", StringComparison.Ordinal))
        {
            if (method.ArgumentNames.TryGetValue(variableName, out int argumentIndex))
            {
                index = method.Definition.SignatureHeader.IsInstance ? argumentIndex + 1 : argumentIndex;
            }
            else
            {
                ReportError(
                    DiagnosticIds.ArgumentNotFound,
                    string.Format(DiagnosticMessageTemplates.ArgumentNotFound, variableName),
                    opcodeToken);
            }
        }
        else
        {
            for (int i = method.LocalsScopes.Count - 1; i >= 0; i--)
            {
                if (method.LocalsScopes[i].TryGetValue(variableName, out int localIndex))
                {
                    index = localIndex;
                    break;
                }
            }

            if (index is null)
            {
                ReportError(
                    DiagnosticIds.LocalNotFound,
                    string.Format(DiagnosticMessageTemplates.LocalNotFound, variableName),
                    opcodeToken);
            }
        }

        WriteVariableIndex(method, opcode, index ?? -1);
    }

    internal void EmitInt32Instruction(IToken opcodeToken, IToken valueToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        int value = ParseInt32(valueToken);
        if (instruction.OpCode is ILOpCode.Ldc_i4 or ILOpCode.Ldc_i4_s)
        {
            instruction.Method.Definition.MethodBody.LoadConstantI4(instruction.OpCode, value, _options.Optimize);
            return;
        }

        instruction.Method.Definition.MethodBody.OpCode(instruction.OpCode);
        instruction.Method.Definition.MethodBody.CodeBuilder.WriteByte((byte)value);
    }

    internal void EmitInt64Instruction(IToken opcodeToken, IToken valueToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        Debug.Assert(instruction.OpCode == ILOpCode.Ldc_i8);
        instruction.Method.Definition.MethodBody.LoadConstantI8(ParseInt64(valueToken));
    }

    internal void EmitFloatingInstruction(IToken opcodeToken, double value)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        WriteFloatingInstruction(instruction.Method, instruction.OpCode, value);
    }

    internal void EmitFloatingInstruction(IToken opcodeToken, IToken valueToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        WriteFloatingInstruction(instruction.Method, instruction.OpCode, ParseInt64(valueToken));
    }

    internal void EmitBranchOffsetInstruction(IToken opcodeToken, IToken offsetToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        int offset = ParseInt32(offsetToken);
        instruction.Method.Definition.MethodBody.Branch(instruction.OpCode, offset,
            _options.Optimize, Location.From(opcodeToken, _documents));
    }

    internal void EmitBranchLabelInstruction(IToken opcodeToken, IToken labelToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        CurrentMethodContext method = instruction.Method;
        string labelName = ParseIdentifier(labelToken);
        LabelHandle label = GetOrCreateMethodLabel(method, labelName, opcodeToken);

        method.Definition.MethodBody.Branch(instruction.OpCode, label,
            _options.Optimize, Location.From(opcodeToken, _documents));
    }

    internal void EmitRawFloatingInstruction(
        IToken opcodeToken,
        ImmutableArray<byte> bytes,
        IToken bytesToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        double value;
        ReadOnlySpan<byte> byteSpan = bytes.AsSpan();
        if (byteSpan.Length >= sizeof(double))
        {
            value = BitConverter.ToDouble(byteSpan);
        }
        else if (byteSpan.Length >= sizeof(float))
        {
            value = BitConverter.ToSingle(byteSpan);
        }
        else
        {
            ReportError(
                DiagnosticIds.ByteArrayTooShort,
                DiagnosticMessageTemplates.ByteArrayTooShort,
                bytesToken);
            value = 0;
        }

        WriteFloatingInstruction(instruction.Method, instruction.OpCode, value);
    }

    internal void EmitRawStringInstruction(IToken opcodeToken, ImmutableArray<byte> bytes)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        string value = MemoryMarshal.Cast<byte, char>(bytes.AsSpan()).ToString();
        instruction.Method.Definition.MethodBody.LoadString(_metadataBuilder.GetOrAddUserString(value));
    }

    internal void EmitStringInstruction(IToken opcodeToken, string value)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        instruction.Method.Definition.MethodBody.LoadString(_metadataBuilder.GetOrAddUserString(value));
    }

    internal void EmitAnsiStringInstruction(IToken opcodeToken, string value)
    {
        int byteCount = Encoding.UTF8.GetByteCount(value);
        if ((byteCount % 2) != 0)
        {
            byteCount++;
        }

        Span<byte> utf8Bytes = new byte[byteCount];
        Encoding.UTF8.GetBytes(value, utf8Bytes);
        EmitStringInstruction(opcodeToken, new string(MemoryMarshal.Cast<byte, char>(utf8Bytes)));
    }

    internal void EmitRawTokenInstruction(IToken opcodeToken, IToken valueToken)
    {
        if (StartInstruction(opcodeToken) is not { } instruction)
        {
            return;
        }

        instruction.Method.Definition.MethodBody.OpCode(instruction.OpCode);
        instruction.Method.Definition.MethodBody.CodeBuilder.WriteInt32(ParseInt32(valueToken));
    }

    internal void EmitByte(IToken valueToken)
    {
        _currentMethod?.Definition.MethodBody.CodeBuilder.WriteByte((byte)ParseInt32(valueToken));
    }

    internal void SetMaxStack(IToken valueToken)
    {
        if (_currentMethod is not null)
        {
            _currentMethod.Definition.MaxStack = ParseInt32(valueToken);
        }
    }

    internal void SetEntryPoint()
    {
        if (_currentMethod is not null)
        {
            _entityRegistry.EntryPoint = _currentMethod.Definition;
        }
    }

    internal void SetZeroInit()
    {
        if (_currentMethod is not null)
        {
            _currentMethod.Definition.BodyAttributes = MethodBodyAttributes.InitLocals;
        }
    }

    internal void DefineLabel(IToken nameToken)
    {
        if (_currentMethod is not { } method)
        {
            return;
        }

        string labelName = ParseIdentifier(nameToken);
        method.UndefinedLabelReferences.Remove(labelName);
        if (!method.Labels.TryGetValue(labelName, out LabelHandle label))
        {
            label = method.Definition.MethodBody.DefineLabel();
            method.Labels[labelName] = label;
        }

        method.Definition.MethodBody.MarkLabel(label);
    }

#pragma warning disable CA1822 // Parser actions are invoked through the per-parser GrammarActions instance.
    internal CILParser.SwitchInstructionBuilder CreateSwitchInstruction(IToken opcodeToken)
        => new(opcodeToken);

    internal void AddSwitchLabel(CILParser.SwitchInstructionBuilder builder, IToken labelToken)
        => builder.Operands.Add((labelToken, false));

    internal void AddSwitchOffset(CILParser.SwitchInstructionBuilder builder, IToken offsetToken)
        => builder.Operands.Add((offsetToken, true));
#pragma warning restore CA1822

    internal void CompleteSwitchInstruction(CILParser.SwitchInstructionBuilder? builder)
    {
        if (builder is null ||
            StartInstruction(builder.OpcodeToken) is not { } instruction)
        {
            return;
        }

        Debug.Assert(instruction.OpCode == ILOpCode.Switch);
        CurrentMethodContext method = instruction.Method;
        List<(LabelHandle Label, int? Offset)> labels = new(builder.Operands.Count);
        foreach ((IToken token, bool isOffset) in builder.Operands)
        {
            if (isOffset)
            {
                labels.Add((default, ParseInt32(token)));
                continue;
            }

            string labelName = ParseIdentifier(token);
            LabelHandle label = GetOrCreateMethodLabel(
                method,
                labelName,
                builder.OpcodeToken);

            labels.Add((label, null));
        }

        method.Definition.MethodBody.Switch(labels, Location.From(builder.OpcodeToken, _documents));
    }

    private (CurrentMethodContext Method, ILOpCode OpCode)? StartInstruction(IToken opcodeToken)
    {
        if (_currentMethod is not { } method)
        {
            return null;
        }

        ILOpCode opcode = ParseOpCodeFromToken(opcodeToken);
        return (method, opcode);
    }

    private void WriteVariableIndex(CurrentMethodContext method, ILOpCode opcode, int index)
        => method.Definition.MethodBody.Variable(opcode, index, _options.Optimize);

    private static void WriteFloatingInstruction(CurrentMethodContext method, ILOpCode opcode, double value)
    {
        if (opcode == ILOpCode.Ldc_r4)
        {
            method.Definition.MethodBody.LoadConstantR4((float)value);
        }
        else
        {
            method.Definition.MethodBody.LoadConstantR8(value);
        }
    }

    private static ILOpCode ParseOpCodeFromToken(IToken token)
    {
        string text = token.Text.TrimEnd('.');
        if (text == "unused")
        {
            return ILOpCode.Unused;
        }

        string normalized = text.Replace('.', '_');
        normalized = normalized switch
        {
            "ldelem_u8" => "ldelem_i8",
            "ldind_u8" => "ldind_i8",
            "endfault" => "endfinally",
            _ => normalized
        };

        return (ILOpCode)Enum.Parse(typeof(ILOpCode), normalized, ignoreCase: true);
    }

    internal EntityRegistry.EntityBase ResolveMetadataToken(CILParser.MdtokenContext context)
        => ResolveMetadataToken(context.Value);
}