diff --git a/slothy/core/core.py b/slothy/core/core.py index f320d241..ccdda70d 100644 --- a/slothy/core/core.py +++ b/slothy/core/core.py @@ -39,7 +39,7 @@ from ortools.sat.python import cp_model from slothy.core.config import Config -from slothy.helper import LockAttributes, Permutation, DeferHandler, SourceLine, LLVM_Mc +from slothy.helper import LockAttributes, Permutation, DeferHandler, SourceLine, LLVM_Mc, SelfTest, SelfTestException try: from unicorn import * @@ -869,80 +869,14 @@ def selftest(self, log): else: old_source, new_source = self.orig_code, self.code - CODE_BASE = 0x010000 - CODE_SZ = 0x010000 - RAM_BASE = 0x020000 - RAM_SZ = 0x010000 - - regs = [r for ty in self._config.arch.RegisterType for r in \ - self._config.arch.RegisterType.list_registers(ty)] - - def run_code(code, txt=None): - objcode, offset = LLVM_Mc.assemble(code, - self._config.arch.llvm_mc_arch, - self._config.arch.llvm_mc_attr, - log) - # Setup emulator - mu = Uc(self.config.arch.unicorn_arch, self.config.arch.unicorn_mode) - # Copy initial register contents into emulator - for r,v in initial_register_contents.items(): - ur = self._config.arch.RegisterType.unicorn_reg_by_name(r) - if ur is None: - continue - mu.reg_write(ur, v) - # Copy code into emulator - mu.mem_map(CODE_BASE, CODE_SZ) - mu.mem_write(CODE_BASE, objcode) - # Copy initial memory contents into emulator - mu.mem_map(RAM_BASE, RAM_SZ) - mu.mem_write(RAM_BASE, initial_memory) - # Run emulator - try: - mu.emu_start(CODE_BASE + offset, CODE_BASE + len(objcode)) - except: - log.error("Failed to emulate code using unicorn engine") - log.error("Code") - log.error(SouceLine.write_multiline(code)) + # Check if register contents are the same + regs_expected = set(self.config.outputs).union(self.config.reserved_regs) + # Ignore hint registers, flags and sp for now + regs_expected = set(filter(lambda t: t.startswith("t") is False and + t != "sp" and t != "flags", regs_expected)) - final_register_contents = {} - for r in regs: - ur = self._config.arch.RegisterType.unicorn_reg_by_name(r) - if ur is None: - continue - final_register_contents[r] = mu.reg_read(ur) - final_memory_contents = mu.mem_read(RAM_BASE, RAM_SZ) - - return final_register_contents, final_memory_contents - - for _ in range(self._config.selftest_iterations): - initial_memory = os.urandom(RAM_SZ) - cur_ram = RAM_BASE - # Set initial register contents arbitrarily, except for registers - # which must hold valid memory addresses. - initial_register_contents = {} - for r in regs: - initial_register_contents[r] = int.from_bytes(os.urandom(16)) - for (reg, sz) in address_gprs.items(): - initial_register_contents[reg] = cur_ram - cur_ram += sz - - final_regs_old, final_mem_old = run_code(old_source, txt="old") - final_regs_new, final_mem_new = run_code(new_source, txt="new") - - # Check if memory contents are the same - if final_mem_old != final_mem_new: - raise SlothySelfTestException(f"Selftest failed: Memory mismatch") - - # Check if register contents are the same - regs_expected = set(self.config.outputs).union(self.config.reserved_regs) - # Ignore hint registers, flags and sp for now - regs_expected = set(filter(lambda t: t.startswith("t") is False and - t != "sp" and t != "flags", regs_expected)) - for r in regs_expected: - if final_regs_old[r] != final_regs_new[r]: - raise SlothySelfTestException(f"Selftest failed: Register mismatch for {r}: {hex(final_regs_old[r])} != {hex(final_regs_new[r])}") - - log.info("Local selftest: OK") + SelfTest.run(self.config, log, old_source, new_source, address_gprs, regs_expected, + self.config.selftest_iterations) def selfcheck_with_fixup(self, log): """Do selfcheck, and consider preamble/postamble fixup in case of SW pipelining @@ -1438,12 +1372,6 @@ def __init__(self, config): self.lock() -class SlothySelfCheckException(Exception): - """Exception thrown upon selfcheck failures""" - -class SlothySelfTestException(Exception): - """Exception thrown upon selftest failures""" - class SlothyBase(LockAttributes): """Stateless core of SLOTHY. diff --git a/slothy/core/slothy.py b/slothy/core/slothy.py index 6c99c2d6..82a10b32 100644 --- a/slothy/core/slothy.py +++ b/slothy/core/slothy.py @@ -55,7 +55,7 @@ from slothy.core.heuristics import Heuristics from slothy.helper import CPreprocessor, SourceLine from slothy.helper import AsmAllocation, AsmMacro, AsmHelper, AsmIfElse -from slothy.helper import CPreprocessor, LLVM_Mca, LLVM_Mc, LLVM_Mca_Error +from slothy.helper import CPreprocessor, LLVM_Mca, LLVM_Mc, LLVM_Mca_Error, SelfTest, SelfTestException try: from unicorn import * @@ -63,9 +63,6 @@ except ImportError: Uc = None -class SlothyGlobalSelfTestException(Exception): - """Exception thrown upon global selftest failures""" - class Slothy: """SLOTHY optimizer @@ -191,7 +188,7 @@ def global_selftest(self, funcname, address_gprs, iterations=5): log = self.logger.getChild(f"global_selftest_{funcname}") if Uc is None: - raise SlothyGlobalSelfTestException("Cannot run selftest -- unicorn-engine is not available.") + raise SelfTestException("Cannot run selftest -- unicorn-engine is not available.") if self.config.arch.unicorn_arch is None or \ self.config.arch.llvm_mc_arch is None: @@ -201,87 +198,9 @@ def global_selftest(self, funcname, address_gprs, iterations=5): old_source = self.original_source new_source = self.source - CODE_BASE = 0x010000 - CODE_SZ = 0x010000 - CODE_END = CODE_BASE + CODE_SZ - RAM_BASE = 0x030000 - RAM_SZ = 0x010000 - STACK_BASE = 0x040000 - STACK_SZ = 0x010000 - STACK_TOP = STACK_BASE + STACK_SZ - - regs = [r for ty in self.config.arch.RegisterType for r in \ - self.config.arch.RegisterType.list_registers(ty)] - - def run_code(code, txt=None): - objcode, offset = LLVM_Mc.assemble(code, - self.config.arch.llvm_mc_arch, - self.config.arch.llvm_mc_attr, - log, symbol=funcname, - preprocessor=self.config.compiler_binary, - include_paths=self.config.compiler_include_paths) - # Setup emulator - mu = Uc(self.config.arch.unicorn_arch, self.config.arch.unicorn_mode) - # Copy initial register contents into emulator - for r,v in initial_register_contents.items(): - ur = self.config.arch.RegisterType.unicorn_reg_by_name(r) - if ur is None: - continue - mu.reg_write(ur, v) - # Put a valid address in the LR that serves as the marker to terminate emulation - mu.reg_write(self.config.arch.RegisterType.unicorn_link_register(), CODE_END) - # Setup stack - mu.reg_write(self.config.arch.RegisterType.unicorn_stack_pointer(), STACK_TOP) - # Copy code into emulator - mu.mem_map(CODE_BASE, CODE_SZ) - mu.mem_write(CODE_BASE, objcode) - - # Copy initial memory contents into emulator - mu.mem_map(RAM_BASE, RAM_SZ) - mu.mem_write(RAM_BASE, initial_memory) - # Setup stack - mu.mem_map(STACK_BASE, STACK_SZ) - mu.mem_write(STACK_BASE, initial_stack) - # Run emulator - mu.emu_start(CODE_BASE + offset, CODE_END) - - final_register_contents = {} - for r in regs: - ur = self.config.arch.RegisterType.unicorn_reg_by_name(r) - if ur is None: - continue - final_register_contents[r] = mu.reg_read(ur) - final_memory_contents = mu.mem_read(RAM_BASE, RAM_SZ) - - return final_register_contents, final_memory_contents - - for _ in range(iterations): - initial_memory = os.urandom(RAM_SZ) - initial_stack = os.urandom(STACK_SZ) - cur_ram = RAM_BASE - # Set initial register contents arbitrarily, except for registers - # which must hold valid memory addresses. - initial_register_contents = {} - for r in regs: - initial_register_contents[r] = int.from_bytes(os.urandom(16)) - for (reg, sz) in address_gprs.items(): - initial_register_contents[reg] = cur_ram - cur_ram += sz - - final_regs_old, final_mem_old = run_code(old_source, txt="old") - final_regs_new, final_mem_new = run_code(new_source, txt="new") - - # Check if memory contents are the same - if final_mem_old != final_mem_new: - raise SlothyGlobalSelfTestException(f"Selftest failed: Memory mismatch") - - # Check that callee-saved registers are the same - regs_expected = self.config.arch.RegisterType.callee_saved_registers() - for r in regs_expected: - if final_regs_old[r] != final_regs_new[r]: - raise SlothyGlobalSelfTestException(f"Selftest failed: Register mismatch for {r}: {hex(final_regs_old[r])} != {hex(final_regs_new[r])}") - - log.info(f"Global selftest for {funcname}: OK") + SelfTest.run(self.config, log, old_source, new_source, address_gprs, + self.config.arch.RegisterType.callee_saved_registers(), 5, + fnsym=funcname) # # Stateful wrappers around heuristics diff --git a/slothy/helper.py b/slothy/helper.py index 3877f735..4ef6ebf0 100644 --- a/slothy/helper.py +++ b/slothy/helper.py @@ -27,12 +27,19 @@ import re import subprocess +import os import platform import logging from abc import ABC, abstractmethod from sympy import simplify from slothy.targets.common import * +try: + from unicorn import * + from unicorn.arm64_const import * +except ImportError: + Uc = None + class SourceLine: """Representation of a single line of source code""" @@ -1269,6 +1276,109 @@ def run(header, body, arch, cpu, log, full=False, issue_width=None): res = r.stdout.split('\n') return res +class SelfTestException(Exception): + """Exception thrown upon selftest failures""" + +class SelfTest(): + + @staticmethod + def run(config, log, codeA, codeB, address_gprs, output_registers, iterations, fnsym=None): + CODE_BASE = 0x010000 + CODE_SZ = 0x010000 + CODE_END = CODE_BASE + CODE_SZ + RAM_BASE = 0x030000 + RAM_SZ = 0x010000 + STACK_BASE = 0x040000 + STACK_SZ = 0x010000 + STACK_TOP = STACK_BASE + STACK_SZ + + regs = [r for ty in config.arch.RegisterType for r in \ + config.arch.RegisterType.list_registers(ty)] + + def run_code(code, txt=None): + objcode, offset = LLVM_Mc.assemble(code, + config.arch.llvm_mc_arch, + config.arch.llvm_mc_attr, + log, symbol=fnsym, + preprocessor=config.compiler_binary, + include_paths=config.compiler_include_paths) + # Setup emulator + mu = Uc(config.arch.unicorn_arch, config.arch.unicorn_mode) + # Copy initial register contents into emulator + for r,v in initial_register_contents.items(): + ur = config.arch.RegisterType.unicorn_reg_by_name(r) + if ur is None: + continue + mu.reg_write(ur, v) + if fnsym is not None: + # If we expect a function return, put a valid address in the LR + # that serves as the marker to terminate emulation + mu.reg_write(config.arch.RegisterType.unicorn_link_register(), CODE_END) + # Setup stack + mu.reg_write(config.arch.RegisterType.unicorn_stack_pointer(), STACK_TOP) + # Copy code into emulator + mu.mem_map(CODE_BASE, CODE_SZ) + mu.mem_write(CODE_BASE, objcode) + + # Copy initial memory contents into emulator + mu.mem_map(RAM_BASE, RAM_SZ) + mu.mem_write(RAM_BASE, initial_memory) + # Setup stack + mu.mem_map(STACK_BASE, STACK_SZ) + mu.mem_write(STACK_BASE, initial_stack) + # Run emulator + try: + # For a function, expect a function return; otherwise, expect + # to run to the address CODE_END stored in the link register + if fnsym is None: + mu.emu_start(CODE_BASE + offset, CODE_BASE + len(objcode)) + else: + mu.emu_start(CODE_BASE + offset, CODE_END) + except: + log.error("Failed to emulate code using unicorn engine") + log.error("Code") + log.error(SouceLine.write_multiline(code)) + + final_register_contents = {} + for r in regs: + ur = config.arch.RegisterType.unicorn_reg_by_name(r) + if ur is None: + continue + final_register_contents[r] = mu.reg_read(ur) + final_memory_contents = mu.mem_read(RAM_BASE, RAM_SZ) + + return final_register_contents, final_memory_contents + + for _ in range(iterations): + initial_memory = os.urandom(RAM_SZ) + initial_stack = os.urandom(STACK_SZ) + cur_ram = RAM_BASE + # Set initial register contents arbitrarily, except for registers + # which must hold valid memory addresses. + initial_register_contents = {} + for r in regs: + initial_register_contents[r] = int.from_bytes(os.urandom(16)) + for (reg, sz) in address_gprs.items(): + initial_register_contents[reg] = cur_ram + cur_ram += sz + + final_regs_old, final_mem_old = run_code(codeA, txt="old") + final_regs_new, final_mem_new = run_code(codeB, txt="new") + + # Check if memory contents are the same + if final_mem_old != final_mem_new: + raise SelfTestException(f"Selftest failed: Memory mismatch") + + # Check that callee-saved registers are the same + for r in output_registers: + if final_regs_old[r] != final_regs_new[r]: + raise SelfTestException(f"Selftest failed: Register mismatch for {r}: {hex(final_regs_old[r])} != {hex(final_regs_new[r])}") + + if fnsym is None: + log.info(f"Local selftest: OK") + else: + log.info(f"Global selftest for {fnsym}: OK") + class Permutation(): """Helper class for manipulating permutations"""