Singularity/Library/PackageCache/com.unity.burst@1.8.4/Tests/Editor/BurstDisassemblerTests.cs
2024-05-06 11:45:45 -07:00

383 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Burst;
using Unity.Burst.Editor;
using UnityEditorInternal;
using System.Runtime.CompilerServices;
public class BurstDisassemblerTests
{
private BurstDisassembler _disassembler;
[OneTimeSetUp]
public void SetUp()
{
_disassembler = new BurstDisassembler();
}
private static string GetThisFilePath([CallerFilePath] string path = null) => path;
// A Test behaves as an ordinary method
[Test]
public void GetBlockIdxFromTextIdxTest()
{
var thisPath = Path.GetDirectoryName(GetThisFilePath());
Assert.IsTrue(_disassembler.Initialize(
File.ReadAllText(Path.Combine(thisPath, "burstTestTarget.txt")),
BurstDisassembler.AsmKind.Intel,
false,
false));
for (int blockIdx = 0; blockIdx < _disassembler.Blocks.Count; blockIdx++)
{
int blockStart = 0;
for (int i = 0; i < blockIdx; i++)
{
blockStart += _disassembler.GetOrRenderBlockToText(i).Length;
}
var blockStr = _disassembler.GetOrRenderBlockToText(blockIdx);
int blockEnd = blockStart + blockStr.Length - 1;
Assert.AreEqual((blockIdx, blockStart, blockEnd),
_disassembler.GetBlockIdxFromTextIdx(blockStart + 1),
$"Block index was wrong for block with label {blockStr.Substring(0, blockStr.IndexOf('\n'))}");
}
}
[Test]
public void InstantiateRegistersUsedTest()
{
Assert.IsTrue(_disassembler.Initialize(simpleAssembly, BurstDisassembler.AsmKind.Intel));
var regsUsed = _disassembler._registersUsedAtLine;
// Match against expected:
var expectedLines = from l in expected select l.lineNr;
var failed = expectedLines.Except(regsUsed._linesRegisters.Keys);
failed = failed.Concat(regsUsed._linesRegisters.Keys.Except(expectedLines)).Distinct();
if (failed.Any())
{
// Not exact match
foreach (var f in failed)
{
Debug.Log($"lineNumber {f} failed");
}
Assert.Fail();
}
}
[Test]
public void CleanRegisterListTest()
{
Assert.IsTrue(_disassembler.Initialize(simpleAssembly, BurstDisassembler.AsmKind.Intel));
var regs = new List<string> { "rcx", "ecx", "rax" };
var output = _disassembler._registersUsedAtLine.CleanRegs(regs);
var expected = new List<string> { "rcx", "rax" };
Assert.AreEqual(output, expected);
}
[Test]
public void IndexOfRegisterTest()
{
var assembly =
"\n" +
" nop\n" +
" movsxd rcx, cx\n" +
" mov rax, qword ptr [rbp - 16]";
Assert.IsTrue(_disassembler.Initialize(assembly, BurstDisassembler.AsmKind.Intel));
string[,] regs =
{
{ "rcx", "cx" },
{ "rax", "rbp" }
};
string[] lines =
{
" movsxd rcx, cx\n",
" mov rax, qword ptr [rbp - 16]"
};
for (var i = 0; i < 2; i++)
{
var line = lines[i];
var reg = regs[i, 0];
var asmLine = _disassembler.Lines[i+1];
var output = _disassembler.GetRegisterTokenIndex(asmLine, reg);
var regIdx = _disassembler.Tokens[output].AlignedPosition - _disassembler.Tokens[asmLine.TokenIndex].AlignedPosition;
var expected = line.IndexOf(reg) + 1;
Assert.AreEqual(expected, regIdx, $"Failed for line \"{line}\"");
reg = regs[i, 1];
output = _disassembler.GetRegisterTokenIndex(asmLine, reg, output + 1);
regIdx = _disassembler.Tokens[output].AlignedPosition - _disassembler.Tokens[asmLine.TokenIndex].AlignedPosition;
expected = line.IndexOf(reg, expected + 1) + 1;
Assert.AreEqual(expected, regIdx, $"Failed for line \"{line}\"");
}
}
[Test]
[TestCase("x86", new [] {"rdx","edx","dx","dl"}, "dl")]
[TestCase("arm", new [] {"wsp", "sp"},"sp")]
[TestCase("arm", new [] {"v0.2d", "s0", "q0", "h0", "d0", "b0"}, "b0")]
[TestCase("arm", new [] {"w0","x0"}, "x0")]
public void RegisterEqualityTest(string assemblyName, string[] assemblyLine, string register)
{
BurstDisassembler.AsmTokenKindProvider tokenProvider = BurstDisassembler.ARM64AsmTokenKindProvider.Instance;
if (assemblyName == "x86")
{
tokenProvider = BurstDisassembler.X86AsmTokenKindProvider.Instance;
}
foreach (var reg in assemblyLine)
{
Assert.IsTrue(tokenProvider.RegisterEqual(reg, register), $"{reg} == {register}");
}
// Some special cases:
tokenProvider = BurstDisassembler.ARM64AsmTokenKindProvider.Instance;
Assert.IsFalse(tokenProvider.RegisterEqual("w8", "x0"), $"w8 != x0");
Assert.IsFalse(tokenProvider.RegisterEqual("w0", "q0"), "w0 != q0");
Assert.IsFalse(tokenProvider.RegisterEqual("x0", "q0"), "x0 != q0");
}
[Test]
public void RegisterEqualTest()
{
// Only tests for x86, as the others are trivial.
Assert.IsTrue(_disassembler.Initialize(simpleAssembly, BurstDisassembler.AsmKind.Intel));
// Get all register strings:
var tokenProvider = BurstDisassembler.X86AsmTokenKindProvider.Instance;
var tokenProviderT = typeof(BurstDisassembler.AsmTokenKindProvider);
var field = tokenProviderT.GetField("_tokenKinds",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(field, "Could not find _tokenKinds field in AsmTokenKindProvider");
var allTokens = (Dictionary<StringSlice, BurstDisassembler.AsmTokenKind>)field.GetValue(tokenProvider);
var tokensToTest =
from tok in allTokens.Keys
where allTokens.TryGetValue(tok, out var kind)
&& kind == BurstDisassembler.AsmTokenKind.Register
select tok.ToString();
// Test that equality works for all registers:
try
{
foreach (var reg in tokensToTest)
{
// Simply check whether all registers are processable:
tokenProvider.RegisterEqual(reg, "rax");
}
}
catch (Exception e)
{
Assert.Fail($"Not all registers works for register equality (x86). {e}");
}
}
[Test]
public void InstructionAlignmentTest()
{
var assembly =
"\n" + // newline as BurstDisassembler ignores first line
" push rbp\n" +
" .seh_pushreg rbp\n" +
" sub rsp, 32\n";
(int, char)[] expectedPositions =
{
(1,' '), (10, 'p'), (14, ' '), (24, 'r'), (27, '\n'),
(28, ' '), (37, '.'), (49, ' '), (50, 'r'), (53, '\n'),
(54, ' '), (63, 's'), (66, ' '), (77, 'r'), (80, ','), (82, '3'), (84, '\n')
};
Assert.IsTrue(_disassembler.Initialize(assembly, BurstDisassembler.AsmKind.Intel));
var builder = new StringBuilder();
for (int i = 0; i < _disassembler.Blocks.Count; i++)
{
var text = _disassembler.GetOrRenderBlockToTextUncached(i, false);
builder.Append(text);
}
var output = builder.ToString();
for (var i = 0; i < expectedPositions.Length; i++)
{
Assert.AreEqual(expectedPositions[i].Item1, _disassembler.Tokens[i].AlignedPosition);
}
foreach (var (idx, c) in expectedPositions)
{
// -1 as token index for some reason aren't zero indexed.
Assert.AreEqual(c, output[idx-1], $"Token position for index {idx} was wrong.");
}
}
[Test]
public void X86AsmTokenProviderSimdKindTest()
{
var tp = BurstDisassembler.X86AsmTokenKindProvider.Instance;
BurstDisassembler.SIMDkind actual = tp.SimdKind(new StringSlice("vsqrtsd"));
var expected = BurstDisassembler.SIMDkind.Scalar;
Assert.AreEqual(expected, actual);
actual = tp.SimdKind(new StringSlice("vroundpd"));
expected = BurstDisassembler.SIMDkind.Packed;
Assert.AreEqual(expected, actual);
actual = tp.SimdKind(new StringSlice("xsaves"));
expected = BurstDisassembler.SIMDkind.Infrastructure;
Assert.AreEqual(expected,actual);
}
[Test]
public void ARMAsmTokenProviderSimdKindTest()
{
var tp = BurstDisassembler.ARM64AsmTokenKindProvider.Instance;
BurstDisassembler.SIMDkind actual = tp.SimdKind(new StringSlice("vaddw"));
var expected = BurstDisassembler.SIMDkind.Scalar;
Assert.AreEqual(expected, actual);
actual = tp.SimdKind(new StringSlice("vadd.i8"));
expected = BurstDisassembler.SIMDkind.Packed;
Assert.AreEqual(expected, actual);
}
private string GetFirstColorTag(string line)
{
const string colorTag = "#XXXXXX";
const string tag = "<color=";
int idx = line.IndexOf('<');
return line.Substring(idx + tag.Length, colorTag.Length);
}
private const string ARMsimdAssembly =
"\n" +
" ldr r0, [sp, #12]\n" +
" vldr s0, [sp, #20]\n" +
" vstr s0, [sp, #4]\n" +
" ldr r1, [sp, #24]\n" +
" vldr s0, [sp, #4]\n" +
" vmov s2, r0\n" +
" vadd.f32 s0, s0, s2\n" +
" vstr s0, [sp, #20]";
private const string X86SimdAssembly =
"\n" +
" mov rcx, qword ptr [rbp - 32]\n" +
" vmovss xmm0, dword ptr [rbp - 12]\n" +
" vmovss dword ptr [rbp - 40], xmm0\n" +
" mov edx, dword ptr [rbp - 8]\n" +
" call \"Unity.Collections.NativeArray`1<float>.get_Item(Unity.Collections.NativeArray`1<float>* this, int index) -> float_c303f72c9cc472e2ef84a442ead69ef2 from Unity.Burst.Editor.Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\"\n" +
" vmovaps xmm1, xmm0\n" +
" vmovss xmm0, dword ptr [rbp - 40]\n" +
" vaddss xmm0, xmm0, xmm1\n" +
" vmovss dword ptr [rbp - 12], xmm0\n" +
" vzeroall";
[Test]
[TestCase(X86SimdAssembly, 0, BurstDisassembler.DarkColorInstructionSIMDScalar, 1)]
[TestCase(X86SimdAssembly, 0, BurstDisassembler.DarkColorInstructionSIMDPacked, 5)]
[TestCase(X86SimdAssembly, 0, BurstDisassembler.DarkColorInstructionSIMD, 9)]
[TestCase(ARMsimdAssembly, 1, BurstDisassembler.DarkColorInstructionSIMDScalar, 1)]
[TestCase(ARMsimdAssembly, 1, BurstDisassembler.DarkColorInstructionSIMDPacked, 6)]
public void AssemblyColouringSmellTest(string asm, int asmkind, string colorTag, int lineIdx)
{
_disassembler.Initialize(asm, (BurstDisassembler.AsmKind)asmkind, true, true, true);
var line = _disassembler.Lines[lineIdx];
_disassembler._output.Clear();
_disassembler.RenderLine(ref line, true);
var lineString = _disassembler._output.ToString();
_disassembler._output.Clear();
Assert.AreEqual(colorTag, GetFirstColorTag(lineString));
}
private List<(int lineNr, List<string>)> expected = new List<(int lineNr, List<string>)>
{
(2, new List<string> { "rbp" }),
(3, new List<string> { "rbp" }),
(4, new List<string> { "rsp" }),
(6, new List<string> { "rbp", "rsp" }),
(7, new List<string> { "rbp" }),
(11, new List<string> { "rsp" }),
(12, new List<string> { "rbp" }),
(26, new List<string> { "rbp" }),
(27, new List<string> { "rbp" }),
(28, new List<string> { "rsp" }),
(30, new List<string> { "rbp", "rsp" }),
(31, new List<string> { "rbp" }),
(36, new List<string> { "rsp" }),
(37, new List<string> { "rbp" }),
};
private string simpleAssembly =
"\n" + // newline as BurstDisassembler ignores first line
".Lfunc_begin0:\n" +
".seh_proc 589a9d678dbb1201e550a054238fad11\n" +
" push rbp\n" +
" .seh_pushreg rbp\n" +
" sub rsp, 32\n" +
" .seh_stackalloc 32\n" +
" lea rbp, [rsp + 32]\n" +
" .seh_setframe rbp, 32\n" +
" .seh_endprologue\n" +
" call A.B.DoIt\n" +
" nop\n" +
" add rsp, 32\n" +
" pop rbp\n" +
" ret\n" +
" .Lfunc_end0:\n" +
" .seh_endproc\n" +
" \n" +
" .def burst.initialize;\n" +
" .scl 2;\n" +
" .type 32;\n" +
" .endef\n" +
" .globl burst.initialize\n" +
" .p2align 4, 0x90\n" +
" burst.initialize:\n" +
" .Lfunc_begin1:\n" +
" .seh_proc burst.initialize\n" +
" push rbp\n" +
" .seh_pushreg rbp\n" +
" sub rsp, 32\n" +
" .seh_stackalloc 32\n" +
" lea rbp, [rsp + 32]\n" +
" .seh_setframe rbp, 32\n" +
" .seh_endprologue\n" +
" call burst.initialize.externals\n" +
" call burst.initialize.statics\n" +
" nop\n" +
" add rsp, 32\n" +
" pop rbp\n" +
" ret\n" +
" .Lfunc_end1:\n" +
" .seh_endproc\n" +
" \n" +
" .def burst.initialize.externals;\n" +
" .scl 2;\n" +
" .type 32;\n" +
" .endef\n" +
" .globl burst.initialize.externals\n" +
" .p2align 4, 0x90";
}