#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 { /// /// Disassembler for Intel and ARM /// internal partial class BurstDisassembler { // The following member need to be reset/clear on each Reset() private readonly Dictionary _fileName; private readonly Dictionary _fileList; private readonly List _tokens; private readonly List _blocks; private readonly List _blockToString; private readonly List _columnIndices; private readonly List _lines; internal UsedRegisters _registersUsedAtLine; private readonly DictionaryGlobalLabel _globalLabels; private readonly List _tempLabelRefs; private readonly Dictionary _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(); _fileList = new Dictionary(); _tokens = new List(65536); _blocks = new List(128); _blockToString = new List(128); _columnIndices = new List(65536); _lines = new List(4096); _registersUsedAtLine = new UsedRegisters(4096); _tempLabelRefs = new List(4096); _globalLabels = new DictionaryGlobalLabel(128); _mapBlockIndexToGlobalLabel = new Dictionary(128); _output = new StringBuilder(); } internal List ColumnIndices => _columnIndices; /// /// Gets all the blocks. /// public List Blocks => _blocks; /// /// Gets whether the disassembly is colored. /// public bool IsColored => _colored; /// /// Gets all the lines for all the blocks. /// public List Lines => _lines; /// /// Gets all the tokens /// public List Tokens => _tokens; public int LineUsedReg(int lineIdx, string reg) => _registersUsedAtLine.RegisterMatch(lineIdx, reg); public bool LineUsesRegs(int lineIdx, out List usedRegs) => _registersUsedAtLine.LineContainsRegs(lineIdx, out usedRegs); public List CleanRegs(List 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; } /// /// Get a token index for a particular block, line number and column number. /// /// /// /// /// Returns the line index to query /// The token index to use with or -1 if the line, column was not found. 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; } /// /// Gets or renders a particular block to text without caching the result. /// /// The block to render. /// Whether output should be colored. /// A string representation of the block. public string GetOrRenderBlockToTextUncached(int blockIndex, bool colored) { return RenderBlock(blockIndex, colored); } /// /// Gets or renders a particular block to text (colored if specified at time) /// /// The block to render. /// A string representation of the block. public string GetOrRenderBlockToText(int blockIndex) { var str = _blockToString[blockIndex]; if (str == null) { str = RenderBlock(blockIndex, _colored); _blockToString[blockIndex] = str; } return str; } /// /// Gets a token at the specified token index. /// /// The token index /// The token available at the specified index public AsmToken GetToken(int tokenIndex) { return _tokens[tokenIndex]; } /// /// Returns the text representation of the token at the specified index /// /// /// public StringSlice GetTokenAsTextSlice(int tokenIndex) { return _tokens[tokenIndex].Slice(_input); } /// /// Returns the text representation of the specified token. /// public StringSlice GetTokenAsTextSlice(AsmToken token) { return token.Slice(_input); } /// /// Returns the text representation of the specified token. /// public string GetTokenAsText(AsmToken token) { return token.ToString(_input); } /// /// Try and get description of . /// /// Instruction to query information about. /// If instruction present the queried information, else default string. /// Whether instruction was present in burst disassembler core. 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}"); } } /// /// Initialize the disassembler with the input and parametesr. /// /// /// /// /// /// /// 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; } /// /// Helper method to output the full (colored) text as we did before. /// /// This method will be deprecated. Just here for testing during the transition. /// 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); } /// /// Finds the block index encapsulating . /// /// Text index relative to . /// Left-most block index to search within. /// Right-most block index to search within. /// (block index, blocks start index in , blocks end index in ) public (int idx, int l, int r) GetBlockIdxFromTextIdx(int textIdx) { return GetBlockIdxFromTextIdx(textIdx, 0, _blockTextIdxs.Count - 1); } /// /// Finds the block index encapsulating . /// /// Text index relative to . /// Left-most block index to search within. /// Right-most block index to search within. /// (block index, blocks start index in , blocks end index in ) 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 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("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); break; case AsmTokenKind.Label: case AsmTokenKind.Identifier: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); break; case AsmTokenKind.Qualifier: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); break; case AsmTokenKind.Instruction: case AsmTokenKind.CallInstruction: case AsmTokenKind.BranchInstruction: case AsmTokenKind.JumpInstruction: case AsmTokenKind.ReturnInstruction: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); 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("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); if (i == length - 2) // last slice always newline break; column += AlignInstruction(_output, slice.Length, _inputAsmKind); break; case AsmTokenKind.Register: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); break; case AsmTokenKind.Number: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); break; case AsmTokenKind.String: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); break; case AsmTokenKind.Comment: _output.Append("'); _output.Append(_input, slice.Position, slice.Length); column += slice.Length; _output.Append(""); 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("'); _output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno])); if (colored) _output.Append(""); } // 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("'); _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(""); } else { if (colored) _output.Append("'); _output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno])).Append('(').Append(lineno).Append(", ").Append(colno + 1).Append(')'); if (colored) _output.Append(""); } } _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 Edges; public void AddEdge(in AsmEdge edge) { var edges = Edges; if (edges == null) { edges = new List(); Edges = edges; } edges.Add(edge); } /// /// Sort edges by in-bound first, block index, line index /// public void SortEdges() { var edges = Edges; if (edges == null) return; edges.Sort(EdgeComparer.Instance); } private class EdgeComparer : IComparer { 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, } /// /// An iterator skipping spaces. /// struct AsmTokenIterator { private readonly List _tokens; private readonly int _startIndex; private readonly int _endIndex; private int _index; public AsmTokenIterator(List 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; /// /// Index into , the column indices will then contain 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. /// [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, } /// /// An inbound or outbound connection for a block to another block+line /// [DebuggerDisplay("Edge {Kind} Origin: {OriginRef} LineRef: {LineRef}")] public struct AsmEdge : IEquatable { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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(); } /// /// Structure used to store all label references before they are getting fully resolved /// [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 { public DictionaryLocalLabel() { } public DictionaryLocalLabel(int capacity) : base(capacity) { } public AsmLineRef GlobalLabelLineRef; } private class DictionaryGlobalLabel : Dictionary { 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; /// /// Dictionary> /// internal readonly Dictionary> _linesRegisters; private readonly List _tmp; private int _currentLineIdx; public UsedRegisters(int count) { _linesRegisters = new Dictionary>(count); _tmp = new List(2); _currentLineIdx = -1; _tokenProvider = null; } public void AddTokenProvider(AsmTokenKindProvider provider) { _tokenProvider = provider; } private int NumberOfOcurences(List 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 CleanRegs(List regs) { var tmpTokenProvider = _tokenProvider; var retVal = new List(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 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(_tmp); _tmp.Clear(); _currentLineIdx = -1; } public int Count => _linesRegisters.Count; public void Clear() { _linesRegisters.Clear(); _tmp.Clear(); } } } } #endif