Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First stage of intrinsics support #111

Merged
merged 2 commits into from
Sep 27, 2024
Merged
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
13 changes: 13 additions & 0 deletions examples/cfg/example/intrinsics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
intrinsics:
intrinsics:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thomasgoodfellow What is the reason for the nested intrinsics.intrinsics here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's regrettably ugly, emerging from my (mis?)use of dacite to have an optional list of intrinsic definitions. I'd certainly like to remove one layer of that onion but the formulations I tried didn't parse the YML I fed them

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose to move the intrinsics definitions under extensions: as the intrinstics are usually set specific. But this can be done in a followup-PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. BTW are you back earlier than scheduled, or not really back?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be officially back on Tuesday. But I got some time to work on my personal coding projects and while I am waiting for docker images to build, I can do some Seal5-related work ;)

- args:
- arg_name: rd
arg_type: i32
- arg_name: rs1
arg_type: i32
- arg_name: rs2
arg_type: i32
instr_name: xexample.subincacc
intrinsic_name: subincacc
ret_type: i32
1 change: 1 addition & 0 deletions examples/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
EXAMPLES_DIR / "cfg" / "tests.yml",
EXAMPLES_DIR / "cfg" / "passes.yml",
EXAMPLES_DIR / "cfg" / "git.yml",
EXAMPLES_DIR / "cfg" / "example/intrinsics.yml",
]
seal5_flow.load(cfg_files, verbose=VERBOSE, overwrite=False)

Expand Down
10 changes: 10 additions & 0 deletions examples/tests/example/test_subincacc.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,13 @@ __attribute__((naked)) void test_subincacc() {
// CHECK: ab ba b5 51 xexample.subincacc x21, x11, x27
asm("xexample.subincacc x21, x11, x27");
}

void test_intrinsic() {
// CHECK: <test_intrinsic>
int a = 3;
int b = 7;
int c = 4;
// Can't rely upon specific registers being used but at least instruction should have been used
// CHECK: example.subincacc
c = __builtin_xexample_subincacc(a, b, c);
}
16 changes: 14 additions & 2 deletions seal5/backends/riscv_instr_info/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Check failure on line 23 in seal5/backends/riscv_instr_info/writer.py

View workflow job for this annotation

GitHub Actions / Flake8

seal5/backends/riscv_instr_info/writer.py#L23

'seal5.settings.ExtensionsSettings' imported but unused (F401)

# from seal5.settings import ExtensionsSettings

Expand Down Expand Up @@ -220,6 +221,12 @@
)
return tablegen_str

def gen_intrinsic_pattern(instr, intrinsic: IntrinsicDefn):

Check failure on line 224 in seal5/backends/riscv_instr_info/writer.py

View workflow job for this annotation

GitHub Actions / Flake8

seal5/backends/riscv_instr_info/writer.py#L224

Expected 2 blank lines, found 1 (E302)
pat = f"""class Pat_{instr.name}<SDPatternOperator OpNode, Instruction Inst>
: Pat<(OpNode {instr.llvm_ins_str}), (Inst {instr.llvm_ins_str})>;
def : Pat_{instr.name}<int_riscv_{intrinsic.intrinsic_name}, {instr.name}>;"""
return pat


def main():
"""Main app entrypoint."""
Expand All @@ -234,6 +241,7 @@
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("--no-add-intrinsics", dest='add_intrinsics', default=True, action='store_false', help="Suppress patterns for intrinsic functions")

Check failure on line 244 in seal5/backends/riscv_instr_info/writer.py

View workflow job for this annotation

GitHub Actions / Flake8

seal5/backends/riscv_instr_info/writer.py#L244

Line too long (155 > 120 characters) (E501)
args = parser.parse_args()

# initialize logging
Expand Down Expand Up @@ -283,7 +291,7 @@
}
# 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:
Expand Down Expand Up @@ -313,6 +321,11 @@
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}}"
Expand All @@ -325,7 +338,6 @@
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",
Expand Down
Empty file.
216 changes: 216 additions & 0 deletions seal5/backends/riscv_intrinsics/writer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# 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
if ir_type == 'i32':
return 'llvm_i32_ty'
raise NotImplementedError(f'Unhandled ir_type "{ir_type}"')


def build_attr(arch: str, intrinsic: IntrinsicDefn):
uses_mem = False # @todo

Check failure on line 57 in seal5/backends/riscv_intrinsics/writer.py

View workflow job for this annotation

GitHub Actions / Flake8

seal5/backends/riscv_intrinsics/writer.py#L57

Local variable 'uses_mem' is assigned to but never used (F841)
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:
"""Pairs patch contents to location to apply it"""
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("intrinsics/writer - 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 # unused today, but needed very soon

Check failure on line 162 in seal5/backends/riscv_intrinsics/writer.py

View workflow job for this annotation

GitHub Actions / Flake8

seal5/backends/riscv_intrinsics/writer.py#L162

Local variable 'llvm_version' is assigned to but never used (F841)
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()
1 change: 1 addition & 0 deletions seal5/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
# ("riscv_instr_formats", passes.gen_riscv_instr_formats_patch, {}),
("riscv_register_info", passes.gen_riscv_register_info_patch, {}),
("riscv_instr_info", passes.gen_riscv_instr_info_patch, {}),
("riscv_intrinsics", passes.gen_riscv_intrinsics, {}),
# subtarget_tests
# register_types
# operand_types
Expand Down
61 changes: 61 additions & 0 deletions seal5/pass_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,67 @@ 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,
)
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,
Expand Down
Loading
Loading