Skip to content

Commit

Permalink
Merge pull request #120 from slothy-optimizer/llvm_mc_output
Browse files Browse the repository at this point in the history
selftest: Print stderr output upon llvm-mc failure
  • Loading branch information
hanno-becker authored Dec 12, 2024
2 parents e6ec7ec + 62e3a58 commit f9f8575
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 179 deletions.
88 changes: 8 additions & 80 deletions slothy/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
91 changes: 5 additions & 86 deletions slothy/core/slothy.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,14 @@
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 *
from unicorn.arm64_const import *
except ImportError:
Uc = None

class SlothyGlobalSelfTestException(Exception):
"""Exception thrown upon global selftest failures"""

class Slothy:
"""SLOTHY optimizer
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
126 changes: 113 additions & 13 deletions slothy/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down Expand Up @@ -1212,10 +1219,7 @@ def assemble(source, arch, attr, log, symbol=None, preprocessor=None, include_pa
log.debug(f"Calling LLVM MC assmelber on the following code")
log.debug(code)

### FOR DEBUGGING ONLY
###
### Remove once things work ...
args = [f"--arch={arch}", "--assemble", "--show-encoding"]
args = [f"--arch={arch}", "--assemble", "--filetype=obj"]
if attr is not None:
args.append(f"--mattr={attr}")
try:
Expand All @@ -1224,15 +1228,8 @@ def assemble(source, arch, attr, log, symbol=None, preprocessor=None, include_pa
except subprocess.CalledProcessError as exc:
log.error("llvm-mc failed to handle the following code")
log.error(code)
raise LLVM_Mc_Error from exc

args = [f"--arch={arch}", "--assemble", "--filetype=obj"]
if attr is not None:
args.append(f"--mattr={attr}")
try:
r = subprocess.run(["llvm-mc"] + args,
input=code.encode(), capture_output=True, check=True)
except subprocess.CalledProcessError as exc:
log.error("Output from llvm-mc")
log.error(exc.stderr.decode())
raise LLVM_Mc_Error from exc

# TODO: If there are relocations remaining, we should fail at this point
Expand Down Expand Up @@ -1279,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"""

Expand Down

0 comments on commit f9f8575

Please sign in to comment.