Files
OSACA/tests/test_marker_utils.py
2025-03-05 09:36:07 +01:00

470 lines
18 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Unit tests for IACA/OSACA marker utilities
"""
import os
import unittest
from collections import OrderedDict
from osaca.semantics import (
reduce_to_section,
find_basic_blocks,
find_jump_labels,
find_basic_loop_bodies,
)
from osaca.parser import ParserAArch64, ParserX86ATT, ParserX86Intel
class TestMarkerUtils(unittest.TestCase):
@classmethod
def setUpClass(self):
self.parser_AArch = ParserAArch64()
self.parser_x86_att = ParserX86ATT()
self.parser_x86_intel = ParserX86Intel()
with open(self._find_file("triad_arm_iaca.s")) as f:
triad_code_arm = f.read()
with open(self._find_file("triad_x86_iaca.s")) as f:
triad_code_x86_att = f.read()
with open(self._find_file("triad_x86_intel_iaca.s")) as f:
triad_code_x86_intel = f.read()
self.parsed_AArch = self.parser_AArch.parse_file(triad_code_arm)
self.parsed_x86_att = self.parser_x86_att.parse_file(triad_code_x86_att)
self.parsed_x86_intel = self.parser_x86_intel.parse_file(triad_code_x86_intel)
#################
# Test
#################
def test_marker_detection_AArch64(self):
kernel = reduce_to_section(self.parsed_AArch, ParserAArch64())
self.assertEqual(len(kernel), 138)
self.assertEqual(kernel[0].line_number, 307)
self.assertEqual(kernel[-1].line_number, 444)
def test_marker_detection_x86_att(self):
kernel = reduce_to_section(self.parsed_x86_att, ParserX86ATT())
self.assertEqual(len(kernel), 9)
self.assertEqual(kernel[0].line_number, 146)
self.assertEqual(kernel[-1].line_number, 154)
def test_marker_detection_x86_intel(self):
kernel = reduce_to_section(self.parsed_x86_intel, ParserX86Intel())
self.assertEqual(len(kernel), 7)
self.assertEqual(kernel[0].line_number, 111)
self.assertEqual(kernel[-1].line_number, 117)
def test_marker_matching_AArch64(self):
# preparation
bytes_1_line = ".byte 213,3,32,31\n"
bytes_2_lines_1 = ".byte 213,3,32\n" + ".byte 31\n"
bytes_2_lines_2 = ".byte 213,3\n" + ".byte 32,31\n"
bytes_2_lines_3 = ".byte 213\n" + ".byte 3,32,31\n"
bytes_3_lines_1 = ".byte 213,3\n" + ".byte 32\n" + ".byte 31\n"
bytes_3_lines_2 = ".byte 213\n" + ".byte 3,32\n" + ".byte 31\n"
bytes_3_lines_3 = ".byte 213\n" + ".byte 3\n" + ".byte 32,31\n"
bytes_4_lines = ".byte 213\n" + ".byte 3\n" + ".byte 32\n" + ".byte 31\n"
bytes_hex = ".byte 0xd5, 0x3, 0x20, 0x1f\n"
bytes_mixed = ".byte 0xd5\n.byte 3,0x20\n.byte 31\n"
mov_start_1 = "mov x1, #111\n"
mov_start_2 = "mov x1, 111 // should work as well\n"
mov_end_1 = "mov x1, #222 // preferred way\n"
mov_end_2 = "mov x1, 222\n"
prologue = (
"mov x12, xzr\n"
+ "\tldp x9, x10, [sp, #16] // 8-byte Folded Reload\n"
+ " .p2align 6\n"
)
kernel = (
".LBB0_28:\n"
+ "fmul v7.2d, v7.2d, v19.2d\n"
+ "stp q0, q1, [x10, #-32]\n"
+ "b.ne .LBB0_28\n"
)
epilogue = ".LBB0_29: // Parent Loop BB0_20 Depth=1\n" + "bl dummy\n"
kernel_length = len(list(filter(None, kernel.split("\n"))))
bytes_variations = [
bytes_1_line,
bytes_2_lines_1,
bytes_2_lines_2,
bytes_2_lines_3,
bytes_3_lines_1,
bytes_3_lines_2,
bytes_3_lines_3,
bytes_4_lines,
bytes_hex,
bytes_mixed,
]
mov_start_variations = [mov_start_1, mov_start_2]
mov_end_variations = [mov_end_1, mov_end_2]
# actual tests
for mov_start_var in mov_start_variations:
for bytes_var_1 in bytes_variations:
for mov_end_var in mov_end_variations:
for bytes_var_2 in bytes_variations:
sample_code = (
prologue
+ mov_start_var
+ bytes_var_1
+ kernel
+ mov_end_var
+ bytes_var_2
+ epilogue
)
with self.subTest(
mov_start=mov_start_var,
bytes_start=bytes_var_1,
mov_end=mov_end_var,
bytes_end=bytes_var_2,
):
sample_parsed = self.parser_AArch.parse_file(sample_code)
sample_kernel = reduce_to_section(sample_parsed, ParserAArch64())
self.assertEqual(len(sample_kernel), kernel_length)
kernel_start = len(
list(
filter(
None,
(prologue + mov_start_var + bytes_var_1).split("\n"),
)
)
)
parsed_kernel = self.parser_AArch.parse_file(
kernel, start_line=kernel_start
)
self.assertEqual(sample_kernel, parsed_kernel)
def test_marker_matching_x86(self):
# preparation
bytes_1_line = ".byte 100,103,144\n"
bytes_2_lines_1 = ".byte 100,103\n" + ".byte 144\n"
bytes_2_lines_2 = ".byte 100\n" + ".byte 103,144\n"
bytes_3_lines = (
".byte 100 # IACA MARKER UTILITY\n"
+ ".byte 103 # IACA MARKER UTILITY\n"
+ ".byte 144 # IACA MARKER UTILITY\n"
)
bytes_hex_line = ".byte 0x64,0x67,0x90\n"
bytes_mixed = ".byte 0x64 # MARKER\n .byte 103,0x90 # ANOTHER MARKER\n"
mov_start_1 = "movl $111, %ebx # IACA START\n"
mov_start_2 = "mov $111, %ebx # IACA START\n"
mov_end_1 = "movl $222, %ebx # IACA END\n"
mov_end_2 = "mov $222, %ebx # IACA END\n"
prologue = "movl -92(%rbp), %r11d\n" + "movl $111, %ebx\n"
kernel = (
"vfmadd132sd (%r15,%rcx,8), %xmm5, %xmm0\n"
+ "vmovsd %xmm0, (%r14,%rcx,8)\n"
+ "cmpl %ebx, %ecx\n"
+ "jge .L8\n"
)
epilogue = ".LE9:\t\t#12.2\n" "call dummy\n"
kernel_length = len(list(filter(None, kernel.split("\n"))))
bytes_variations = [
bytes_1_line,
bytes_2_lines_1,
bytes_2_lines_2,
bytes_3_lines,
bytes_hex_line,
bytes_mixed,
]
mov_start_variations = [mov_start_1, mov_start_2]
mov_end_variations = [mov_end_1, mov_end_2]
# actual tests
for mov_start_var in mov_start_variations:
for bytes_var_1 in bytes_variations:
for mov_end_var in mov_end_variations:
for bytes_var_2 in bytes_variations:
sample_code = (
prologue
+ mov_start_var
+ bytes_var_1
+ kernel
+ mov_end_var
+ bytes_var_2
+ epilogue
)
with self.subTest(
mov_start=mov_start_var,
bytes_start=bytes_var_1,
mov_end=mov_end_var,
bytes_end=bytes_var_2,
):
sample_parsed = self.parser_x86_att.parse_file(sample_code)
sample_kernel = reduce_to_section(sample_parsed, ParserX86ATT())
self.assertEqual(len(sample_kernel), kernel_length)
kernel_start = len(
list(
filter(
None,
(prologue + mov_start_var + bytes_var_1).split("\n"),
)
)
)
parsed_kernel = self.parser_x86_att.parse_file(
kernel, start_line=kernel_start
)
self.assertEqual(sample_kernel, parsed_kernel)
def test_marker_special_cases_AArch(self):
bytes_line = ".byte 213,3,32,31\n"
start_marker = "mov x1, #111\n" + bytes_line
end_marker = "mov x1, #222\n" + bytes_line
prologue = "dup v0.2d, x14\n" "neg x9, x9\n" ".p2align 6\n"
kernel = (
".LBB0_28:\n"
+ "fmul v7.2d, v7.2d, v19.2d\n"
+ "stp q0, q1, [x10, #-32]\n"
+ "b.ne .LBB0_28\n"
)
epilogue = ".LBB0_29: // Parent Loop BB0_20 Depth=1\n" "bl dummy\n"
samples = [
# (test name,
# ignored prologue, section to be extraced, ignored epilogue)
("markers", prologue + start_marker, kernel, end_marker + epilogue),
("marker at file start", start_marker, kernel, end_marker + epilogue),
("no start marker", "", prologue + kernel, end_marker + epilogue),
("marker at file end", prologue + start_marker, kernel, end_marker),
("no end marker", prologue + start_marker, kernel + epilogue, ""),
("empty kernel", prologue + start_marker, "", end_marker + epilogue),
]
for test_name, pro, kernel, epi in samples:
code = pro + kernel + epi
parsed = self.parser_AArch.parse_file(code)
test_kernel = reduce_to_section(parsed, ParserAArch64())
if kernel:
kernel_length = len(kernel.strip().split("\n"))
else:
kernel_length = 0
self.assertEqual(
len(test_kernel),
kernel_length,
msg="Invalid extracted kernel length on {!r} sample".format(test_name),
)
if pro:
kernel_start = len((pro).strip().split("\n"))
else:
kernel_start = 0
parsed_kernel = self.parser_AArch.parse_file(kernel, start_line=kernel_start)
self.assertEqual(
test_kernel,
parsed_kernel,
msg="Invalid extracted kernel on {!r}".format(test_name),
)
def test_marker_special_cases_x86(self):
bytes_line = ".byte 100\n" ".byte 103\n" ".byte 144\n"
start_marker = "movl $111, %ebx\n" + bytes_line
end_marker = "movl $222, %ebx\n" + bytes_line
prologue = "movl -88(%rbp), %r10d\n" "xorl %r11d, %r11d\n" ".p2align 4,,10\n"
kernel = (
".L3: #L3\n"
"vmovsd .LC1(%rip), %xmm0\n"
"vmovsd %xmm0, (%r15,%rcx,8)\n"
"cmpl %ecx, %ebx\n"
"jle .L3\n"
)
epilogue = "leaq -56(%rbp), %rsi\n" "movl %r10d, -88(%rbp)\n" "call timing\n"
samples = [
# (test name,
# ignored prologue, section to be extraced, ignored epilogue)
("markers", prologue + start_marker, kernel, end_marker + epilogue),
("marker at file start", start_marker, kernel, end_marker + epilogue),
("no start marker", "", prologue + kernel, end_marker + epilogue),
("marker at file end", prologue + start_marker, kernel, end_marker),
("no end marker", prologue + start_marker, kernel + epilogue, ""),
("empty kernel", prologue + start_marker, "", end_marker + epilogue),
]
for test_name, pro, kernel, epi in samples:
code = pro + kernel + epi
parsed = self.parser_x86_att.parse_file(code)
test_kernel = reduce_to_section(parsed, ParserX86ATT())
if kernel:
kernel_length = len(kernel.strip().split("\n"))
else:
kernel_length = 0
self.assertEqual(
len(test_kernel),
kernel_length,
msg="Invalid extracted kernel length on {!r} sample".format(test_name),
)
if pro:
kernel_start = len((pro).strip().split("\n"))
else:
kernel_start = 0
parsed_kernel = self.parser_x86_att.parse_file(kernel, start_line=kernel_start)
self.assertEqual(
test_kernel,
parsed_kernel,
msg="Invalid extracted kernel on {!r}".format(test_name),
)
def test_find_jump_labels(self):
self.assertEqual(
find_jump_labels(self.parsed_x86_att),
OrderedDict(
[
(".LFB24", 10),
(".L4", 65),
(".L3", 79),
(".L2", 102),
(".L13", 111),
(".L12", 120),
(".L6", 132),
(".L10", 145),
(".L9", 161),
(".L8", 183),
(".L15", 252),
(".L26", 256),
(".L14", 259),
(".LFB25", 277),
(".L28", 289),
]
),
)
self.assertEqual(
find_jump_labels(self.parsed_AArch),
OrderedDict(
[
("triad", 18),
(".LBB0_3", 71),
(".LBB0_4", 76),
(".LBB0_5", 84),
(".LBB0_7", 92),
(".LBB0_8", 95),
(".LBB0_9", 106),
(".LBB0_11", 118),
(".LBB0_12", 133),
(".LBB0_14", 177),
(".LBB0_15", 190),
(".LBB0_16", 205),
(".LBB0_17", 208),
(".LBB0_18", 221),
(".LBB0_19", 228),
(".LBB0_20", 260),
(".LBB0_22", 272),
(".LBB0_24", 283),
(".LBB0_26", 290),
(".LBB0_28", 298),
(".LBB0_29", 306),
(".LBB0_31", 448),
(".LBB0_32", 458),
(".LBB0_33", 480),
(".LBB0_34", 484),
(".LBB0_35", 493),
(".LBB0_36", 504),
(".LBB0_37", 508),
(".LBB0_38", 518),
("main", 574),
]
),
)
def test_find_basic_blocks(self):
self.assertEqual(
[
(k, v[0].line_number, v[-1].line_number)
for k, v in find_basic_blocks(self.parsed_x86_att).items()
],
[
(".LFB24", 11, 56),
(".L4", 66, 74),
(".L3", 80, 89),
(".L2", 103, 112),
(".L13", 112, 121),
(".L12", 121, 125),
(".L6", 133, 135),
(".L10", 146, 154),
(".L9", 162, 170),
(".L8", 184, 187),
(".L15", 253, 256),
(".L26", 257, 259),
(".L14", 260, 262),
(".LFB25", 278, 290),
(".L28", 290, 300),
],
)
self.assertEqual(
[
(k, v[0].line_number, v[-1].line_number)
for k, v in find_basic_blocks(self.parsed_AArch).items()
],
[
("triad", 19, 64),
(".LBB0_3", 72, 77),
(".LBB0_4", 77, 83),
(".LBB0_5", 85, 89),
(".LBB0_7", 93, 95),
(".LBB0_8", 96, 105),
(".LBB0_9", 107, 114),
(".LBB0_11", 119, 134),
(".LBB0_12", 134, 173),
(".LBB0_14", 178, 191),
(".LBB0_15", 191, 205),
(".LBB0_16", 206, 208),
(".LBB0_17", 209, 222),
(".LBB0_18", 222, 228),
(".LBB0_19", 229, 261),
(".LBB0_20", 261, 269),
(".LBB0_22", 273, 280),
(".LBB0_24", 284, 286),
(".LBB0_26", 291, 293),
(".LBB0_28", 299, 307),
(".LBB0_29", 307, 444),
(".LBB0_31", 449, 459),
(".LBB0_32", 459, 480),
(".LBB0_33", 481, 484),
(".LBB0_34", 485, 494),
(".LBB0_35", 494, 504),
(".LBB0_36", 505, 508),
(".LBB0_37", 509, 518),
(".LBB0_38", 519, 568),
("main", 575, 590),
],
)
def test_find_basic_loop_body(self):
self.assertEqual(
[
(k, v[0].line_number, v[-1].line_number)
for k, v in find_basic_loop_bodies(self.parsed_x86_att).items()
],
[(".L4", 66, 74), (".L10", 146, 154), (".L28", 290, 300)],
)
self.assertEqual(
[
(k, v[0].line_number, v[-1].line_number)
for k, v in find_basic_loop_bodies(self.parsed_AArch).items()
],
[
(".LBB0_12", 134, 173),
(".LBB0_15", 191, 205),
(".LBB0_18", 222, 228),
(".LBB0_29", 307, 444),
(".LBB0_32", 459, 480),
(".LBB0_35", 494, 504),
],
)
##################
# Helper functions
##################
@staticmethod
def _find_file(name):
testdir = os.path.dirname(__file__)
name = os.path.join(testdir, "test_files", name)
assert os.path.exists(name)
return name
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(TestMarkerUtils)
unittest.TextTestRunner(verbosity=2).run(suite)