You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1415 lines
56 KiB
1415 lines
56 KiB
#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; |
|
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; |
|
private 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"; |
|
private const string DarkColorInstructionSIMD = "#C586C0"; |
|
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 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 ColorRegister; |
|
private string ColorNumber; |
|
private string ColorString; |
|
private string ColorComment; |
|
|
|
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); |
|
_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; |
|
|
|
/// <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> |
|
/// <returns></returns> |
|
public bool Initialize(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColoring = true) |
|
{ |
|
try |
|
{ |
|
InitializeImpl(input, asmKind, useDarkSkin, useSyntaxColoring); |
|
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() |
|
{ |
|
_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 void InitializeImpl(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColoring = true) |
|
{ |
|
UseSkin(useDarkSkin); |
|
_colored = useSyntaxColoring; |
|
var tokenProvider = InitializeInput(input, asmKind); |
|
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> |
|
/// <param name="end">Right-most block index to search within.</param> |
|
/// <returns>(block index, blocks start index in <see cref="_input"/>, blocks end index in <see cref="_input"/>)</returns> |
|
public (int idx, int l, int r) GetBlockIdxFromTextIdx(int textIdx) |
|
{ |
|
return GetBlockIdxFromTextIdx(textIdx, 0, _blockTextIdxs.Count - 1); |
|
} |
|
|
|
/// <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> |
|
/// <param name="end">Right-most block index to search within.</param> |
|
/// <returns>(block index, blocks start index in <see cref="_input"/>, blocks end index in <see cref="_input"/>)</returns> |
|
public (int idx, int l, int r) GetBlockIdxFromTextIdx(int textIdx, int start, int end) |
|
{ |
|
if (start <= end) |
|
{ |
|
int mid = start + (end - start) / 2; |
|
|
|
if (_blockTextIdxs[mid].startIdx <= textIdx && textIdx <= _blockTextIdxs[mid].endIdx) |
|
{ |
|
// if textIdx is within range given at index mid |
|
return (mid, _blockTextIdxs[mid].startIdx, _blockTextIdxs[mid].endIdx); |
|
} |
|
|
|
if (_blockTextIdxs[mid].endIdx < textIdx) |
|
{ |
|
// Look to the right |
|
return GetBlockIdxFromTextIdx(textIdx, mid + 1, end); |
|
} |
|
if (textIdx < _blockTextIdxs[mid].startIdx) |
|
{ |
|
// look to the left |
|
return GetBlockIdxFromTextIdx(textIdx, start, mid - 1); |
|
} |
|
} |
|
return (-1, -1, -1); |
|
} |
|
|
|
private void UseSkin(bool useDarkSkin) |
|
{ |
|
if (useDarkSkin) |
|
{ |
|
ColorLineDirective = DarkColorLineDirective; |
|
ColorDirective = DarkColorDirective; |
|
ColorIdentifier = DarkColorIdentifier; |
|
ColorQualifier = DarkColorQualifier; |
|
ColorInstruction = DarkColorInstruction; |
|
ColorInstructionSIMD = DarkColorInstructionSIMD; |
|
ColorRegister = DarkColorRegister; |
|
ColorNumber = DarkColorNumber; |
|
ColorString = DarkColorString; |
|
ColorComment = DarkColorComment; |
|
} |
|
else |
|
{ |
|
ColorLineDirective = LightColorLineDirective; |
|
ColorDirective = LightColorDirective; |
|
ColorIdentifier = LightColorIdentifier; |
|
ColorQualifier = LightColorQualifier; |
|
ColorInstruction = LightColorInstruction; |
|
ColorInstructionSIMD = LightColorInstructionSIMD; |
|
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 void ParseAndProcessTokens(AsmTokenKindProvider asmTokenProvider) |
|
{ |
|
Reset(); |
|
|
|
var tokenizer = new AsmTokenizer(_input, _inputAsmKind, asmTokenProvider); |
|
|
|
// 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. |
|
for (int i = 0; i < line.Length; i++) |
|
{ |
|
totalIdx -= _tokens[line.TokenIndex + i].Length; |
|
} |
|
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. |
|
for (int i = 0; i < line.Length; i++) |
|
{ |
|
totalIdx -= _tokens[line.TokenIndex + i].Length; |
|
} |
|
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); |
|
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; |
|
} |
|
|
|
line.Length++; |
|
newLine = token.Kind == AsmTokenKind.NewLine; |
|
totalIdx += token.Length; |
|
} |
|
|
|
// Process the remaining line |
|
if (line.Length > 0) |
|
{ |
|
_lines.Add(line); |
|
block.Length++; |
|
} |
|
|
|
if (block.Length > 0) |
|
{ |
|
_blocks.Add(block); |
|
_blockTextIdxs.Add((blockStartIdx, totalIdx)); |
|
_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.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 += 4 + 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; |
|
} |
|
|
|
private 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); |
|
|
|
if (colored) |
|
{ |
|
// 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); |
|
} |
|
|
|
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: |
|
_output.Append("<color=").Append(ColorInstructionSIMD).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 |
|
{ |
|
_output.Append(_input, slice.Position, 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; |
|
AlignInstruction(_output, slice.Length, _inputAsmKind); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void RenderSourceFileLocation(ref AsmLine line, bool colored) |
|
{ |
|
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("=== ").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("=== ").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("=== ").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; |
|
} |
|
} |
|
} |
|
} |
|
#endif |