mesa/src/nouveau/compiler/latencies/lat_rs_gen.py

209 lines
6.2 KiB
Python

#! /usr/bin/env python3
#
# Copyright © 2024 Collabora Ltd. and Red Hat Inc.
# SPDX-License-Identifier: MIT
# This script takes a list of Rust files, each of the form nvh_path_to_mod.rs
# and constructs a lib.rs which puts each of them in ::path::to::mod.
import argparse
import csv
import os
import sys
from mako import template
TEMPLATE_RS = template.Template(text="""\
// Copyright 2024 Red Hat Inc.
// SPDX-License-Identifier: MIT
// This file is generated by lat_rs_gen.py. DO NOT EDIT!
#![allow(unused_variables)]
const fn pred(has_pred: bool, a: u32, b: u32) -> u32 {
if has_pred {
a + b
} else {
b
}
}
% for reg_file, cats in file_cats.items():
<% enum_name = to_camel(reg_file) + 'Latency' + sm.upper() %>
#[derive(PartialEq)]
pub enum ${enum_name} {
% for category in cats[0].header.cats:
${to_camel(category)},
% endfor
}
impl ${to_camel(reg_file)}Latency${sm.upper()} {
% for bigcat in cats:
pub fn ${bigcat.header.latcat}(
${bigcat.header.cat0}: ${enum_name},
${bigcat.header.cat1}: ${enum_name},
has_pred: bool
) -> u32 {
use ${enum_name}::*;
match ${bigcat.header.cat1} {
% for cat in bigcat.header.cats:
#[allow(clippy::match_single_binding)]
${to_camel(cat)} => match ${bigcat.header.cat0} {
<% has_non = False %>
% for cat2 in bigcat.header.cats:
% if bigcat.fields[loop.parent.index].flds[cat2].pred == True:
${to_camel(cat2)} => pred(
has_pred,
${bigcat.fields[loop.parent.index].flds[cat2].value},
${bigcat.fields[loop.parent.index].flds[cat2].pred_val}
),
% elif bigcat.fields[loop.parent.index].flds[cat2].value != "none":
${to_camel(cat2)} => ${bigcat.fields[loop.parent.index].flds[cat2].value},
% else:
<% has_none = True %>
% endif
% endfor
% if has_none:
_ => panic!("Illegal ${bigcat.header.cat0} value in ${bigcat.header.latcat} for ${to_camel(cat)}"),
% endif
}
% endfor
}
}
% endfor
}
% endfor
""")
## A mere convenience to convert snake_case to CamelCase. Numbers are prefixed
## with "_".
def to_camel(snake_str):
result = ''.join(word.title() for word in snake_str.split('_'))
return result if not result[0].isdigit() else '_' + result
def reader(csvfile):
"""Wrapper around csv.reader that skips comments and blanks."""
# csv.reader actually reads the file one line at a time (it was designed to
# open excel generated sheets), so hold the file until all of the lines are
# read.
with open(csvfile, 'r') as f:
for line in csv.reader(f):
if line and not line[0].startswith('#'):
yield line
class Fld(object):
def __init__(self, line):
if "none" in line:
self.valid = False
else:
self.valid = True
self.pred = False
if "+" in line:
self.pred = True
part = line.split("+")
self.value = part[0]
self.pred_val = part[1]
elif " & sb" in line:
self.scoreboard = True
self.value = line.removesuffix(" & sb");
else:
self.scoreboard = False
self.value = line.strip()
class Header(object):
def __init__(self, line):
self.latcat = line[0].strip()
self.cats = line[1:]
if self.latcat == "raw":
self.cat0 = "writer"
self.cat1 = "reader"
elif self.latcat == "war":
self.cat0 = "reader"
self.cat1 = "writer"
elif self.latcat == "waw":
self.cat0 = "writer1"
self.cat1 = "writer2"
class Fields(object):
def __init__(self, header, line):
self.fldcat = line[0].strip()
self.flds = {}
for index, cat in enumerate(header.cats):
self.flds[cat] = Fld(line[index + 1])
class Category(object):
def __init__(self, header, fields):
self.header = header
self.fields = fields
lattypes = ["reg", "ureg", "pred", "upred"]
def emit_cats(dirname, f, sm, lat):
cats = []
for index, cat in enumerate(["raw", "war", "waw"]):
first_line = False
fields = []
for l in reader(dirname + "sm" + sm + "/" + lat + "_" + cat + ".csv"):
if first_line == False:
header = Header(l)
first_line = True
else:
fields.append(Fields(header, l))
cats.append(Category(header, fields))
try:
f.write(TEMPLATE_RS.render(lat=lat, sm=sm, cats=cats))
except Exception:
# In the event there's an error, this imports some helpers from mako
# to print a useful stack trace and prints it, then exits with
# status 1, if python is run with debug; otherwise it just raises
# the exception
import sys
from mako import exceptions
print(exceptions.text_error_template().render(), file=sys.stderr)
sys.exit(1)
def emit_sm(dirname, f, sm):
for lat in lattypes:
emit_cats(dirname, f, sm, lat)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--import-path', required=True)
parser.add_argument('--out-rs', required=True, help='Output Rust file.')
parser.add_argument('--sm', help='SM', required=True)
parser.add_argument('csv_files', metavar='FILE', nargs='*',
action='append',
help='Input CSV filename')
args = parser.parse_args()
sys.path.insert(0, args.import_path)
import util
file_cats = {}
for csv_file in args.csv_files[0]:
split = os.path.basename(csv_file).removesuffix('.csv').split('_')
assert len(split) == 2
reg_file = split[0]
latcat = split[1]
r = reader(csv_file)
header = Header(next(r))
fields = [Fields(header, l) for l in r]
cat = Category(header, fields)
file_cats.setdefault(reg_file, []).append(cat)
environment = dict(
sm=args.sm,
file_cats=file_cats,
to_camel=to_camel,
)
util.write_template_rs(args.out_rs, TEMPLATE_RS, environment)
if __name__ == '__main__':
main()