1597 lines
62 KiB
C#
1597 lines
62 KiB
C#
#if UNITY_EDITOR || BURST_INTERNAL
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
using Debug = UnityEngine.Debug;
|
||
|
||
namespace Unity.Burst.Editor
|
||
{
|
||
/// <summary>
|
||
/// Disassembler for Intel and ARM
|
||
/// </summary>
|
||
internal partial class BurstDisassembler
|
||
{
|
||
// The following member need to be reset/clear on each Reset()
|
||
private readonly Dictionary<int, string> _fileName;
|
||
private readonly Dictionary<int, string[]> _fileList;
|
||
private readonly List<AsmToken> _tokens;
|
||
private readonly List<AsmBlock> _blocks;
|
||
private readonly List<string> _blockToString;
|
||
private readonly List<int> _columnIndices;
|
||
private readonly List<AsmLine> _lines;
|
||
internal UsedRegisters _registersUsedAtLine;
|
||
private readonly DictionaryGlobalLabel _globalLabels;
|
||
private readonly List<TempLabelRef> _tempLabelRefs;
|
||
private readonly Dictionary<int, StringSlice> _mapBlockIndexToGlobalLabel;
|
||
private DictionaryLocalLabel _currentDictLocalLabel;
|
||
public bool IsInitialized { get; private set; }
|
||
|
||
// ^^^
|
||
private string _input;
|
||
private AsmKind _inputAsmKind;
|
||
internal readonly StringBuilder _output;
|
||
private bool _colored;
|
||
|
||
// This is used to aligned instructions and there operands so they look like this
|
||
//
|
||
// mulps x,x,x
|
||
// shufbps x,x,x
|
||
//
|
||
// instead of
|
||
//
|
||
// mulps x,x,x
|
||
// shufbps x,x,x
|
||
//
|
||
// Notice if instruction name is longer than this no alignment will be done.
|
||
private const int InstructionAlignment = 10;
|
||
|
||
private static readonly StringSlice CVLocDirective = new StringSlice(".cv_loc");
|
||
|
||
// Colors used for the tokens
|
||
// TODO: Make this configurable via some editor settings?
|
||
private const string DarkColorLineDirective = "#FFFF00";
|
||
private const string DarkColorDirective = "#CCCCCC";
|
||
private const string DarkColorIdentifier = "#d4d4d4";
|
||
private const string DarkColorQualifier = "#DCDCAA";
|
||
private const string DarkColorInstruction = "#4EC9B0";
|
||
internal const string DarkColorInstructionSIMD = "#C586C0";
|
||
internal const string DarkColorInstructionSIMDPacked = "#A586C0";
|
||
internal const string DarkColorInstructionSIMDScalar = "#E586C0";
|
||
private const string DarkColorRegister = "#d7ba7d";
|
||
private const string DarkColorNumber = "#9cdcfe";
|
||
private const string DarkColorString = "#ce9178";
|
||
private const string DarkColorComment = "#6A9955";
|
||
|
||
private const string LightColorLineDirective = "#888800";
|
||
private const string LightColorDirective = "#444444";
|
||
private const string LightColorIdentifier = "#1c1c1c";
|
||
private const string LightColorQualifier = "#267f99";
|
||
private const string LightColorInstruction = "#0451a5";
|
||
private const string LightColorInstructionSIMD = "#0000ff";
|
||
private const string LightColorInstructionSIMDPacked = "#8000ff";
|
||
private const string LightColorInstructionSIMDScalar = "#8050ff";
|
||
private const string LightColorRegister = "#811f3f";
|
||
private const string LightColorNumber = "#007ACC";
|
||
private const string LightColorString = "#a31515";
|
||
private const string LightColorComment = "#008000";
|
||
|
||
private string ColorLineDirective;
|
||
private string ColorDirective;
|
||
private string ColorIdentifier;
|
||
private string ColorQualifier;
|
||
private string ColorInstruction;
|
||
private string ColorInstructionSIMD;
|
||
private string ColorInstructionSIMDPacked;
|
||
private string ColorInstructionSIMDScalar;
|
||
private string ColorRegister;
|
||
private string ColorNumber;
|
||
private string ColorString;
|
||
private string ColorComment;
|
||
|
||
private char _commentStart;
|
||
|
||
public BurstDisassembler()
|
||
{
|
||
_fileName = new Dictionary<int, string>();
|
||
_fileList = new Dictionary<int, string[]>();
|
||
_tokens = new List<AsmToken>(65536);
|
||
_blocks = new List<AsmBlock>(128);
|
||
_blockToString = new List<string>(128);
|
||
_columnIndices = new List<int>(65536);
|
||
_lines = new List<AsmLine>(4096);
|
||
_registersUsedAtLine = new UsedRegisters(4096);
|
||
_tempLabelRefs = new List<TempLabelRef>(4096);
|
||
_globalLabels = new DictionaryGlobalLabel(128);
|
||
_mapBlockIndexToGlobalLabel = new Dictionary<int, StringSlice>(128);
|
||
_output = new StringBuilder();
|
||
}
|
||
|
||
internal List<int> ColumnIndices => _columnIndices;
|
||
|
||
/// <summary>
|
||
/// Gets all the blocks.
|
||
/// </summary>
|
||
public List<AsmBlock> Blocks => _blocks;
|
||
|
||
/// <summary>
|
||
/// Gets whether the disassembly is colored.
|
||
/// </summary>
|
||
public bool IsColored => _colored;
|
||
|
||
/// <summary>
|
||
/// Gets all the lines for all the blocks.
|
||
/// </summary>
|
||
public List<AsmLine> Lines => _lines;
|
||
|
||
/// <summary>
|
||
/// Gets all the tokens
|
||
/// </summary>
|
||
public List<AsmToken> Tokens => _tokens;
|
||
|
||
public int LineUsedReg(int lineIdx, string reg) => _registersUsedAtLine.RegisterMatch(lineIdx, reg);
|
||
public bool LineUsesRegs(int lineIdx, out List<string> usedRegs) => _registersUsedAtLine.LineContainsRegs(lineIdx, out usedRegs);
|
||
public List<string> CleanRegs(List<string> regs) => _registersUsedAtLine.CleanRegs(regs);
|
||
|
||
public int GetRegisterTokenIndex(AsmLine line, string reg, int startIndex = 0)
|
||
{
|
||
var idx = -1;
|
||
|
||
var i = Math.Max(line.TokenIndex, startIndex);
|
||
var len = line.TokenIndex + line.Length;
|
||
for (; i < len; i++)
|
||
{
|
||
var token = Tokens[i];
|
||
if (_registersUsedAtLine.RegisterEquality(reg, GetTokenAsText(token)))
|
||
{
|
||
idx = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return idx;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get a token index for a particular block, line number and column number.
|
||
/// </summary>
|
||
/// <param name="blockIndex"></param>
|
||
/// <param name="line"></param>
|
||
/// <param name="column"></param>
|
||
/// <param name="lineIndex">Returns the line index to query <see cref="Lines"/></param>
|
||
/// <returns>The token index to use with <see cref="GetToken"/> or -1 if the line, column was not found.</returns>
|
||
public int GetTokenIndexFromColumn(int blockIndex, int line, int column, out int lineIndex)
|
||
{
|
||
lineIndex = -1;
|
||
var block = _blocks[blockIndex];
|
||
var lineStartIndex = block.LineIndex + line;
|
||
var asmLine = _lines[lineStartIndex];
|
||
if (asmLine.Kind != AsmLineKind.SourceFileLocation)
|
||
{
|
||
var columnIndex = asmLine.ColumnIndex;
|
||
for (int j = 1; j < asmLine.Length; j++)
|
||
{
|
||
// _columnIndices doesn't have an index for the first token (because the column is always 0)
|
||
var tokenColumn = _columnIndices[columnIndex + j - 1];
|
||
var token = GetToken(asmLine.TokenIndex + j);
|
||
|
||
if (tokenColumn <= column && column < tokenColumn + token.Length)
|
||
{
|
||
lineIndex = lineStartIndex;
|
||
return asmLine.TokenIndex + j;
|
||
}
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or renders a particular block to text without caching the result.
|
||
/// </summary>
|
||
/// <param name="blockIndex">The block to render.</param>
|
||
/// <param name="colored">Whether output should be colored.</param>
|
||
/// <returns>A string representation of the block.</returns>
|
||
public string GetOrRenderBlockToTextUncached(int blockIndex, bool colored)
|
||
{
|
||
return RenderBlock(blockIndex, colored);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or renders a particular block to text (colored if specified at <see cref="Initialize"/> time)
|
||
/// </summary>
|
||
/// <param name="blockIndex">The block to render.</param>
|
||
/// <returns>A string representation of the block.</returns>
|
||
public string GetOrRenderBlockToText(int blockIndex)
|
||
{
|
||
var str = _blockToString[blockIndex];
|
||
if (str == null)
|
||
{
|
||
str = RenderBlock(blockIndex, _colored);
|
||
_blockToString[blockIndex] = str;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a token at the specified token index.
|
||
/// </summary>
|
||
/// <param name="tokenIndex">The token index</param>
|
||
/// <returns>The token available at the specified index</returns>
|
||
public AsmToken GetToken(int tokenIndex)
|
||
{
|
||
return _tokens[tokenIndex];
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns the text representation of the token at the specified index
|
||
/// </summary>
|
||
/// <param name="tokenIndex"></param>
|
||
/// <returns></returns>
|
||
public StringSlice GetTokenAsTextSlice(int tokenIndex)
|
||
{
|
||
return _tokens[tokenIndex].Slice(_input);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns the text representation of the specified token.
|
||
/// </summary>
|
||
public StringSlice GetTokenAsTextSlice(AsmToken token)
|
||
{
|
||
return token.Slice(_input);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns the text representation of the specified token.
|
||
/// </summary>
|
||
public string GetTokenAsText(AsmToken token)
|
||
{
|
||
return token.ToString(_input);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Try and get description of <see cref="instruction"/>.
|
||
/// </summary>
|
||
/// <param name="instruction">Instruction to query information about.</param>
|
||
/// <param name="info">If instruction present the queried information, else default string.</param>
|
||
/// <returns>Whether instruction was present in burst disassembler core.</returns>
|
||
internal bool GetInstructionInformation(string instruction, out string info)
|
||
{
|
||
switch (_inputAsmKind)
|
||
{
|
||
case AsmKind.Intel:
|
||
return X86AsmInstructionInfo.GetX86InstructionInfo(instruction, out info);
|
||
case AsmKind.ARM:
|
||
return ARM64InstructionInfo.GetARM64Info(instruction, out info);
|
||
case AsmKind.LLVMIR:
|
||
return LLVMIRInstructionInfo.GetLLVMIRInfo(instruction, out info);
|
||
case AsmKind.Wasm:
|
||
return WasmInstructionInfo.GetWasmInfo(instruction, out info);
|
||
default:
|
||
throw new InvalidOperationException($"No instruction information for {_inputAsmKind}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Initialize the disassembler with the input and parametesr.
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <param name="asmKind"></param>
|
||
/// <param name="useDarkSkin"></param>
|
||
/// <param name="useSyntaxColoring"></param>
|
||
/// <param name="smellTest"></param>
|
||
/// <returns></returns>
|
||
public bool Initialize(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColoring = true, bool smellTest = false)
|
||
{
|
||
try
|
||
{
|
||
InitializeImpl(input, asmKind, useDarkSkin, useSyntaxColoring, smellTest);
|
||
IsInitialized = true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Reset();
|
||
#if BURST_INTERNAL
|
||
throw new InvalidOperationException($"Error while trying to disassemble the input: {ex}");
|
||
#else
|
||
UnityEngine.Debug.Log($"Error while trying to disassemble the input: {ex}");
|
||
#endif
|
||
}
|
||
|
||
return IsInitialized;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Helper method to output the full (colored) text as we did before.
|
||
///
|
||
/// This method will be deprecated. Just here for testing during the transition.
|
||
/// </summary>
|
||
public string RenderFullText()
|
||
{
|
||
// If not initialized correctly (disassembly failed), return the input string as-is
|
||
if (!IsInitialized) return _input ?? string.Empty;
|
||
|
||
var builder = new StringBuilder();
|
||
for (int i = 0; i < _blocks.Count; i++)
|
||
{
|
||
var text = GetOrRenderBlockToText(i);
|
||
builder.Append(text);
|
||
}
|
||
return builder.ToString();
|
||
}
|
||
|
||
private void Reset()
|
||
{
|
||
_registersUsedAtLine.Clear();
|
||
_fileList.Clear();
|
||
_fileName.Clear();
|
||
_tokens.Clear();
|
||
_blocks.Clear();
|
||
_blockTextIdxs.Clear();
|
||
_blockToString.Clear();
|
||
_columnIndices.Clear();
|
||
_lines.Clear();
|
||
_tempLabelRefs.Clear();
|
||
_globalLabels.Clear();
|
||
_mapBlockIndexToGlobalLabel.Clear();
|
||
_currentDictLocalLabel = null;
|
||
IsInitialized = false;
|
||
}
|
||
|
||
private AsmTokenKindProvider _tokenProvider = null;
|
||
|
||
private void InitializeImpl(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColoring = true, bool smellTest=false)
|
||
{
|
||
_commentStart = (asmKind == AsmKind.Intel || asmKind == AsmKind.Wasm) ? '#' : ';';
|
||
UseSkin(useDarkSkin, smellTest);
|
||
_colored = useSyntaxColoring;
|
||
_tokenProvider = InitializeInput(input, asmKind);
|
||
_registersUsedAtLine.AddTokenProvider(_tokenProvider);
|
||
ParseAndProcessTokens(_tokenProvider);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Finds the block index encapsulating <see cref="textIdx"/>.
|
||
/// </summary>
|
||
/// <param name="textIdx">Text index relative to <see cref="_input"/>.</param>
|
||
/// <param name="start">Left-most block index to search within.</param>
|
||
/// <returns>(block index, blocks start index in <see cref="_input"/>)</returns>
|
||
public (int idx, int l) GetBlockIdxFromTextIdx(int textIdx)
|
||
{
|
||
return GetBlockIdxFromTextIdx(textIdx, 0);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Finds the block index encapsulating <see cref="textIdx"/>.
|
||
/// </summary>
|
||
/// <param name="textIdx">Text index relative to <see cref="_input"/>.</param>
|
||
/// <param name="start">Left-most block index to search within.</param>
|
||
/// <returns>(block index, blocks start index in <see cref="_input"/>)</returns>
|
||
public (int idx, int l) GetBlockIdxFromTextIdx(int textIdx, int start)
|
||
{
|
||
int end = _blockTextIdxs.Count-1;
|
||
while (start <= end)
|
||
{
|
||
int mid = (end + start) / 2;
|
||
var (startIdx, endIdx) = _blockTextIdxs[mid];
|
||
|
||
if (startIdx <= textIdx && textIdx <= endIdx)
|
||
{
|
||
return (mid, startIdx);
|
||
}
|
||
|
||
if (endIdx < textIdx)
|
||
{
|
||
start = mid + 1;
|
||
}
|
||
else
|
||
{
|
||
end = mid - 1;
|
||
}
|
||
}
|
||
return (-1, -1);
|
||
}
|
||
|
||
private bool _smellTest;
|
||
private void UseSkin(bool useDarkSkin, bool smellTest)
|
||
{
|
||
_smellTest = smellTest;
|
||
if (useDarkSkin)
|
||
{
|
||
ColorLineDirective = DarkColorLineDirective;
|
||
ColorDirective = DarkColorDirective;
|
||
ColorIdentifier = DarkColorIdentifier;
|
||
ColorQualifier = DarkColorQualifier;
|
||
ColorInstruction = DarkColorInstruction;
|
||
ColorInstructionSIMD = DarkColorInstructionSIMD;
|
||
ColorInstructionSIMDPacked = DarkColorInstructionSIMDPacked;
|
||
ColorInstructionSIMDScalar = DarkColorInstructionSIMDScalar;
|
||
ColorRegister = DarkColorRegister;
|
||
ColorNumber = DarkColorNumber;
|
||
ColorString = DarkColorString;
|
||
ColorComment = DarkColorComment;
|
||
}
|
||
else
|
||
{
|
||
ColorLineDirective = LightColorLineDirective;
|
||
ColorDirective = LightColorDirective;
|
||
ColorIdentifier = LightColorIdentifier;
|
||
ColorQualifier = LightColorQualifier;
|
||
ColorInstruction = LightColorInstruction;
|
||
ColorInstructionSIMD = LightColorInstructionSIMD;
|
||
ColorInstructionSIMDPacked = LightColorInstructionSIMDPacked;
|
||
ColorInstructionSIMDScalar = LightColorInstructionSIMDScalar;
|
||
ColorRegister = LightColorRegister;
|
||
ColorNumber = LightColorNumber;
|
||
ColorString = LightColorString;
|
||
ColorComment = LightColorComment;
|
||
}
|
||
}
|
||
|
||
private int AlignInstruction(StringBuilder output, int instructionLength, AsmKind asmKind)
|
||
{
|
||
// Only support Intel for now
|
||
if (instructionLength >= InstructionAlignment || asmKind != AsmKind.Intel)
|
||
return 0;
|
||
|
||
int align = InstructionAlignment - instructionLength;
|
||
output.Append(' ', align);
|
||
return align;
|
||
}
|
||
|
||
private AsmTokenKindProvider InitializeInput(string input, AsmKind asmKind)
|
||
{
|
||
AsmTokenKindProvider asmTokenProvider = null;
|
||
|
||
_input = input;
|
||
_inputAsmKind = asmKind;
|
||
|
||
switch (asmKind)
|
||
{
|
||
case AsmKind.Intel:
|
||
asmTokenProvider = (AsmTokenKindProvider)X86AsmTokenKindProvider.Instance;
|
||
break;
|
||
case AsmKind.ARM:
|
||
asmTokenProvider = (AsmTokenKindProvider)ARM64AsmTokenKindProvider.Instance;
|
||
break;
|
||
case AsmKind.Wasm:
|
||
asmTokenProvider = (AsmTokenKindProvider)WasmAsmTokenKindProvider.Instance;
|
||
break;
|
||
case AsmKind.LLVMIR:
|
||
asmTokenProvider = (AsmTokenKindProvider)LLVMIRAsmTokenKindProvider.Instance;
|
||
break;
|
||
default:
|
||
throw new InvalidOperationException($"No {nameof(AsmTokenKindProvider)} for {asmKind}");
|
||
}
|
||
|
||
return asmTokenProvider;
|
||
}
|
||
|
||
private int GetLineLen(in AsmLine line)
|
||
{
|
||
int len = 0;
|
||
int offset = line.TokenIndex;
|
||
int numLineTokens = line.Length;
|
||
for (int i = 0; i < numLineTokens; i++)
|
||
{
|
||
AsmToken token = _tokens[offset + i];
|
||
len += token.Kind != AsmTokenKind.NewLine
|
||
? token.Length
|
||
: 1; // We don't use windows line endings, but internal token might,
|
||
}
|
||
|
||
return len;
|
||
}
|
||
|
||
private void ParseAndProcessTokens(AsmTokenKindProvider asmTokenProvider)
|
||
{
|
||
Reset();
|
||
|
||
var tokenizer = new AsmTokenizer(_input, _inputAsmKind, asmTokenProvider, _commentStart);
|
||
|
||
// Adjust token size
|
||
var pseudoTokenSizeMax = _input.Length / 7;
|
||
if (pseudoTokenSizeMax > _tokens.Capacity)
|
||
{
|
||
_tokens.Capacity = pseudoTokenSizeMax;
|
||
}
|
||
|
||
// Start the top-block as a directive block
|
||
var block = new AsmBlock { Kind = AsmBlockKind.Block };
|
||
AsmLine line = default;
|
||
var blockKindDetectFlags = BlockKindDetectFlags.None;
|
||
|
||
// Skip first line
|
||
// Don't tokenize the first line that contains e.g:
|
||
// While compiling job: System.Single BurstJobTester/MyJob::CheckFmaSlow(System.Single,System.Single,System.Single)
|
||
while (tokenizer.TryGetNextToken(out var token))
|
||
{
|
||
if (token.Kind == AsmTokenKind.NewLine)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Read all tokens
|
||
// Create blocks and lines on the fly, record functions
|
||
int totalIdx = 0;
|
||
int blockStartIdx = 0;
|
||
bool newLine = false;
|
||
var (possiblyRemoveAlignment, addedAlignment) = (false, 0);
|
||
while (tokenizer.TryGetNextToken(out var token))
|
||
{
|
||
var tokenIndex = _tokens.Count;
|
||
_tokens.Add(token);
|
||
|
||
if (newLine)
|
||
{
|
||
if (possiblyRemoveAlignment)
|
||
{
|
||
// Alignment was added just before a newline
|
||
totalIdx -= addedAlignment;
|
||
}
|
||
|
||
// Push new line
|
||
if (line.Kind == AsmLineKind.SourceFile)
|
||
{
|
||
// Have to remove the line from totalIdx, for proper block idx saving.
|
||
totalIdx -= GetLineLen(line);
|
||
ProcessSourceFile(ref line);
|
||
// We drop this line, we don't store SourceFile line as-is but just below as SourceFileLocation
|
||
}
|
||
else
|
||
{
|
||
var lineRef = new AsmLineRef(_blocks.Count, block.Length);
|
||
if (line.Kind == AsmLineKind.SourceLocation)
|
||
{
|
||
// Have to remove the line from totalIdx, for proper block idx saving.
|
||
totalIdx -= GetLineLen(line);
|
||
ProcessSourceLocation(ref line, ref totalIdx);
|
||
// after this, the line is now a SourceFileLocation
|
||
}
|
||
else if (line.Kind == AsmLineKind.LabelDeclaration)
|
||
{
|
||
// Record labels (global and locals)
|
||
ProcessLabelDeclaration(lineRef, line);
|
||
}
|
||
else if (line.Kind == AsmLineKind.CodeBranch || line.Kind == AsmLineKind.CodeJump)
|
||
{
|
||
// Record temp branch/jumps
|
||
ProcessJumpOrBranch(lineRef, ref line);
|
||
}
|
||
|
||
_lines.Add(line);
|
||
_registersUsedAtLine.PushLine();
|
||
block.Length++;
|
||
}
|
||
|
||
bool previousLineWasBranch = line.Kind == AsmLineKind.CodeBranch;
|
||
|
||
// Reset the line
|
||
line = default;
|
||
line.Kind = AsmLineKind.Empty;
|
||
line.TokenIndex = tokenIndex;
|
||
// We create a new block when hitting a label declaration
|
||
// If the previous line was a conditional branch, it is like having an implicit label
|
||
if (previousLineWasBranch || token.Kind == AsmTokenKind.Label)
|
||
{
|
||
// Refine the kind of block before pushing it
|
||
if ((blockKindDetectFlags & BlockKindDetectFlags.Code) != 0)
|
||
{
|
||
block.Kind = AsmBlockKind.Code;
|
||
}
|
||
else if ((blockKindDetectFlags & BlockKindDetectFlags.Data) != 0)
|
||
{
|
||
block.Kind = AsmBlockKind.Data;
|
||
}
|
||
else if ((blockKindDetectFlags & BlockKindDetectFlags.Directive) != 0)
|
||
{
|
||
block.Kind = AsmBlockKind.Directive;
|
||
}
|
||
|
||
// Push the current block
|
||
_blocks.Add(block);
|
||
_blockTextIdxs.Add((blockStartIdx, totalIdx-1));
|
||
_blockToString.Add(null);
|
||
|
||
// Create a new block
|
||
blockStartIdx = totalIdx;
|
||
block = new AsmBlock
|
||
{
|
||
Kind = AsmBlockKind.None,
|
||
LineIndex = _lines.Count,
|
||
Length = 0
|
||
};
|
||
blockKindDetectFlags = BlockKindDetectFlags.None;
|
||
}
|
||
}
|
||
|
||
// If the current line is still undefined try to detect what kind of line we have
|
||
var lineKind = line.Kind;
|
||
if (lineKind == AsmLineKind.Empty)
|
||
{
|
||
switch (token.Kind)
|
||
{
|
||
case AsmTokenKind.Directive:
|
||
lineKind = AsmLineKind.Directive;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Directive;
|
||
break;
|
||
case AsmTokenKind.SourceFile:
|
||
lineKind = AsmLineKind.SourceFile;
|
||
break;
|
||
case AsmTokenKind.SourceLocation:
|
||
lineKind = AsmLineKind.SourceLocation;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Code;
|
||
break;
|
||
case AsmTokenKind.DataDirective:
|
||
lineKind = AsmLineKind.Data;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Data;
|
||
break;
|
||
case AsmTokenKind.Instruction:
|
||
case AsmTokenKind.InstructionSIMD:
|
||
lineKind = AsmLineKind.Code;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Code;
|
||
break;
|
||
case AsmTokenKind.BranchInstruction:
|
||
lineKind = AsmLineKind.CodeBranch;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Code;
|
||
break;
|
||
case AsmTokenKind.JumpInstruction:
|
||
lineKind = AsmLineKind.CodeJump;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Code;
|
||
break;
|
||
case AsmTokenKind.CallInstruction:
|
||
lineKind = AsmLineKind.CodeCall;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Code;
|
||
break;
|
||
case AsmTokenKind.ReturnInstruction:
|
||
lineKind = AsmLineKind.CodeReturn;
|
||
blockKindDetectFlags |= BlockKindDetectFlags.Code;
|
||
break;
|
||
case AsmTokenKind.Label:
|
||
lineKind = newLine ? AsmLineKind.LabelDeclaration : AsmLineKind.Empty;
|
||
break;
|
||
case AsmTokenKind.Comment:
|
||
lineKind = AsmLineKind.Comment;
|
||
break;
|
||
case AsmTokenKind.FunctionBegin:
|
||
lineKind = AsmLineKind.FunctionBegin;
|
||
break;
|
||
case AsmTokenKind.FunctionEnd:
|
||
lineKind = AsmLineKind.FunctionEnd;
|
||
break;
|
||
}
|
||
line.Kind = lineKind;
|
||
}
|
||
|
||
// Add alignment for it to match the output BurstDisassembler gives to the outside world
|
||
switch (token.Kind)
|
||
{
|
||
case AsmTokenKind.Instruction:
|
||
case AsmTokenKind.CallInstruction:
|
||
case AsmTokenKind.BranchInstruction:
|
||
case AsmTokenKind.JumpInstruction:
|
||
case AsmTokenKind.ReturnInstruction:
|
||
case AsmTokenKind.InstructionSIMD:
|
||
if (!(token.Length >= InstructionAlignment || _inputAsmKind != AsmKind.Intel))
|
||
{
|
||
totalIdx += (InstructionAlignment - token.Length);
|
||
possiblyRemoveAlignment = true;
|
||
addedAlignment = InstructionAlignment - token.Length;
|
||
}
|
||
break;
|
||
// If new line is hit do not set to false, as to carry the information
|
||
// into the next iteration.
|
||
case AsmTokenKind.NewLine:
|
||
break;
|
||
default:
|
||
possiblyRemoveAlignment = false;
|
||
break;
|
||
}
|
||
|
||
// Add used registers to the index appropriate for specific line.
|
||
if (token.Kind == AsmTokenKind.Register)
|
||
{
|
||
_registersUsedAtLine.Add(_lines.Count, GetTokenAsText(token));
|
||
}
|
||
|
||
line.Length++;
|
||
newLine = token.Kind == AsmTokenKind.NewLine;
|
||
totalIdx += newLine ? 1 : token.Length;
|
||
}
|
||
|
||
// Process the remaining line
|
||
if (line.Length > 0)
|
||
{
|
||
_lines.Add(line);
|
||
block.Length++;
|
||
|
||
_registersUsedAtLine.PushLine();
|
||
}
|
||
|
||
if (block.Length > 0)
|
||
{
|
||
_blocks.Add(block);
|
||
_blockTextIdxs.Add((blockStartIdx, totalIdx - 1));
|
||
_blockToString.Add(null);
|
||
}
|
||
|
||
ProcessLabelsAndCreateEdges();
|
||
}
|
||
|
||
private void ProcessLabelDeclaration(in AsmLineRef lineRef, in AsmLine line)
|
||
{
|
||
var iterator = GetIterator(line);
|
||
|
||
iterator.TryGetNext(out var token); // label
|
||
var text = token.Slice(_input);
|
||
if (IsLabelLocal(text))
|
||
{
|
||
// if ´_currentDictLocalLabel==null´ we just hit a local label prior to any global labels.
|
||
// So we simply create a empty global label, to hold this local:
|
||
if (_currentDictLocalLabel is null)
|
||
{
|
||
_currentDictLocalLabel = _globalLabels.GetOrCreate(new StringSlice(""), lineRef);
|
||
_mapBlockIndexToGlobalLabel[lineRef.BlockIndex] = text;
|
||
}
|
||
|
||
// Record local labels to the current global label dictionary
|
||
_currentDictLocalLabel.Add(text, lineRef);
|
||
}
|
||
else
|
||
{
|
||
// Create a local label dictionary per global label
|
||
_currentDictLocalLabel = _globalLabels.GetOrCreate(text, lineRef);
|
||
// Associate the current block index to this global index
|
||
_mapBlockIndexToGlobalLabel[lineRef.BlockIndex] = text;
|
||
}
|
||
}
|
||
|
||
private void ProcessJumpOrBranch(in AsmLineRef lineRef, ref AsmLine line)
|
||
{
|
||
var iterator = GetIterator(line);
|
||
iterator.TryGetNext(out _); // branch/jump instruction
|
||
|
||
if (iterator.TryGetNext(out var label, out var labelTokenIndex))
|
||
{
|
||
if (label.Kind == AsmTokenKind.String || label.Kind == AsmTokenKind.Identifier || label.Kind == AsmTokenKind.Label)
|
||
{
|
||
// In case the token is not a label, convert it to a label after this
|
||
if (label.Kind != AsmTokenKind.Label)
|
||
{
|
||
var token = _tokens[labelTokenIndex];
|
||
token = new AsmToken(AsmTokenKind.Label, token.Position, token.AlignedPosition, token.Length);
|
||
_tokens[labelTokenIndex] = token;
|
||
}
|
||
|
||
var currentGlobalBlockIndex = _currentDictLocalLabel.GlobalLabelLineRef.BlockIndex;
|
||
_tempLabelRefs.Add(new TempLabelRef(currentGlobalBlockIndex, lineRef, label.Position, label.Length));
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ProcessSourceFile(ref AsmLine line)
|
||
{
|
||
var it = GetIterator(line);
|
||
|
||
it.TryGetNext(out _); // skip .file or .cv_file
|
||
|
||
int index = 0;
|
||
if (it.TryGetNext(out var token) && token.Kind == AsmTokenKind.Number)
|
||
{
|
||
var numberAsStr = GetTokenAsText(token);
|
||
index = int.Parse(numberAsStr);
|
||
}
|
||
|
||
if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.String)
|
||
{
|
||
var filename = GetTokenAsText(token).Trim('"').Replace('\\', '/');
|
||
string[] fileLines = null;
|
||
|
||
//blockIdx += 4 + System.IO.Path.GetFileName(filename).Length;// ("=== " + filename).Length
|
||
try
|
||
{
|
||
if (System.IO.File.Exists(filename))
|
||
{
|
||
fileLines = System.IO.File.ReadAllLines(filename);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
fileLines = null;
|
||
}
|
||
|
||
|
||
_fileName.Add(index, filename);
|
||
_fileList.Add(index, fileLines);
|
||
}
|
||
}
|
||
|
||
private void ProcessSourceLocation(ref AsmLine line, ref int blockIdx)
|
||
{
|
||
var it = GetIterator(line);
|
||
|
||
// .loc {fileno} {lineno} [column] [options] -
|
||
// .cv_loc funcid fileno lineno [column]
|
||
int fileno = 0;
|
||
int colno = 0;
|
||
int lineno = 0; // NB 0 indicates no information given
|
||
|
||
if (it.TryGetNext(out var token))
|
||
{
|
||
var tokenSlice = GetTokenAsTextSlice(token);
|
||
if (tokenSlice == CVLocDirective)
|
||
{
|
||
// skip funcId
|
||
it.TryGetNext(out token);
|
||
}
|
||
}
|
||
|
||
if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.Number)
|
||
{
|
||
var numberAsStr = GetTokenAsText(token);
|
||
fileno = int.Parse(numberAsStr);
|
||
}
|
||
|
||
if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.Number)
|
||
{
|
||
var numberAsStr = GetTokenAsText(token);
|
||
lineno = int.Parse(numberAsStr);
|
||
}
|
||
|
||
if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.Number)
|
||
{
|
||
var numberAsStr = GetTokenAsText(token);
|
||
colno = int.Parse(numberAsStr);
|
||
}
|
||
|
||
// Transform the SourceLocation into a SourceFileLocation
|
||
line.Kind = AsmLineKind.SourceFileLocation;
|
||
line.SourceFileNumber = fileno;
|
||
line.SourceLineNumber = lineno;
|
||
line.SourceColumnNumber = colno;
|
||
|
||
// Make sure blockTextIdxs are correct
|
||
if (fileno == 0) return;
|
||
blockIdx += 2 + System.IO.Path.GetFileName(_fileName[fileno]).Length; // ("; " + filename).length
|
||
|
||
if (lineno != 0)
|
||
{
|
||
blockIdx += 4 + lineno.ToString().Length + (colno + 1).ToString().Length;// "(x, y)"
|
||
|
||
if (_fileList.ContainsKey(fileno) && _fileList[fileno] != null && lineno - 1 < _fileList[fileno].Length)
|
||
{
|
||
blockIdx += _fileList[fileno][lineno - 1].Length;
|
||
}
|
||
}
|
||
blockIdx++; // \n
|
||
}
|
||
|
||
private static bool IsLabelLocal(in StringSlice slice)
|
||
{
|
||
return slice.StartsWith(".L");
|
||
}
|
||
|
||
private void ProcessLabelsAndCreateEdges()
|
||
{
|
||
foreach (var tempLabelRef in _tempLabelRefs)
|
||
{
|
||
var globalBlockIndex = tempLabelRef.GlobalBlockIndex;
|
||
|
||
// Source Block + Line
|
||
var srcRef = tempLabelRef.LineRef;
|
||
var srcBlockIndex = srcRef.BlockIndex;
|
||
var srcLineIndex = srcRef.LineIndex;
|
||
var srcBlock = _blocks[srcBlockIndex];
|
||
// Line where the edge occurs
|
||
var srcLine = _lines[srcBlock.LineIndex + srcLineIndex];
|
||
|
||
var label = new StringSlice(_input, tempLabelRef.StringIndex, tempLabelRef.StringLength);
|
||
var isLocal = IsLabelLocal(label);
|
||
AsmLineRef destRef;
|
||
if (isLocal)
|
||
{
|
||
var globalLabel = _mapBlockIndexToGlobalLabel[globalBlockIndex];
|
||
var localLabel = _globalLabels[globalLabel];
|
||
destRef = localLabel[label];
|
||
}
|
||
else
|
||
{
|
||
if (_globalLabels.TryGetValue(label, out var entry))
|
||
{
|
||
destRef = entry.GlobalLabelLineRef;
|
||
}
|
||
else
|
||
{
|
||
continue; // Some global labels (at least on arm) e.g. __divsi3 are runtime library defined and not present at all in the source
|
||
}
|
||
}
|
||
|
||
// Destination Block + Line
|
||
var dstBlock = _blocks[destRef.BlockIndex];
|
||
|
||
// Create edges
|
||
srcBlock.AddEdge(new AsmEdge(AsmEdgeKind.OutBound, srcRef, destRef));
|
||
dstBlock.AddEdge(new AsmEdge(AsmEdgeKind.InBound, destRef, srcRef));
|
||
|
||
// For conditional branches, add the false branch as well
|
||
// TODO: should we comment that in the meantime or?
|
||
if (srcLine.Kind == AsmLineKind.CodeBranch)
|
||
{
|
||
// The implicit destination block for the false branch is the next block of the source
|
||
// TODO: we pickup the line 0, while we might want to select the first code of line or first Label declaration
|
||
var blockFalseRef = new AsmLineRef(srcRef.BlockIndex + 1, 0);
|
||
dstBlock = _blocks[blockFalseRef.BlockIndex];
|
||
|
||
srcBlock.AddEdge(new AsmEdge(AsmEdgeKind.OutBound, srcRef, blockFalseRef));
|
||
dstBlock.AddEdge(new AsmEdge(AsmEdgeKind.InBound, blockFalseRef, srcRef));
|
||
}
|
||
}
|
||
|
||
// Sort all edges
|
||
foreach (var block in Blocks)
|
||
{
|
||
block.SortEdges();
|
||
}
|
||
}
|
||
|
||
private List<(int startIdx, int endIdx)> _blockTextIdxs = new List<(int startIdx, int endIdx)>(128);
|
||
|
||
public List<(int startIdx, int endIdx)> BlockIdxs => _blockTextIdxs;
|
||
|
||
|
||
private string RenderBlock(int blockIndex, bool colored)
|
||
{
|
||
var block = _blocks[blockIndex];
|
||
_output.Clear();
|
||
var lineStart = block.LineIndex;
|
||
var length = block.Length;
|
||
for (int i = 0; i < length; i++)
|
||
{
|
||
var line = _lines[lineStart + i];
|
||
RenderLine(ref line, colored);
|
||
// write back the line that has been modified. But only if we run with the same color mode,
|
||
// that the disassembler was initialized with.
|
||
if (colored == _colored) _lines[lineStart + i] = line;
|
||
}
|
||
|
||
var str = _output.ToString();
|
||
_output.Length = 0;
|
||
return str;
|
||
}
|
||
|
||
internal void RenderLine(ref AsmLine line, bool colored)
|
||
{
|
||
// Render this line with a specific renderer
|
||
if (line.Kind == AsmLineKind.SourceFileLocation)
|
||
{
|
||
RenderSourceFileLocation(ref line, colored);
|
||
return;
|
||
}
|
||
|
||
// Process all tokens
|
||
var length = line.Length;
|
||
int column = 0;
|
||
for (int i = 0; i < length; i++)
|
||
{
|
||
var token = _tokens[line.TokenIndex + i];
|
||
var slice = token.Slice(_input);
|
||
|
||
// We don't record the first column because it is always 0
|
||
if (column > 0)
|
||
{
|
||
if (line.ColumnIndex == 0)
|
||
{
|
||
line.ColumnIndex = _columnIndices.Count;
|
||
}
|
||
_columnIndices.Add(column);
|
||
}
|
||
|
||
if (colored)
|
||
{
|
||
switch (token.Kind)
|
||
{
|
||
case AsmTokenKind.DataDirective:
|
||
case AsmTokenKind.Directive:
|
||
case AsmTokenKind.FunctionBegin:
|
||
case AsmTokenKind.FunctionEnd:
|
||
_output.Append("<color=").Append(ColorDirective).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.Label:
|
||
case AsmTokenKind.Identifier:
|
||
_output.Append("<color=").Append(ColorIdentifier).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.Qualifier:
|
||
_output.Append("<color=").Append(ColorQualifier).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.Instruction:
|
||
case AsmTokenKind.CallInstruction:
|
||
case AsmTokenKind.BranchInstruction:
|
||
case AsmTokenKind.JumpInstruction:
|
||
case AsmTokenKind.ReturnInstruction:
|
||
_output.Append("<color=").Append(ColorInstruction).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
if (i == length - 2) // last slice always a newline
|
||
break;
|
||
column += AlignInstruction(_output, slice.Length, _inputAsmKind);
|
||
break;
|
||
case AsmTokenKind.InstructionSIMD:
|
||
// Perform smell test for simd instructions:
|
||
var col = ColorInstructionSIMD;
|
||
if (_smellTest)
|
||
{
|
||
switch (_tokenProvider.SimdKind(slice))
|
||
{
|
||
case SIMDkind.Packed:
|
||
col = ColorInstructionSIMDPacked;
|
||
break;
|
||
case SIMDkind.Scalar:
|
||
col = ColorInstructionSIMDScalar;
|
||
break;
|
||
case SIMDkind.Infrastructure:
|
||
break;
|
||
}
|
||
}
|
||
|
||
_output.Append("<color=").Append(col).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
if (i == length - 2) // last slice always newline
|
||
break;
|
||
column += AlignInstruction(_output, slice.Length, _inputAsmKind);
|
||
break;
|
||
case AsmTokenKind.Register:
|
||
_output.Append("<color=").Append(ColorRegister).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.Number:
|
||
_output.Append("<color=").Append(ColorNumber).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.String:
|
||
_output.Append("<color=").Append(ColorString).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.Comment:
|
||
_output.Append("<color=").Append(ColorComment).Append('>');
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
_output.Append("</color>");
|
||
break;
|
||
case AsmTokenKind.NewLine:
|
||
_output.Append('\n');
|
||
break;
|
||
default:
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (token.Kind == AsmTokenKind.NewLine)
|
||
{
|
||
_output.Append('\n');
|
||
}
|
||
else
|
||
{
|
||
_output.Append(_input, slice.Position, slice.Length);
|
||
column += slice.Length;
|
||
}
|
||
|
||
// Also wants to align instructions in uncolored mode the same way as colored.
|
||
switch (token.Kind)
|
||
{
|
||
case AsmTokenKind.Instruction:
|
||
case AsmTokenKind.CallInstruction:
|
||
case AsmTokenKind.BranchInstruction:
|
||
case AsmTokenKind.JumpInstruction:
|
||
case AsmTokenKind.ReturnInstruction:
|
||
case AsmTokenKind.InstructionSIMD:
|
||
// Do not add alignment to instruction with no arguments
|
||
// last slice always a newline
|
||
if (i == length - 2) break;
|
||
column += AlignInstruction(_output, slice.Length, _inputAsmKind);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void RenderSourceFileLocation(ref AsmLine line, bool colored)
|
||
{
|
||
char[] comment = {_commentStart, ' '};
|
||
var fileno = line.SourceFileNumber;
|
||
var lineno = line.SourceLineNumber;
|
||
var colno = line.SourceColumnNumber;
|
||
|
||
// If the file number is 0, skip the line
|
||
if (fileno == 0)
|
||
{
|
||
}
|
||
// If the line number is 0, then we can update the file tracking, but still not output a line
|
||
else if (lineno == 0)
|
||
{
|
||
if (colored) _output.Append("<color=").Append(ColorLineDirective).Append('>');
|
||
_output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno]));
|
||
if (colored) _output.Append("</color>");
|
||
}
|
||
// We have a source line and number -- can we load file and extract this line?
|
||
else
|
||
{
|
||
if (_fileList.ContainsKey(fileno) && _fileList[fileno] != null && lineno - 1 < _fileList[fileno].Length)
|
||
{
|
||
if (colored) _output.Append("<color=").Append(ColorLineDirective).Append('>');
|
||
_output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno])).Append('(').Append(lineno).Append(", ").Append(colno + 1).Append(')').Append(_fileList[fileno][lineno - 1]);
|
||
if (colored) _output.Append("</color>");
|
||
}
|
||
else
|
||
{
|
||
if (colored) _output.Append("<color=").Append(ColorLineDirective).Append('>');
|
||
_output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno])).Append('(').Append(lineno).Append(", ").Append(colno + 1).Append(')');
|
||
if (colored) _output.Append("</color>");
|
||
}
|
||
}
|
||
_output.Append('\n');
|
||
}
|
||
private AsmTokenIterator GetIterator(in AsmLine line)
|
||
{
|
||
return new AsmTokenIterator(_tokens, line.TokenIndex, line.Length);
|
||
}
|
||
|
||
public enum AsmKind
|
||
{
|
||
Intel,
|
||
ARM,
|
||
Wasm,
|
||
LLVMIR
|
||
}
|
||
|
||
[Flags]
|
||
enum BlockKindDetectFlags
|
||
{
|
||
None = 0,
|
||
Code = 1 << 0,
|
||
Data = 1 << 1,
|
||
Directive = 1 << 2,
|
||
}
|
||
|
||
public enum AsmBlockKind
|
||
{
|
||
None,
|
||
Block,
|
||
Directive,
|
||
Code,
|
||
Data
|
||
}
|
||
|
||
[DebuggerDisplay("Block {Kind} LineIndex = {LineIndex} Length = {Length}")]
|
||
public class AsmBlock
|
||
{
|
||
public AsmBlockKind Kind;
|
||
|
||
public int LineIndex;
|
||
|
||
public int Length;
|
||
|
||
// Edges attached to this block, might be null if no edges
|
||
public List<AsmEdge> Edges;
|
||
|
||
public void AddEdge(in AsmEdge edge)
|
||
{
|
||
var edges = Edges;
|
||
if (edges == null)
|
||
{
|
||
edges = new List<AsmEdge>();
|
||
Edges = edges;
|
||
}
|
||
edges.Add(edge);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sort edges by in-bound first, block index, line index
|
||
/// </summary>
|
||
public void SortEdges()
|
||
{
|
||
var edges = Edges;
|
||
if (edges == null) return;
|
||
edges.Sort(EdgeComparer.Instance);
|
||
}
|
||
|
||
private class EdgeComparer : IComparer<AsmEdge>
|
||
{
|
||
public static readonly EdgeComparer Instance = new EdgeComparer();
|
||
|
||
public int Compare(AsmEdge x, AsmEdge y)
|
||
{
|
||
// Order by kind first (InBound first, outbound first)
|
||
if (x.Kind != y.Kind)
|
||
{
|
||
return x.Kind == AsmEdgeKind.InBound ? -1 : 1;
|
||
}
|
||
|
||
// Order by Block Index
|
||
if (x.LineRef.BlockIndex != y.LineRef.BlockIndex) return x.LineRef.BlockIndex.CompareTo(y.LineRef.BlockIndex);
|
||
|
||
// Then order by Line Index
|
||
return x.LineRef.LineIndex.CompareTo(y.LineRef.LineIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
public enum AsmLineKind
|
||
{
|
||
Empty = 0,
|
||
Comment,
|
||
Directive,
|
||
SourceFile,
|
||
SourceLocation,
|
||
SourceFileLocation, // computed line
|
||
FunctionBegin,
|
||
FunctionEnd,
|
||
LabelDeclaration,
|
||
Code,
|
||
CodeCall,
|
||
CodeBranch,
|
||
CodeJump,
|
||
CodeReturn,
|
||
Data,
|
||
}
|
||
|
||
/// <summary>
|
||
/// An <see cref="AsmToken"/> iterator skipping spaces.
|
||
/// </summary>
|
||
struct AsmTokenIterator
|
||
{
|
||
private readonly List<AsmToken> _tokens;
|
||
private readonly int _startIndex;
|
||
private readonly int _endIndex;
|
||
private int _index;
|
||
|
||
public AsmTokenIterator(List<AsmToken> tokens, int index, int length)
|
||
{
|
||
if (tokens == null) throw new ArgumentNullException(nameof(tokens));
|
||
_tokens = tokens;
|
||
if (index < 0 || index >= tokens.Count) throw new ArgumentOutOfRangeException(nameof(index), $"Invalid index {index}. Must be >= 0 and < {tokens.Count}");
|
||
if (length < 0) throw new ArgumentOutOfRangeException(nameof(length), $"Invalid length {length}. Must be >=0");
|
||
_startIndex = index;
|
||
_endIndex = index + length - 1;
|
||
if (_endIndex >= tokens.Count) throw new ArgumentOutOfRangeException(nameof(length), $"Invalid length {length}. The final index {_endIndex} cannot be >= {tokens.Count}");
|
||
_index = index;
|
||
}
|
||
|
||
public void Reset()
|
||
{
|
||
_index = _startIndex;
|
||
}
|
||
|
||
public bool TryGetNext(out AsmToken token)
|
||
{
|
||
while (_index <= _endIndex)
|
||
{
|
||
var nextToken = _tokens[_index++];
|
||
if (nextToken.Kind == AsmTokenKind.Misc) continue;
|
||
token = nextToken;
|
||
return true;
|
||
}
|
||
|
||
token = default;
|
||
return false;
|
||
}
|
||
|
||
public bool TryGetNext(out AsmToken token, out int tokenIndex)
|
||
{
|
||
while (_index <= _endIndex)
|
||
{
|
||
tokenIndex = _index;
|
||
var nextToken = _tokens[_index++];
|
||
if (nextToken.Kind == AsmTokenKind.Misc) continue;
|
||
token = nextToken;
|
||
return true;
|
||
}
|
||
|
||
tokenIndex = -1;
|
||
token = default;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
[DebuggerDisplay("{ToDebuggerDisplay(),nq}")]
|
||
[StructLayout(LayoutKind.Explicit)]
|
||
public struct AsmLine
|
||
{
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
// CAUTION: It is important to not put *any managed objects*
|
||
// into this struct for GC efficiency
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
[FieldOffset(0)] public AsmLineKind Kind;
|
||
|
||
[FieldOffset(4)] public int TokenIndex;
|
||
|
||
// only valid when Kind == SourceFileLocation
|
||
[FieldOffset(4)] public int SourceFileNumber;
|
||
|
||
[FieldOffset(8)] public int Length;
|
||
|
||
// only valid when Kind == SourceFileLocation
|
||
[FieldOffset(8)] public int SourceLineNumber;
|
||
|
||
// only valid when Kind == SourceFileLocation
|
||
[FieldOffset(12)] public int SourceColumnNumber;
|
||
|
||
/// <summary>
|
||
/// Index into <see cref="_columnIndices"/>, the column indices will then contain <see cref="Length"/> minus 1 of column ints,
|
||
/// each column corresponding the horizontal offset to a token.
|
||
/// The first column is always 0 for the first token, hence the minus 1.
|
||
/// Only get filled when asking for the text for a block.
|
||
/// </summary>
|
||
[FieldOffset(16)] public int ColumnIndex;
|
||
|
||
private string ToDebuggerDisplay()
|
||
{
|
||
if (Kind == AsmLineKind.SourceFileLocation)
|
||
{
|
||
return $"Line {Kind} File={SourceFileNumber} Line={SourceLineNumber} Column={SourceColumnNumber}";
|
||
}
|
||
else
|
||
{
|
||
return $"Line {Kind} TokenIndex={TokenIndex} Length={Length} ColumnIndex={ColumnIndex}";
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
public enum AsmEdgeKind
|
||
{
|
||
InBound,
|
||
OutBound,
|
||
}
|
||
|
||
/// <summary>
|
||
/// An inbound or outbound connection for a block to another block+line
|
||
/// </summary>
|
||
[DebuggerDisplay("Edge {Kind} Origin: {OriginRef} LineRef: {LineRef}")]
|
||
public struct AsmEdge : IEquatable<AsmEdge>
|
||
{
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
// CAUTION: It is important to not put *any managed objects*
|
||
// into this struct for GC efficiency
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
public AsmEdge(AsmEdgeKind kind, AsmLineRef originRef, AsmLineRef lineRef)
|
||
{
|
||
Kind = kind;
|
||
OriginRef = originRef;
|
||
LineRef = lineRef;
|
||
}
|
||
|
||
|
||
public AsmEdgeKind Kind;
|
||
|
||
public AsmLineRef OriginRef;
|
||
|
||
public AsmLineRef LineRef;
|
||
|
||
public override string ToString()
|
||
{
|
||
return Kind == AsmEdgeKind.InBound ?
|
||
$"Edge {Kind} {LineRef} => {OriginRef}"
|
||
: $"Edge {Kind} {OriginRef} => {LineRef}";
|
||
}
|
||
|
||
public bool Equals(AsmEdge obj) => Kind == obj.Kind && OriginRef.Equals(obj.OriginRef) && LineRef.Equals(obj.LineRef);
|
||
|
||
public override bool Equals(object obj) => obj is AsmEdge other && Equals(other);
|
||
|
||
public override int GetHashCode() => base.GetHashCode();
|
||
}
|
||
|
||
public readonly struct AsmLineRef: IEquatable<AsmLineRef>
|
||
{
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
// CAUTION: It is important to not put *any managed objects*
|
||
// into this struct for GC efficiency
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
|
||
public AsmLineRef(int blockIndex, int lineIndex)
|
||
{
|
||
BlockIndex = blockIndex;
|
||
LineIndex = lineIndex;
|
||
}
|
||
|
||
public readonly int BlockIndex;
|
||
|
||
public readonly int LineIndex;
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"Block: {BlockIndex}, Line: {LineIndex}";
|
||
}
|
||
|
||
public bool Equals(AsmLineRef obj) => BlockIndex == obj.BlockIndex && LineIndex == obj.LineIndex;
|
||
|
||
public override bool Equals(object obj) => obj is AsmLineRef other && Equals(other);
|
||
|
||
public override int GetHashCode() => base.GetHashCode();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Structure used to store all label references before they are getting fully resolved
|
||
/// </summary>
|
||
[DebuggerDisplay("TempLabelRef {LineRef} - String {StringIndex}, {StringLength}")]
|
||
private readonly struct TempLabelRef
|
||
{
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
// CAUTION: It is important to not put *any managed objects*
|
||
// into this struct for GC efficiency
|
||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
public TempLabelRef(int globalBlockIndex, AsmLineRef lineRef, int stringIndex, int stringLength)
|
||
{
|
||
GlobalBlockIndex = globalBlockIndex;
|
||
LineRef = lineRef;
|
||
StringIndex = stringIndex;
|
||
StringLength = stringLength;
|
||
}
|
||
|
||
public readonly int GlobalBlockIndex;
|
||
|
||
public readonly AsmLineRef LineRef;
|
||
|
||
public readonly int StringIndex;
|
||
|
||
public readonly int StringLength;
|
||
}
|
||
|
||
private class DictionaryLocalLabel : Dictionary<StringSlice, AsmLineRef>
|
||
{
|
||
public DictionaryLocalLabel()
|
||
{
|
||
}
|
||
|
||
public DictionaryLocalLabel(int capacity) : base(capacity)
|
||
{
|
||
}
|
||
|
||
public AsmLineRef GlobalLabelLineRef;
|
||
}
|
||
|
||
private class DictionaryGlobalLabel : Dictionary<StringSlice, DictionaryLocalLabel>
|
||
{
|
||
public DictionaryGlobalLabel()
|
||
{
|
||
}
|
||
|
||
public DictionaryGlobalLabel(int capacity) : base(capacity)
|
||
{
|
||
}
|
||
|
||
public DictionaryLocalLabel GetOrCreate(StringSlice label, AsmLineRef globalLineRef)
|
||
{
|
||
if (!TryGetValue(label, out var dictLabel))
|
||
{
|
||
dictLabel = new DictionaryLocalLabel();
|
||
Add(label, dictLabel);
|
||
}
|
||
dictLabel.GlobalLabelLineRef = globalLineRef;
|
||
return dictLabel;
|
||
}
|
||
}
|
||
|
||
internal struct UsedRegisters
|
||
{
|
||
private AsmTokenKindProvider _tokenProvider;
|
||
|
||
/// <summary>
|
||
/// Dictionary<lineNr, List<reg>>
|
||
/// </summary>
|
||
internal readonly Dictionary<int, List<string>> _linesRegisters;
|
||
|
||
private readonly List<string> _tmp;
|
||
private int _currentLineIdx;
|
||
|
||
public UsedRegisters(int count)
|
||
{
|
||
_linesRegisters = new Dictionary<int, List<string>>(count);
|
||
_tmp = new List<string>(2);
|
||
_currentLineIdx = -1;
|
||
_tokenProvider = null;
|
||
}
|
||
|
||
public void AddTokenProvider(AsmTokenKindProvider provider)
|
||
{
|
||
_tokenProvider = provider;
|
||
}
|
||
|
||
private int NumberOfOcurences(List<string> regs, string target)
|
||
{
|
||
var count = 0;
|
||
foreach (var elm in regs)
|
||
{
|
||
if (_tokenProvider.RegisterEqual(elm, target))
|
||
{
|
||
count++;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
public int RegisterMatch(int lineIdx, string reg)
|
||
{
|
||
return LineContainsRegs(lineIdx, out var actualRegs)
|
||
? NumberOfOcurences(actualRegs, reg)
|
||
: 0;
|
||
}
|
||
|
||
public bool RegisterEquality(string regA, string regB) => _tokenProvider.RegisterEqual(regA, regB);
|
||
|
||
public List<string> CleanRegs(List<string> regs)
|
||
{
|
||
var tmpTokenProvider = _tokenProvider;
|
||
var retVal = new List<string>(regs.Count);
|
||
|
||
foreach (var reg in regs)
|
||
{
|
||
if (!retVal.Exists(elm => tmpTokenProvider.RegisterEqual(reg, elm)))
|
||
{
|
||
retVal.Add(reg);
|
||
}
|
||
}
|
||
return retVal;
|
||
}
|
||
|
||
public bool LineContainsRegs(int lineIdx, out List<string> value)
|
||
{
|
||
return _linesRegisters.TryGetValue(lineIdx, out value);
|
||
}
|
||
|
||
public void Add(int lineIdx, string reg)
|
||
{
|
||
_currentLineIdx = lineIdx;
|
||
_tmp.Add(reg);
|
||
}
|
||
|
||
public void PushLine()
|
||
{
|
||
if (_currentLineIdx == -1)
|
||
{
|
||
// We haven't actually tried to add anything.
|
||
return;
|
||
}
|
||
_linesRegisters[_currentLineIdx] = new List<string>(_tmp);
|
||
_tmp.Clear();
|
||
_currentLineIdx = -1;
|
||
}
|
||
|
||
public int Count => _linesRegisters.Count;
|
||
|
||
public void Clear()
|
||
{
|
||
_linesRegisters.Clear();
|
||
_tmp.Clear();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif |