Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions common/OptimizationRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#

import logging
import re
import sys
from pathlib import Path

Expand Down Expand Up @@ -86,6 +87,8 @@ def __init__(
base_dir="examples",
outfile_full=False,
var="",
write_config=True, # indicates if current config
# for the example should be stored for reference
**kwargs,
):
if name is None:
Expand All @@ -96,6 +99,9 @@ def __init__(
infile += f"_{var}"
name += f"_{target_label_dict[target]}"

self.base_dir = base_dir
self.subfolder = subfolder
self.write_config = write_config
self.arch = arch
self.target = target
self.funcname = funcname
Expand Down Expand Up @@ -222,3 +228,73 @@ def run(
out_dir = Path(self.outfile_full).parent
out_dir.mkdir(parents=True, exist_ok=True)
slothy.write_source_to_file(self.outfile_full)

def _write_config(self) -> None:
"""
Writes the configuration for the current example to a file {example_name}.conf
along with the optimized code. This helps to keep track of different
configurations while looking for the best one.

:return: None
:rtype: None
"""
example_name = self.__class__.__name__
subfolder_full = arch_label_dict[self.arch] + "/" + self.subfolder
example_config_path = f"{self.base_dir}/naive/{subfolder_full}_example.py"
outfile = self.outfile.split("/")[-1].split(".")[0]
out_name = f"{self.base_dir}/opt/{subfolder_full}{outfile}.conf"
self.extract_class_source(example_config_path, example_name, out_name)

def extract_class_source(
self, file_path: str, class_name: str, output_file: str
) -> None:
"""
Extracts the example's configuration from the matching _example.py
file and writes it to a file {output_file}.conf

:param file_path: the path to example file
:type file_path: str
:param class_name: the name of the example class
:type class_name: str
:param output_file: the file name of the original optimized example
:type output_file: str
:return: None
:rtype: None
"""
base_level = logging.INFO

logging.basicConfig(
level=base_level,
)
logger = logging.getLogger(self.name)
with open(file_path, "r") as file:
content = file.read()

# Use regular expression to find the class definition
# This pattern looks for the class definition with any
# whitespace and also captures the class body
pattern = r"class\s+{}\s?\(.*\):[\s\S]*?(?=\nclass\s|\Z)".format(
re.escape(class_name)
)

match = re.search(pattern, content)

if match:
class_source_code = match.group(0)
output_file_striped = output_file.split(".")[0].split("/")[-1]
comment = (
"# This configuration was used to generate the most "
+ "recent optimized code for"
+ f"\n# the {output_file_striped} example.\n\n"
)
class_source_code = comment + class_source_code
with open(output_file, "w") as output:
output.write(class_source_code)
logger.info(
f" Config for class '{class_name}' has been written to {output_file}"
)
else:
logger.warning(
f" Config for class '{class_name}' not found in {file_path}."
)
pass
2 changes: 2 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ def run_example(name, **kwargs):
break
if ex is None:
raise OptimizationRunnerException(f"Could not find example {name}")
if ex.write_config:
ex._write_config()
ex.run(**kwargs)

for e in todo:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This configuration was used to generate the most recent optimized code for
# the keccak_f1600_x1_scalar_no_symbolic example.

class neon_keccak_x1_no_symbolic(OptimizationRunner):
def __init__(self, var="", arch=AArch64_Neon, target=Target_CortexA55):
name = "keccak_f1600_x1_scalar_slothy_no_symbolic"
infile = "keccak_f1600_x1_scalar_slothy"
outfile = "examples/naive/aarch64/keccak/keccak_f1600_x1_scalar_no_symbolic.s"
super().__init__(
infile,
name,
outfile=outfile,
rename=True,
arch=arch,
target=target,
outfile_full=True,
subfolder=SUBFOLDER,
)

def core(self, slothy):
slothy.config.reserved_regs = ["x18", "sp"]

slothy.config.inputs_are_outputs = True
slothy.config.variable_size = True
slothy.config.visualize_expected_performance = False
slothy.config.timeout = 10800

slothy.config.selfcheck_failure_logfile = "selfcheck_fail.log"

slothy.config.outputs = ["flags"]
slothy.config.constraints.stalls_first_attempt = 64
slothy.config.constraints.minimize_spills = True
slothy.config.constraints.allow_reordering = True
slothy.config.constraints.allow_spills = True
# NOTE:
# There are better solutions to this (the true minimum seems to be 1),
# but they take a long time to find.
slothy.config.objective_lower_bound = 6
slothy.config.visualize_expected_performance = True
slothy.optimize(start="loop", end="end_loop")

slothy.config.outputs = ["hint_STACK_OFFSET_COUNT"]
slothy.optimize(start="initial_round_start", end="initial_round_end")

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This configuration was used to generate the most recent optimized code for
# the intt_kyber_123_4567_manual_ld4_opt_m1_icestorm example.

class intt_kyber_123_4567(OptimizationRunner):
def __init__(
self, var="", arch=AArch64_Neon, target=Target_CortexA55, timeout=None
):
name = "intt_kyber_123_4567"
infile = name

super().__init__(
infile,
name,
rename=True,
arch=arch,
target=target,
timeout=timeout,
subfolder=SUBFOLDER,
var=var,
)

def core(self, slothy):
slothy.config.sw_pipelining.enabled = True
slothy.config.inputs_are_outputs = True
slothy.config.sw_pipelining.minimize_overlapping = False
slothy.config.variable_size = True
slothy.config.reserved_regs = [f"x{i}" for i in range(0, 7)] + ["x30", "sp"]
slothy.config.constraints.stalls_first_attempt = 64
slothy.optimize_loop("layer4567_start")
slothy.optimize_loop("layer123_start")

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This configuration was used to generate the most recent optimized code for
# the ntt_kyber_123_4567_opt_m1_firestorm example.

class ntt_kyber_123_4567(OptimizationRunner):
def __init__(
self, var="", arch=AArch64_Neon, target=Target_CortexA55, timeout=None
):
name = "ntt_kyber_123_4567"
infile = name

self.var = var

super().__init__(
infile,
name,
rename=True,
arch=arch,
target=target,
timeout=timeout,
subfolder=SUBFOLDER,
var=var,
)

def core(self, slothy):
slothy.config.sw_pipelining.enabled = True
slothy.config.inputs_are_outputs = True
slothy.config.sw_pipelining.minimize_overlapping = False
slothy.config.variable_size = True
slothy.config.reserved_regs = [f"x{i}" for i in range(0, 7)] + ["x30", "sp"]
slothy.config.reserved_regs += self.target_reserved
slothy.config.constraints.stalls_first_attempt = 64
slothy.optimize_loop("layer123_start")
slothy.optimize_loop("layer4567_start")
# Build + emulate entire function to test that behaviour has not changed
if self.var == "":
slothy.global_selftest(
"ntt_kyber_123_4567",
{"x0": 1024, "x1": 1024, "x3": 1024, "x4": 1024, "x5": 1024},
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This configuration was used to generate the most recent optimized code for
# the ntt_kyber_123_4567_scalar_load_opt_a55 example.

class ntt_kyber_123_4567(OptimizationRunner):
def __init__(
self, var="", arch=AArch64_Neon, target=Target_CortexA55, timeout=None
):
name = "ntt_kyber_123_4567"
infile = name

self.var = var

super().__init__(
infile,
name,
rename=True,
arch=arch,
target=target,
timeout=timeout,
subfolder=SUBFOLDER,
var=var,
)

def core(self, slothy):
slothy.config.sw_pipelining.enabled = True
slothy.config.inputs_are_outputs = True
slothy.config.sw_pipelining.minimize_overlapping = False
slothy.config.variable_size = True
slothy.config.reserved_regs = [f"x{i}" for i in range(0, 7)] + ["x30", "sp"]
slothy.config.reserved_regs += self.target_reserved
slothy.config.constraints.stalls_first_attempt = 64
slothy.optimize_loop("layer123_start")
slothy.optimize_loop("layer4567_start")
# Build + emulate entire function to test that behaviour has not changed
if self.var == "":
slothy.global_selftest(
"ntt_kyber_123_4567",
{"x0": 1024, "x1": 1024, "x3": 1024, "x4": 1024, "x5": 1024},
)

58 changes: 58 additions & 0 deletions examples/opt/armv7m/dilithium/fnt_257_dilithium_opt_m7.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# This configuration was used to generate the most recent optimized code for
# the fnt_257_dilithium_opt_m7 example.

class fnt_257_dilithium(OptimizationRunner):
def __init__(self, var="", arch=Arch_Armv7M, target=Target_CortexM7, timeout=None):
name = "fnt_257_dilithium"
infile = name
funcname = "__asm_fnt_257"

super().__init__(
infile,
name,
subfolder=SUBFOLDER,
var=var,
rename=True,
arch=arch,
target=target,
timeout=timeout,
funcname=funcname,
)

def core(self, slothy):
slothy.config.outputs = ["r14", "r12"]
slothy.config.inputs_are_outputs = True
slothy.config.visualize_expected_performance = False
slothy.config.unsafe_address_offset_fixup = False
slothy.config.variable_size = True

func_args = {"r1", "r2", "r3"}
r = slothy.config.reserved_regs
r = r.union(f"s{i}" for i in range(30)) # reserve FPR
r = r.union(func_args)
slothy.config.reserved_regs = r

slothy.config.constraints.stalls_first_attempt = 8
slothy.config.sw_pipelining.enabled = True
slothy.config.timeout = 600
slothy.optimize_loop("_fnt_0_1_2")

slothy.config.sw_pipelining.enabled = False
slothy.config.timeout = 300

slothy.config.constraints.stalls_first_attempt = 8
slothy.config.split_heuristic = True
slothy.config.split_heuristic_factor = 8
slothy.config.split_heuristic_stepsize = 0.1
slothy.config.timeout = 180 # Not more than 2min per step
# TODO: run with more repeats
slothy.config.split_heuristic_repeat = 2
slothy.config.outputs = ["s25", "s27", "r12"]
slothy.fusion_loop("_fnt_3_4_5_6", ssa=False)
slothy.optimize_loop("_fnt_3_4_5_6")
slothy.config.split_heuristic_optimize_seam = 6
slothy.optimize_loop("_fnt_3_4_5_6")

# Due dependencies in the memory between loads and stores, skip this for now
# slothy.optimize_loop("_fnt_to_16_bit")

6 changes: 3 additions & 3 deletions slothy/core/dataflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,13 +312,13 @@ def _append_deps(name, dep_lst):
ret.append(f"* Pos: {self.orig_pos}")
ret.append(f"* ASM: {self.inst}")
ret.append(
f" + Outputs: {list(zip(self.inst.args_out,self.inst.arg_types_out))}"
f" + Outputs: {list(zip(self.inst.args_out, self.inst.arg_types_out))}"
)
ret.append(
f" + Inputs: {list(zip(self.inst.args_in,self.inst.arg_types_in))}"
f" + Inputs: {list(zip(self.inst.args_in, self.inst.arg_types_in))}"
)
ret.append(
f" + In/Outs: {list(zip(self.inst.args_in_out,self.inst.arg_types_in_out))}"
f" + In/Outs: {list(zip(self.inst.args_in_out, self.inst.arg_types_in_out))}"
)
ret.append(f"* TYPE: {self.inst.__class__.__name__}")
_append_src("Input sources", self.src_in)
Expand Down