Files
Cesarus85--ringfall-mjolnir-vr/tools/test_codegen_blocker_classifier.py
T

278 lines
8.3 KiB
Python

import hashlib
import subprocess
import sys
import unittest
from pathlib import Path
from tools.codegen_blocker_classifier import (
_capstone_instructions,
_llvm_style_operands,
classify,
parse_disassembly,
)
from tools.coff_compare import build_coff, load
def instruction(mnemonic, operands="", address=0):
return {
"address": address,
"bytes": "90",
"mnemonic": mnemonic,
"operands": operands,
}
def relocation(address=4, target="_callee"):
return {"address": address, "type": 20, "target": ["external", target, 0]}
def evidence(instructions, size=32, relocations=None, sha=None):
relocations = list(relocations or [])
if sha is None:
sha = hashlib.sha256(repr(instructions).encode("utf-8")).hexdigest()
return {
"size": size,
"relocation_count": len(relocations),
"normalized_sha256": sha,
"relocations": relocations,
"instructions": instructions,
}
class CodegenBlockerClassifierTests(unittest.TestCase):
def test_direct_script_invocation_reaches_cli(self):
script = Path(__file__).with_name("codegen_blocker_classifier.py")
completed = subprocess.run(
[sys.executable, str(script), "--help"],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(completed.returncode, 0, completed.stderr)
self.assertIn("--backend", completed.stdout)
def test_capstone_backend_emits_llvm_style_evidence(self):
obj = load(build_coff(
sections=[{
"name": ".text",
"size": 4,
"raw_data": b"\x55\x8b\xec\xc3",
"flags": 0xE0000060,
}],
symbols=[{
"name": "_f",
"value": 0,
"section": 1,
"type": 0x20,
"storage": 2,
}],
))
instructions = _capstone_instructions(obj, "_f")
self.assertEqual(
[item["mnemonic"] for item in instructions],
["pushl", "movl", "retl"],
)
self.assertEqual(instructions[1]["operands"], "%esp, %ebp")
def test_capstone_operands_are_normalized_like_llvm_objdump(self):
self.assertEqual(
_llvm_style_operands("$16, 8(%eax, %ebx, 4)"),
"$0x10, 0x8(%eax,%ebx,4)",
)
def test_parse_llvm_objdump_instruction_and_ignore_relocation(self):
parsed = parse_disassembly("""
00000000 <_f>:
0: 55 pushl %ebp
1: e8 00 00 00 00 calll 0x6 <_f+0x6>
00000002: IMAGE_REL_I386_REL32 _callee
""")
self.assertEqual([item["mnemonic"] for item in parsed], ["pushl", "calll"])
self.assertEqual(parsed[1]["operands"], "0x6 <_f+0x6>")
def test_r1_private_abi_frame_signature(self):
target = evidence([
instruction("pushl", "%esi"),
instruction("movl", "%ecx, %esi"),
instruction("addl", "%edx, %esi"),
instruction("retl"),
], relocations=[relocation()])
candidate = evidence([
instruction("pushl", "%ebp"),
instruction("movl", "%esp, %ebp"),
instruction("subl", "$0x10, %esp"),
instruction("movl", "0x8(%ebp), %eax"),
instruction("movl", "0xc(%ebp), %ecx"),
instruction("retl"),
], size=48, relocations=[relocation()])
result = classify(target, candidate)
self.assertEqual((result["kind"], result["recipe"]), ("RECIPE", "R1"))
def test_r3_sib_vs_scaled_temporary(self):
target = evidence([
instruction("movl", "%edx, _entries(,%ecx,8)"),
instruction("retl"),
])
candidate = evidence([
instruction("leal", "(,%ecx,8), %eax"),
instruction("movl", "%edx, _aggregate+4(%eax)"),
instruction("retl"),
], size=48)
result = classify(target, candidate)
self.assertEqual((result["kind"], result["recipe"]), ("RECIPE", "R3"))
self.assertIn("ownership", result["stop_rule"])
def test_r3_does_not_trigger_on_sib_load(self):
target = evidence([
instruction("movl", "_entries(,%ecx,8), %edx"),
instruction("retl"),
])
candidate = evidence([
instruction("leal", "(,%ecx,8), %eax"),
instruction("movl", "_entries(%eax), %edx"),
instruction("retl"),
], size=48)
self.assertEqual(classify(target, candidate)["kind"], "UNKNOWN")
def test_r3_accepts_decimal_shl_scale(self):
target = evidence([
instruction("movl", "%edx, _entries(,%ecx,8)"),
instruction("retl"),
])
candidate = evidence([
instruction("shll", "$3, %ecx"),
instruction("movl", "%edx, _aggregate+4(%ecx)"),
instruction("retl"),
], size=48)
self.assertEqual(classify(target, candidate)["recipe"], "R3")
def test_r4_x87_fst_fstp_liveness(self):
target = evidence([
instruction("flds", "0x8(%ebp)"),
instruction("fsts", "(%eax)"),
instruction("retl"),
], relocations=[relocation()])
candidate = evidence([
instruction("flds", "0x8(%ebp)"),
instruction("fstps", "(%eax)"),
instruction("retl"),
], relocations=[relocation()])
result = classify(target, candidate)
self.assertEqual((result["kind"], result["recipe"]), ("RECIPE", "R4"))
def test_r5_early_return_shape(self):
relocs = [relocation()]
target = evidence([
instruction("testl", "%eax, %eax"),
instruction("je", "<L0>"),
instruction("retl"),
instruction("retl"),
], relocations=relocs)
candidate = evidence([
instruction("testl", "%eax, %eax"),
instruction("je", "<L0>"),
instruction("retl"),
], relocations=relocs)
result = classify(target, candidate)
self.assertEqual((result["kind"], result["recipe"]), ("RECIPE", "R5"))
def test_pure_register_mirror_is_stop(self):
relocs = [relocation()]
target = evidence([
instruction("pushl", "%esi"),
instruction("movl", "0x8(%ebp), %esi"),
instruction("addl", "%eax, %esi"),
instruction("popl", "%esi"),
instruction("retl"),
], relocations=relocs)
candidate = evidence([
instruction("pushl", "%edi"),
instruction("movl", "0x8(%ebp), %edi"),
instruction("addl", "%eax, %edi"),
instruction("popl", "%edi"),
instruction("retl"),
], relocations=relocs)
result = classify(target, candidate)
self.assertEqual(result["kind"], "STOP")
self.assertEqual(result["class"], "pure-register-permutation")
def test_register_mirror_requires_exact_relocation_addresses(self):
target = evidence([
instruction("movl", "%esi, %eax"), instruction("retl")],
relocations=[relocation(address=4)])
candidate = evidence([
instruction("movl", "%edi, %eax"), instruction("retl")],
relocations=[relocation(address=5)])
self.assertEqual(classify(target, candidate)["kind"], "UNKNOWN")
def test_independent_adjacent_swap_is_stop(self):
relocs = [relocation()]
target = evidence([
instruction("fmuls", "0x30(%edi)"),
instruction("addl", "$0x18, %esp"),
instruction("retl"),
], relocations=relocs)
candidate = evidence([
instruction("addl", "$0x18, %esp"),
instruction("fmuls", "0x30(%edi)"),
instruction("retl"),
], relocations=relocs)
result = classify(target, candidate)
self.assertEqual(result["kind"], "STOP")
self.assertEqual(result["class"], "independent-instruction-scheduling")
def test_swap_with_register_dependency_refuses(self):
target = evidence([
instruction("movl", "(%eax), %ecx"),
instruction("addl", "%ecx, %edx"),
instruction("retl"),
])
candidate = evidence([
instruction("addl", "%ecx, %edx"),
instruction("movl", "(%eax), %ecx"),
instruction("retl"),
])
self.assertEqual(classify(target, candidate)["kind"], "UNKNOWN")
def test_non_bijective_register_rewrite_refuses(self):
target = evidence([
instruction("addl", "%esi, %eax"),
instruction("subl", "%esi, %ecx"),
instruction("retl"),
])
candidate = evidence([
instruction("addl", "%edi, %eax"),
instruction("subl", "%ebx, %ecx"),
instruction("retl"),
])
self.assertEqual(classify(target, candidate)["kind"], "UNKNOWN")
def test_fst_pair_plus_other_difference_refuses(self):
target = evidence([
instruction("fsts", "(%eax)"),
instruction("movl", "%eax, %ecx"),
instruction("retl"),
], relocations=[relocation()])
candidate = evidence([
instruction("fstps", "(%eax)"),
instruction("movl", "%eax, %edx"),
instruction("retl"),
], relocations=[relocation()])
self.assertEqual(classify(target, candidate)["kind"], "UNKNOWN")
def test_equal_measurements_are_refused_not_credited(self):
instructions = [instruction("retl")]
target = evidence(instructions, sha="same")
candidate = evidence(instructions, sha="same")
result = classify(target, candidate)
self.assertEqual(result["kind"], "REFUSE")
self.assertFalse(result["grants_match_credit"])
def test_missing_disassembly_is_unknown(self):
result = classify(evidence([]), evidence([]))
self.assertEqual(result["kind"], "UNKNOWN")
if __name__ == "__main__":
unittest.main()