#if UNITY_EDITOR || BURST_INTERNAL
using System;
using System.Collections.Generic;
using System.Text;
namespace Unity.Burst.Editor
{
///
/// Disassembler for Intel and ARM
///
internal partial class BurstDisassembler
{
private readonly Dictionary _fileName;
private readonly Dictionary _fileList;
private readonly List _tokens;
private static readonly StringSlice FileDirective = new StringSlice(".file");
private static readonly StringSlice CVFileDirective = new StringSlice(".cv_file");
private static readonly StringSlice LocDirective = new StringSlice(".loc");
private static readonly StringSlice CVLocDirective = new StringSlice(".cv_loc");
// 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;
// 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();
_fileList=new Dictionary();
_tokens = new List();
}
public enum AsmKind
{
Intel,
ARM,
Wasm,
LLVMIR
}
public string Process(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColouring = true)
{
UseSkin(useDarkSkin);
#if BURST_INTERNAL
return ProcessImpl(input, asmKind, useSyntaxColouring);
#else
try
{
return ProcessImpl(input, asmKind, useSyntaxColouring);
}
catch (Exception ex)
{
UnityEngine.Debug.Log($"Error while trying to disassemble the input: {ex}");
}
// in case of an error, return the input as-is at least
return input;
#endif
}
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 void AlignInstruction(StringBuilder output, int instructionLength, AsmKind asmKind)
{
// Only support Intel for now
if (instructionLength >= InstructionAlignment || asmKind != AsmKind.Intel)
return;
output.Append(' ', InstructionAlignment - instructionLength);
}
private string ProcessImpl(string input, AsmKind asmKind, bool colourize = true)
{
_fileList.Clear();
_fileName.Clear();
_tokens.Clear();
AsmTokenKindProvider asmTokenProvider = default;
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;
}
var tokenizer = new AsmTokenizer(input, asmKind, asmTokenProvider);
// Adjust token size
var pseudoTokenSizeMax = input.Length / 7;
if (pseudoTokenSizeMax > _tokens.Capacity)
{
_tokens.Capacity = pseudoTokenSizeMax;
}
// Read all tokens
while (tokenizer.TryGetNextToken(out var nextToken))
{
_tokens.Add(nextToken);
}
// Process all tokens
var output = new StringBuilder();
for (int i = 0; i < _tokens.Count; i++)
{
var token = _tokens[i];
var slice = token.Slice(input);
if (token.Kind == AsmTokenKind.Directive && i + 1 < _tokens.Count)
{
if (slice == FileDirective || slice == CVFileDirective)
{
// File is followed by an index and a string or just a string with an implied index = 0
i++;
int index = 0;
SkipSpaces(_tokens, ref i);
if (i < _tokens.Count && _tokens[i].Kind == AsmTokenKind.Number)
{
var numberAsStr = _tokens[i].ToString(input);
index = int.Parse(numberAsStr);
i++;
}
SkipSpaces(_tokens, ref i);
if (i < _tokens.Count && _tokens[i].Kind == AsmTokenKind.String)
{
var filename = _tokens[i].ToString(input).Trim('"');
string[] fileLines;
try
{
fileLines = System.IO.File.ReadAllLines(filename);
}
catch
{
fileLines = null;
}
_fileName.Add(index, filename);
_fileList.Add(index, fileLines);
}
continue;
}
if (slice == LocDirective || slice == CVLocDirective)
{
// .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
i++;
SkipSpaces(_tokens, ref i);
if (slice == CVLocDirective)
{
// silently consume function id
if (i < _tokens.Count && _tokens[i].Kind == AsmTokenKind.Number)
{
i++;
}
SkipSpaces(_tokens, ref i);
}
if (i < _tokens.Count && _tokens[i].Kind == AsmTokenKind.Number)
{
var numberAsStr = _tokens[i].ToString(input);
fileno = int.Parse(numberAsStr);
i++;
}
SkipSpaces(_tokens, ref i);
if (i < _tokens.Count && _tokens[i].Kind == AsmTokenKind.Number)
{
var numberAsStr = _tokens[i].ToString(input);
lineno = int.Parse(numberAsStr);
i++;
}
SkipSpaces(_tokens, ref i);
if (i < _tokens.Count && _tokens[i].Kind == AsmTokenKind.Number)
{
var numberAsStr = _tokens[i].ToString(input);
colno = int.Parse(numberAsStr);
i++;
}
// Skip until end of line
for (; i < _tokens.Count; i++)
{
var tokenToSkip = _tokens[i];
if (tokenToSkip.Kind == AsmTokenKind.NewLine)
{
break;
}
}
// 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 (colourize) output.Append("");
output.Append("=== ").Append(System.IO.Path.GetFileName(_fileName[fileno]));
if (colourize) output.Append("");
output.Append("\n");
}
// 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 (colourize) output.Append("");
output.Append("=== ").Append(System.IO.Path.GetFileName(_fileName[fileno])).Append("(").Append(lineno).Append(", ").Append(colno + 1).Append(")").Append(_fileList[fileno][lineno - 1]);
if (colourize) output.Append("");
output.Append("\n");
}
else
{
if (colourize) output.Append("");
output.Append("=== ").Append(System.IO.Path.GetFileName(_fileName[fileno])).Append("(").Append(lineno).Append(", ").Append(colno + 1).Append(")");
if (colourize) output.Append("");
output.Append("\n");
}
}
continue;
}
}
if (colourize)
{
switch (token.Kind)
{
case AsmTokenKind.Directive:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
case AsmTokenKind.Identifier:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
case AsmTokenKind.Qualifier:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
case AsmTokenKind.Instruction:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
AlignInstruction(output, slice.Length, asmKind);
break;
case AsmTokenKind.InstructionSIMD:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
AlignInstruction(output, slice.Length, asmKind);
break;
case AsmTokenKind.Register:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
case AsmTokenKind.Number:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
case AsmTokenKind.String:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
case AsmTokenKind.Comment:
output.Append("");
output.Append(input, slice.Position, slice.Length);
output.Append("");
break;
default:
output.Append(input, slice.Position, slice.Length);
break;
}
}
else
{
output.Append(input, slice.Position, slice.Length);
}
}
return output.ToString();
}
private static void SkipSpaces(List tokens, ref int i)
{
for(; i < tokens.Count; i++)
{
var kind = tokens[i].Kind;
if (kind != AsmTokenKind.Misc)
{
return;
}
}
}
}
}
#endif