Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3bd8a58

Browse files
authoredSep 27, 2024
Merge pull request #111 from tum-ei-eda/intrinsics
First stage of intrinsics support
2 parents fe569e9 + c93d63b commit 3bd8a58

File tree

9 files changed

+340
-3
lines changed

9 files changed

+340
-3
lines changed
 

‎examples/cfg/example/intrinsics.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
intrinsics:
3+
intrinsics:
4+
- args:
5+
- arg_name: rd
6+
arg_type: i32
7+
- arg_name: rs1
8+
arg_type: i32
9+
- arg_name: rs2
10+
arg_type: i32
11+
instr_name: xexample.subincacc
12+
intrinsic_name: subincacc
13+
ret_type: i32

‎examples/demo.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
EXAMPLES_DIR / "cfg" / "tests.yml",
9696
EXAMPLES_DIR / "cfg" / "passes.yml",
9797
EXAMPLES_DIR / "cfg" / "git.yml",
98+
EXAMPLES_DIR / "cfg" / "example/intrinsics.yml",
9899
]
99100
seal5_flow.load(cfg_files, verbose=VERBOSE, overwrite=False)
100101

‎examples/tests/example/test_subincacc.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ __attribute__((naked)) void test_subincacc() {
2323
// CHECK: ab ba b5 51 xexample.subincacc x21, x11, x27
2424
asm("xexample.subincacc x21, x11, x27");
2525
}
26+
27+
void test_intrinsic() {
28+
// CHECK: <test_intrinsic>
29+
int a = 3;
30+
int b = 7;
31+
int c = 4;
32+
// Can't rely upon specific registers being used but at least instruction should have been used
33+
// CHECK: example.subincacc
34+
c = __builtin_xexample_subincacc(a, b, c);
35+
}

‎seal5/backends/riscv_instr_info/writer.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from seal5.index import NamedPatch, File, write_index_yaml
2222
from seal5.utils import is_power_of_two
23+
from seal5.settings import ExtensionsSettings, IntrinsicDefn
2324

2425
# from seal5.settings import ExtensionsSettings
2526

@@ -220,6 +221,12 @@ def gen_riscv_instr_info_str(instr, set_def):
220221
)
221222
return tablegen_str
222223

224+
def gen_intrinsic_pattern(instr, intrinsic: IntrinsicDefn):
225+
pat = f"""class Pat_{instr.name}<SDPatternOperator OpNode, Instruction Inst>
226+
: Pat<(OpNode {instr.llvm_ins_str}), (Inst {instr.llvm_ins_str})>;
227+
def : Pat_{instr.name}<int_riscv_{intrinsic.intrinsic_name}, {instr.name}>;"""
228+
return pat
229+
223230

224231
def main():
225232
"""Main app entrypoint."""
@@ -234,6 +241,7 @@ def main():
234241
parser.add_argument("--metrics", default=None, help="Output metrics to file")
235242
parser.add_argument("--index", default=None, help="Output index to file")
236243
parser.add_argument("--ext", type=str, default="td", help="Default file extension (if using --splitted)")
244+
parser.add_argument("--no-add-intrinsics", dest='add_intrinsics', default=True, action='store_false', help="Suppress patterns for intrinsic functions")
237245
args = parser.parse_args()
238246

239247
# initialize logging
@@ -283,7 +291,7 @@ def main():
283291
}
284292
# preprocess model
285293
# print("model", model)
286-
# settings = model.get("settings", None)
294+
settings = model.get("settings", None)
287295
artifacts = {}
288296
artifacts[None] = [] # used for global artifacts
289297
if args.splitted:
@@ -313,6 +321,11 @@ def main():
313321
output_file = set_dir / out_name
314322
content = gen_riscv_instr_info_str(instr_def, set_def)
315323
if len(content) > 0:
324+
if args.add_intrinsics and settings.intrinsics.intrinsics:
325+
# TODO: intrinsics should be dict keyed by instr name
326+
for intrinsic in settings.intrinsics.intrinsics:
327+
if intrinsic.instr_name.casefold() == instr_def.mnemonic.casefold():
328+
content += gen_intrinsic_pattern(instr_def, intrinsic)
316329
assert pred is not None
317330
predicate_str = f"Predicates = [{pred}, IsRV{xlen}]"
318331
content = f"let {predicate_str} in {{\n{content}\n}}"
@@ -325,7 +338,6 @@ def main():
325338
artifacts[set_name].append(instr_info_patch)
326339
inc = f"seal5/{set_name}/{output_file.name}"
327340
includes.append(inc)
328-
329341
includes_str = "\n".join([f'include "{inc}"' for inc in includes])
330342
set_td_includes_patch = NamedPatch(
331343
f"llvm/lib/Target/RISCV/seal5/{set_name}.td",

‎seal5/backends/riscv_intrinsics/__init__.py

Whitespace-only changes.
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R
4+
#
5+
# Copyright (C) 2022
6+
# Chair of Electrical Design Automation
7+
# Technical University of Munich
8+
9+
"""Clean M2-ISA-R/Seal5 metamodel to .core_desc file."""
10+
11+
import argparse
12+
import logging
13+
import pathlib
14+
import pickle
15+
import os.path
16+
from typing import Union
17+
from dataclasses import dataclass
18+
19+
from m2isar.metamodel import arch
20+
21+
from seal5.index import NamedPatch, write_index_yaml
22+
from seal5.settings import IntrinsicDefn
23+
24+
logger = logging.getLogger("riscv_intrinsics")
25+
26+
27+
def ir_type_to_text(ir_type: str):
28+
# needs fleshing out with all likely types
29+
# probably needs to take into account RISC-V bit width, e.g. does "Li" means 32 bit integer on a 128-bit platform?
30+
if ir_type == 'i32':
31+
return 'Li'
32+
raise NotImplementedError(f'Unhandled ir_type "{ir_type}"')
33+
34+
35+
def build_target(arch: str, intrinsic: IntrinsicDefn):
36+
37+
# Target couples intrinsic name to argument types and function behaviour
38+
# Start with return type if not void
39+
arg_str = ''
40+
if intrinsic.ret_type:
41+
arg_str += ir_type_to_text(intrinsic.ret_type)
42+
for arg in intrinsic.args:
43+
arg_str += ir_type_to_text(arg.arg_type)
44+
45+
target = f'TARGET_BUILTIN(__builtin_{arch}_{intrinsic.intrinsic_name}, "{arg_str}", "nc", "{arch}")'
46+
return target
47+
48+
49+
def ir_type_to_pattern(ir_type: str):
50+
# needs fleshing out with all likely types
51+
if ir_type == 'i32':
52+
return 'llvm_i32_ty'
53+
raise NotImplementedError(f'Unhandled ir_type "{ir_type}"')
54+
55+
56+
def build_attr(arch: str, intrinsic: IntrinsicDefn):
57+
uses_mem = False # @todo
58+
attr = f' def int_riscv_{intrinsic.intrinsic_name} : Intrinsic<\n ['
59+
if intrinsic.ret_type:
60+
attr += f'{ir_type_to_pattern(intrinsic.ret_type)}'
61+
attr += '],\n ['
62+
for idx, arg in enumerate(intrinsic.args):
63+
if idx:
64+
attr += ', '
65+
attr += ir_type_to_pattern(arg.arg_type)
66+
attr += '],\n'
67+
attr += ' [IntrNoMem, IntrSpeculatable, IntrWillReturn]>;'
68+
return attr
69+
70+
71+
def build_emit(arch: str, intrinsic: IntrinsicDefn):
72+
emit = (f' case RISCV::BI__builtin_{arch}_{intrinsic.intrinsic_name}:\n'
73+
f' ID = Intrinsic::riscv_{intrinsic.intrinsic_name};\n'
74+
f' break;')
75+
return emit
76+
77+
78+
@dataclass
79+
class PatchFrag:
80+
"""Pairs patch contents to location to apply it"""
81+
patchee: str
82+
tag: str
83+
contents: str = ""
84+
85+
86+
def main():
87+
"""Main app entrypoint."""
88+
89+
# read command line args
90+
parser = argparse.ArgumentParser()
91+
parser.add_argument("top_level", help="A .m2isarmodel or .seal5model file.")
92+
parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"])
93+
parser.add_argument("--output", "-o", type=str, default=None)
94+
parser.add_argument("--splitted", action="store_true", help="Split per set")
95+
parser.add_argument("--formats", action="store_true", help="Also generate instruction formats")
96+
parser.add_argument("--metrics", default=None, help="Output metrics to file")
97+
parser.add_argument("--index", default=None, help="Output index to file")
98+
parser.add_argument("--ext", type=str, default="td", help="Default file extension (if using --splitted)")
99+
args = parser.parse_args()
100+
101+
# initialize logging
102+
logging.basicConfig(level=getattr(logging, args.log.upper()))
103+
104+
# resolve model paths
105+
top_level = pathlib.Path(args.top_level)
106+
107+
is_seal5_model = False
108+
if top_level.suffix == ".seal5model":
109+
is_seal5_model = True
110+
if args.output is None:
111+
assert top_level.suffix in [".m2isarmodel", ".seal5model"], "Can not infer model type from file extension."
112+
raise NotImplementedError
113+
114+
# out_path = top_level.parent / (top_level.stem + ".core_desc")
115+
else:
116+
out_path = pathlib.Path(args.output)
117+
118+
logger.info("intrinsics/writer - loading models")
119+
if not is_seal5_model:
120+
raise NotImplementedError
121+
122+
# load models
123+
with open(top_level, "rb") as f:
124+
# models: "dict[str, arch.CoreDef]" = pickle.load(f)
125+
if is_seal5_model:
126+
model: "dict[str, Union[arch.InstructionSet, ...]]" = pickle.load(f)
127+
model["cores"] = {}
128+
else: # TODO: core vs. set!
129+
temp: "dict[str, Union[arch.InstructionSet, arch.CoreDef]]" = pickle.load(f)
130+
assert len(temp) > 0, "Empty model!"
131+
if isinstance(list(temp.values())[0], arch.CoreDef):
132+
model = {"cores": temp, "sets": {}}
133+
elif isinstance(list(temp.values())[0], arch.InstructionSet):
134+
model = {"sets": temp, "cores": {}}
135+
else:
136+
assert False
137+
138+
metrics = {
139+
"n_sets": 0,
140+
"n_skipped": 0,
141+
"n_failed": 0,
142+
"n_success": 0,
143+
}
144+
# preprocess model
145+
# print("model", model)
146+
artifacts = {}
147+
artifacts[None] = [] # used for global artifacts
148+
if args.splitted:
149+
raise NotImplementedError
150+
else:
151+
# errs = []
152+
settings = model.get("settings", None)
153+
llvm_version = None
154+
if not settings or not settings.intrinsics.intrinsics:
155+
logger.warning("No intrinsics configured; didn't need to invoke intrinsics writer.")
156+
quit()
157+
if settings:
158+
llvm_settings = settings.llvm
159+
if llvm_settings:
160+
llvm_state = llvm_settings.state
161+
if llvm_state:
162+
llvm_version = llvm_state.version # unused today, but needed very soon
163+
patch_frags = {
164+
'target': PatchFrag(patchee='clang/include/clang/Basic/BuiltinsRISCV.def', tag='builtins_riscv'),
165+
'attr': PatchFrag(patchee='llvm/include/llvm/IR/IntrinsicsRISCV.td', tag='intrinsics_riscv'),
166+
'emit': PatchFrag(patchee='clang/lib/CodeGen/CGBuiltin.cpp', tag='cg_builtin')
167+
}
168+
for set_name, set_def in model["sets"].items():
169+
artifacts[set_name] = []
170+
metrics["n_sets"] += 1
171+
ext_settings = set_def.settings
172+
if ext_settings is None:
173+
metrics["n_skipped"] += 1
174+
continue
175+
for intrinsic in settings.intrinsics.intrinsics:
176+
metrics["n_success"] += 1
177+
178+
patch_frags['target'].contents += build_target(arch=ext_settings.get_arch(), intrinsic=intrinsic)
179+
patch_frags['attr'].contents += build_attr(arch=ext_settings.get_arch(), intrinsic=intrinsic)
180+
patch_frags['emit'].contents += build_emit(arch=ext_settings.get_arch(), intrinsic=intrinsic)
181+
182+
for id, frag in patch_frags.items():
183+
contents = frag.contents
184+
if len(contents) > 0:
185+
if id == 'target':
186+
contents = f'// {ext_settings.get_arch()}\n{contents}\n'
187+
elif id == 'attr':
188+
contents = f'let TargetPrefix = "riscv" in {{\n{contents}\n}}'
189+
(root, ext) = os.path.splitext(out_path)
190+
patch_path = root + '_' + id + ext
191+
with open(patch_path, "w") as f:
192+
f.write(contents)
193+
key = frag.tag
194+
if ext_settings.experimental:
195+
key += "_experimental"
196+
patch = NamedPatch(frag.patchee, key=key, src_path=patch_path, content=contents)
197+
artifacts[None].append(patch)
198+
if args.metrics:
199+
metrics_file = args.metrics
200+
with open(metrics_file, "w") as f:
201+
f.write(",".join(metrics.keys()))
202+
f.write("\n")
203+
f.write(",".join(map(str, metrics.values())))
204+
f.write("\n")
205+
if args.index:
206+
if sum(map(lambda x: len(x), artifacts.values())) > 0:
207+
global_artifacts = artifacts.get(None, [])
208+
set_artifacts = {key: value for key, value in artifacts.items() if key is not None}
209+
index_file = args.index
210+
write_index_yaml(index_file, global_artifacts, set_artifacts, content=True)
211+
else:
212+
logger.warning("No patches generated. No index file will be written.")
213+
214+
215+
if __name__ == "__main__":
216+
main()

‎seal5/flow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
# ("riscv_instr_formats", passes.gen_riscv_instr_formats_patch, {}),
8282
("riscv_register_info", passes.gen_riscv_register_info_patch, {}),
8383
("riscv_instr_info", passes.gen_riscv_instr_info_patch, {}),
84+
("riscv_intrinsics", passes.gen_riscv_intrinsics, {}),
8485
# subtarget_tests
8586
# register_types
8687
# operand_types

‎seal5/pass_list.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,67 @@ def gen_riscv_isa_info_patch(
10931093
logger.warning("No patches found!")
10941094

10951095

1096+
def gen_riscv_intrinsics(
1097+
input_model: str,
1098+
settings: Optional[Seal5Settings] = None,
1099+
env: Optional[dict] = None,
1100+
verbose: bool = False,
1101+
split: bool = False,
1102+
log_level: str = "debug",
1103+
**kwargs,
1104+
):
1105+
assert not split, "TODO"
1106+
# formats = True
1107+
gen_metrics_file = True
1108+
gen_index_file = True
1109+
input_file = settings.models_dir / f"{input_model}.seal5model"
1110+
assert input_file.is_file(), f"File not found: {input_file}"
1111+
name = input_file.name
1112+
new_name = name.replace(".seal5model", "")
1113+
logger.info("Writing intrinsics patches patch for %s", name)
1114+
out_dir = settings.patches_dir / new_name
1115+
out_dir.mkdir(exist_ok=True)
1116+
1117+
args = [
1118+
settings.models_dir / name,
1119+
"--log",
1120+
log_level,
1121+
"--output",
1122+
out_dir / "riscv_intrinsics_info.patch",
1123+
]
1124+
if split:
1125+
args.append("--splitted")
1126+
if gen_metrics_file:
1127+
metrics_file = out_dir / ("riscv_intrinsics_info_metrics.csv")
1128+
args.extend(["--metrics", metrics_file])
1129+
if gen_index_file:
1130+
index_file = out_dir / ("riscv_intrinsics_index.yml")
1131+
args.extend(["--index", index_file])
1132+
utils.python(
1133+
"-m",
1134+
"seal5.backends.riscv_intrinsics.writer",
1135+
*args,
1136+
env=env,
1137+
print_func=logger.info if verbose else logger.debug,
1138+
live=True,
1139+
)
1140+
if gen_index_file:
1141+
if index_file.is_file():
1142+
patch_base = f"riscv_intrinsics_target_{input_file.stem}"
1143+
patch_settings = PatchSettings(
1144+
name=patch_base,
1145+
stage=int(PatchStage.PHASE_1),
1146+
comment=f"Generated RISCV Intrinsics patch for {input_file.name}",
1147+
index=str(index_file),
1148+
generated=True,
1149+
target="llvm",
1150+
)
1151+
settings.add_patch(patch_settings)
1152+
settings.to_yaml_file(settings.settings_file)
1153+
else:
1154+
logger.warning("No patches found!")
1155+
1156+
10961157
def gen_riscv_instr_info_patch(
10971158
input_model: str,
10981159
settings: Optional[Seal5Settings] = None,

0 commit comments

Comments
 (0)