diff --git a/common/OptimizationRunner.py b/common/OptimizationRunner.py index ca5b8e91f..e025fb8ba 100644 --- a/common/OptimizationRunner.py +++ b/common/OptimizationRunner.py @@ -26,6 +26,7 @@ # import logging +import re import sys from pathlib import Path @@ -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: @@ -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 @@ -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 diff --git a/example.py b/example.py index a42951e9a..ae37297ac 100644 --- a/example.py +++ b/example.py @@ -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: diff --git a/examples/opt/aarch64/keccak/keccak_f1600_x1_scalar_no_symbolic.conf b/examples/opt/aarch64/keccak/keccak_f1600_x1_scalar_no_symbolic.conf new file mode 100644 index 000000000..84b012c4c --- /dev/null +++ b/examples/opt/aarch64/keccak/keccak_f1600_x1_scalar_no_symbolic.conf @@ -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") + diff --git a/examples/opt/aarch64/kyber/intt_kyber_123_4567_manual_ld4_opt_m1_icestorm.conf b/examples/opt/aarch64/kyber/intt_kyber_123_4567_manual_ld4_opt_m1_icestorm.conf new file mode 100644 index 000000000..60fe4e14e --- /dev/null +++ b/examples/opt/aarch64/kyber/intt_kyber_123_4567_manual_ld4_opt_m1_icestorm.conf @@ -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") + diff --git a/examples/opt/aarch64/kyber/ntt_kyber_123_4567_opt_m1_firestorm.conf b/examples/opt/aarch64/kyber/ntt_kyber_123_4567_opt_m1_firestorm.conf new file mode 100644 index 000000000..bb18dd458 --- /dev/null +++ b/examples/opt/aarch64/kyber/ntt_kyber_123_4567_opt_m1_firestorm.conf @@ -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}, + ) + diff --git a/examples/opt/aarch64/kyber/ntt_kyber_123_4567_scalar_load_opt_a55.conf b/examples/opt/aarch64/kyber/ntt_kyber_123_4567_scalar_load_opt_a55.conf new file mode 100644 index 000000000..8ba3751a2 --- /dev/null +++ b/examples/opt/aarch64/kyber/ntt_kyber_123_4567_scalar_load_opt_a55.conf @@ -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}, + ) + diff --git a/examples/opt/armv7m/dilithium/fnt_257_dilithium_opt_m7.conf b/examples/opt/armv7m/dilithium/fnt_257_dilithium_opt_m7.conf new file mode 100644 index 000000000..40841f575 --- /dev/null +++ b/examples/opt/armv7m/dilithium/fnt_257_dilithium_opt_m7.conf @@ -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") + diff --git a/slothy/core/dataflow.py b/slothy/core/dataflow.py index 59d3be107..4bb648005 100644 --- a/slothy/core/dataflow.py +++ b/slothy/core/dataflow.py @@ -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)