diff --git a/seal5/backends/riscv_instr_info/writer.py b/seal5/backends/riscv_instr_info/writer.py index 0850b09e..765cd8e1 100644 --- a/seal5/backends/riscv_instr_info/writer.py +++ b/seal5/backends/riscv_instr_info/writer.py @@ -20,6 +20,7 @@ from seal5.index import NamedPatch, File, write_index_yaml from seal5.utils import is_power_of_two +from seal5.settings import ExtensionsSettings, IntrinsicDefn # from seal5.settings import ExtensionsSettings @@ -220,6 +221,12 @@ def gen_riscv_instr_info_str(instr, set_def): ) return tablegen_str +def gen_intrinsic_pattern(instr, intrinsic: IntrinsicDefn): + pat = f"""class Pat_{instr.name} +: Pat<(OpNode {instr.llvm_ins_str}), (Inst {instr.llvm_ins_str})>; +def : Pat_{instr.name};""" + return pat + def main(): """Main app entrypoint.""" @@ -234,6 +241,7 @@ def main(): parser.add_argument("--metrics", default=None, help="Output metrics to file") parser.add_argument("--index", default=None, help="Output index to file") parser.add_argument("--ext", type=str, default="td", help="Default file extension (if using --splitted)") + parser.add_argument("--add-intrinsics", type=bool, default=True, action=argparse.BooleanOptionalAction, help="Include patterns for intrinsic functions") args = parser.parse_args() # initialize logging @@ -283,7 +291,7 @@ def main(): } # preprocess model # print("model", model) - # settings = model.get("settings", None) + settings = model.get("settings", None) artifacts = {} artifacts[None] = [] # used for global artifacts if args.splitted: @@ -313,6 +321,11 @@ def main(): output_file = set_dir / out_name content = gen_riscv_instr_info_str(instr_def, set_def) if len(content) > 0: + if args.add_intrinsics and settings.intrinsics.intrinsics: + # TODO: intrinsics should be dict keyed by instr name + for intrinsic in settings.intrinsics.intrinsics: + if intrinsic.instr_name.casefold() == instr_def.mnemonic.casefold(): + content += gen_intrinsic_pattern(instr_def, intrinsic) assert pred is not None predicate_str = f"Predicates = [{pred}, IsRV{xlen}]" content = f"let {predicate_str} in {{\n{content}\n}}" @@ -325,7 +338,6 @@ def main(): artifacts[set_name].append(instr_info_patch) inc = f"seal5/{set_name}/{output_file.name}" includes.append(inc) - includes_str = "\n".join([f'include "{inc}"' for inc in includes]) set_td_includes_patch = NamedPatch( f"llvm/lib/Target/RISCV/seal5/{set_name}.td", diff --git a/seal5/backends/riscv_intrinsics/__init__.py b/seal5/backends/riscv_intrinsics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/seal5/backends/riscv_intrinsics/writer.py b/seal5/backends/riscv_intrinsics/writer.py new file mode 100644 index 00000000..f620307d --- /dev/null +++ b/seal5/backends/riscv_intrinsics/writer.py @@ -0,0 +1,214 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R +# +# Copyright (C) 2022 +# Chair of Electrical Design Automation +# Technical University of Munich + +"""Clean M2-ISA-R/Seal5 metamodel to .core_desc file.""" + +import argparse +import logging +import pathlib +import pickle +import os.path +from typing import Union +from dataclasses import dataclass + +from m2isar.metamodel import arch + +from seal5.index import NamedPatch, write_index_yaml +from seal5.settings import IntrinsicDefn + +logger = logging.getLogger("riscv_intrinsics") + + +def ir_type_to_text(ir_type: str): + # needs fleshing out with all likely types + # probably needs to take into account RISC-V bit width, e.g. does "Li" means 32 bit integer on a 128-bit platform? + if ir_type == 'i32': + return 'Li' + raise NotImplementedError(f'Unhandled ir_type "{ir_type}"') + + +def build_target(arch: str, intrinsic: IntrinsicDefn): + + # Target couples intrinsic name to argument types and function behaviour + # Start with return type if not void + arg_str = '' + if intrinsic.ret_type: + arg_str += ir_type_to_text(intrinsic.ret_type) + for arg in intrinsic.args: + arg_str += ir_type_to_text(arg.arg_type) + + target = f'TARGET_BUILTIN(__builtin_{arch}_{intrinsic.intrinsic_name}, "{arg_str}", "nc", "{arch}")' + return target + +def ir_type_to_pattern(ir_type: str): + # needs fleshing out with all likely types + match ir_type: + case 'i32': + return 'llvm_i32_ty' + raise NotImplementedError(f'Unhandled ir_type "{ir_type}"') + +def build_attr(arch: str, intrinsic: IntrinsicDefn): + uses_mem = False #@todo + ret_types = '(unsupported)' # how to spec void intrinsic? + attr = f' def int_riscv_{intrinsic.intrinsic_name} : Intrinsic<\n [' + if intrinsic.ret_type: + attr += f'{ir_type_to_pattern(intrinsic.ret_type)}' + attr += '],\n [' + for idx, arg in enumerate(intrinsic.args): + if idx: + attr += ', ' + attr += ir_type_to_pattern(arg.arg_type) + attr += '],\n' + attr += ' [IntrNoMem, IntrSpeculatable, IntrWillReturn]>;' + return attr + + +def build_emit(arch: str, intrinsic: IntrinsicDefn): + emit = (f' case RISCV::BI__builtin_{arch}_{intrinsic.intrinsic_name}:\n' + f' ID = Intrinsic::riscv_{intrinsic.intrinsic_name};\n' + f' break;') + return emit + + +@dataclass +class PatchFrag: + patchee: str + tag: str + contents: str = "" + +def main(): + """Main app entrypoint.""" + + # read command line args + parser = argparse.ArgumentParser() + parser.add_argument("top_level", help="A .m2isarmodel or .seal5model file.") + parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) + parser.add_argument("--output", "-o", type=str, default=None) + parser.add_argument("--splitted", action="store_true", help="Split per set") + parser.add_argument("--formats", action="store_true", help="Also generate instruction formats") + parser.add_argument("--metrics", default=None, help="Output metrics to file") + parser.add_argument("--index", default=None, help="Output index to file") + parser.add_argument("--ext", type=str, default="td", help="Default file extension (if using --splitted)") + args = parser.parse_args() + + # initialize logging + logging.basicConfig(level=getattr(logging, args.log.upper())) + + # resolve model paths + top_level = pathlib.Path(args.top_level) + + is_seal5_model = False + if top_level.suffix == ".seal5model": + is_seal5_model = True + if args.output is None: + assert top_level.suffix in [".m2isarmodel", ".seal5model"], "Can not infer model type from file extension." + raise NotImplementedError + + # out_path = top_level.parent / (top_level.stem + ".core_desc") + else: + out_path = pathlib.Path(args.output) + + logger.info("loading models") + if not is_seal5_model: + raise NotImplementedError + + # load models + with open(top_level, "rb") as f: + # models: "dict[str, arch.CoreDef]" = pickle.load(f) + if is_seal5_model: + model: "dict[str, Union[arch.InstructionSet, ...]]" = pickle.load(f) + model["cores"] = {} + else: # TODO: core vs. set! + temp: "dict[str, Union[arch.InstructionSet, arch.CoreDef]]" = pickle.load(f) + assert len(temp) > 0, "Empty model!" + if isinstance(list(temp.values())[0], arch.CoreDef): + model = {"cores": temp, "sets": {}} + elif isinstance(list(temp.values())[0], arch.InstructionSet): + model = {"sets": temp, "cores": {}} + else: + assert False + + metrics = { + "n_sets": 0, + "n_skipped": 0, + "n_failed": 0, + "n_success": 0, + } + # preprocess model + # print("model", model) + artifacts = {} + artifacts[None] = [] # used for global artifacts + if args.splitted: + raise NotImplementedError + else: + # errs = [] + settings = model.get("settings", None) + llvm_version = None + if not settings or not settings.intrinsics.intrinsics: + logger.warning("No intrinsics configured; didn't need to invoke intrinsics writer.") + quit() + if settings: + llvm_settings = settings.llvm + if llvm_settings: + llvm_state = llvm_settings.state + if llvm_state: + llvm_version = llvm_state.version + patch_frags = { + 'target': PatchFrag(patchee='clang/include/clang/Basic/BuiltinsRISCV.def', tag='builtins_riscv'), + 'attr': PatchFrag(patchee='llvm/include/llvm/IR/IntrinsicsRISCV.td', tag='intrinsics_riscv'), + 'emit': PatchFrag(patchee='clang/lib/CodeGen/CGBuiltin.cpp', tag='cg_builtin') + } + for set_name, set_def in model["sets"].items(): + artifacts[set_name] = [] + metrics["n_sets"] += 1 + ext_settings = set_def.settings + if ext_settings is None: + metrics["n_skipped"] += 1 + continue + for intrinsic in settings.intrinsics.intrinsics: + metrics["n_success"] += 1 + + patch_frags['target'].contents += build_target(arch=ext_settings.get_arch(), intrinsic=intrinsic) + patch_frags['attr'].contents += build_attr(arch=ext_settings.get_arch(), intrinsic=intrinsic) + patch_frags['emit'].contents += build_emit(arch=ext_settings.get_arch(), intrinsic=intrinsic) + + for id, frag in patch_frags.items(): + contents = frag.contents + if len(contents) > 0: + if id == 'target': + contents = f'// {ext_settings.get_arch()}\n{contents}\n' + elif id == 'attr': + contents = f'let TargetPrefix = "riscv" in {{\n{contents}\n}}' + (root, ext) = os.path.splitext(out_path) + patch_path = root + '_' + id + ext + with open(patch_path, "w") as f: + f.write(contents) + key = frag.tag + if ext_settings.experimental: + key += "_experimental" + patch = NamedPatch(frag.patchee, key=key, src_path=patch_path, content=contents) + artifacts[None].append(patch) + if args.metrics: + metrics_file = args.metrics + with open(metrics_file, "w") as f: + f.write(",".join(metrics.keys())) + f.write("\n") + f.write(",".join(map(str, metrics.values()))) + f.write("\n") + if args.index: + if sum(map(lambda x: len(x), artifacts.values())) > 0: + global_artifacts = artifacts.get(None, []) + set_artifacts = {key: value for key, value in artifacts.items() if key is not None} + index_file = args.index + write_index_yaml(index_file, global_artifacts, set_artifacts, content=True) + else: + logger.warning("No patches generated. No index file will be written.") + + +if __name__ == "__main__": + main() diff --git a/seal5/pass_list.py b/seal5/pass_list.py index 5435b8e2..fbdd3ba0 100644 --- a/seal5/pass_list.py +++ b/seal5/pass_list.py @@ -1076,6 +1076,7 @@ def gen_riscv_isa_info_patch( print_func=logger.info if verbose else logger.debug, live=True, ) +# breakpoint() if gen_index_file: if index_file.is_file(): patch_name = f"riscv_isa_info_{input_file.stem}" @@ -1093,6 +1094,68 @@ def gen_riscv_isa_info_patch( logger.warning("No patches found!") +def gen_riscv_intrinsics( + input_model: str, + settings: Optional[Seal5Settings] = None, + env: Optional[dict] = None, + verbose: bool = False, + split: bool = False, + log_level: str = "debug", + **kwargs, +): + assert not split, "TODO" + # formats = True + gen_metrics_file = True + gen_index_file = True + input_file = settings.models_dir / f"{input_model}.seal5model" + assert input_file.is_file(), f"File not found: {input_file}" + name = input_file.name + new_name = name.replace(".seal5model", "") + logger.info("Writing intrinsics patches patch for %s", name) + out_dir = settings.patches_dir / new_name + out_dir.mkdir(exist_ok=True) + + args = [ + settings.models_dir / name, + "--log", + log_level, + "--output", + out_dir / "riscv_intrinsics_info.patch", + ] + if split: + args.append("--splitted") + if gen_metrics_file: + metrics_file = out_dir / ("riscv_intrinsics_info_metrics.csv") + args.extend(["--metrics", metrics_file]) + if gen_index_file: + index_file = out_dir / ("riscv_intrinsics_index.yml") + args.extend(["--index", index_file]) + utils.python( + "-m", + "seal5.backends.riscv_intrinsics.writer", + *args, + env=env, + print_func=logger.info if verbose else logger.debug, + live=True, + ) +# breakpoint() + if gen_index_file: + if index_file.is_file(): + patch_base = f"riscv_intrinsics_target_{input_file.stem}" + patch_settings = PatchSettings( + name=patch_base, + stage=int(PatchStage.PHASE_1), + comment=f"Generated RISCV Intrinsics patch for {input_file.name}", + index=str(index_file), + generated=True, + target="llvm", + ) + settings.add_patch(patch_settings) + settings.to_yaml_file(settings.settings_file) + else: + logger.warning("No patches found!") + + def gen_riscv_instr_info_patch( input_model: str, settings: Optional[Seal5Settings] = None, @@ -1103,6 +1166,7 @@ def gen_riscv_instr_info_patch( **_kwargs, ): # assert not split, "TODO" +# breakpoint() assert split, "TODO" # formats = True gen_metrics_file = True diff --git a/seal5/settings.py b/seal5/settings.py index 38efe1ef..5135b6ea 100644 --- a/seal5/settings.py +++ b/seal5/settings.py @@ -157,8 +157,10 @@ "clone_depth": 1, "sparse_checkout": False, }, + }, + "intrinsics": { }, -} + } ALLOWED_YAML_TYPES = (int, float, str, bool) @@ -570,6 +572,22 @@ class ToolsSettings(YAMLSettings): pattern_gen: Optional[PatternGenSettings] = None +@dataclass +class IntrinsicArg(YAMLSettings): + arg_name: str + arg_type: str + +@dataclass +class IntrinsicDefn(YAMLSettings): + instr_name: str + intrinsic_name: str + ret_type: Optional[str] = None + args: Optional[List[IntrinsicArg]] = None + +@dataclass +class IntrinsicsSettings(YAMLSettings): + intrinsics: Optional[List[IntrinsicDefn]] = None + @dataclass class Seal5Settings(YAMLSettings): @@ -592,6 +610,7 @@ class Seal5Settings(YAMLSettings): riscv: Optional[RISCVSettings] = None tools: Optional[ToolsSettings] = None metrics: list = field(default_factory=list) + intrinsics: Optional[IntrinsicsSettings] = None def reset(self): """Reset Seal5 seetings.""" @@ -623,6 +642,7 @@ def reset(self): transform_info=None, legalization=None, ) + self.intrinsics = IntrinsicsSettings() def save(self, dest: Optional[Path] = None): """Save Seal5 settings to file."""