blob: fc699432a08cf705a50b64c2aada30f38bcbf24e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2021, Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import tabulate as tbl
import argparse
import csv
import doctest
def quote_literal(val):
if len(val) > 0:
return f"``{val}``"
return val
rst_header_levels = ['=', '~', '-', '_', '@']
__CSV_SECTION_PREFIX = '<SECTION>'
__CSV_COMMENT_PREFIX = '<COMMENT>'
__CSV_HEADER_PREFIX = '<HEADER>'
__intrinsic_table_header = ['Intrinsic', 'Argument preparation',
'AArch64 Instruction', 'Result', 'Supported architectures']
__INTRINSIC_TABLE_KEYWORD = '__intrinsic_table'
__SECTION_TEXT_KEYWORD = '__section_text'
__SECTION_KEYWORDS = [__INTRINSIC_TABLE_KEYWORD, __SECTION_TEXT_KEYWORD]
def rst_literal_quote(mapping):
r"""
>>> rst_literal_quote('a; b')
' ::\n\n a \n b \n\n'
>>> rst_literal_quote('a b')
' ::\n\n a b \n\n'
>>> rst_literal_quote('')
''
"""
if mapping == "":
return ""
lines = mapping.split(';')
indented_lines = [f" {line.strip()} " for line in lines]
lines = [" ::", ""] + indented_lines + ["\n"]
return '\n'.join(lines)
def quote_split_intrinsics(intrinsic):
r"""
>>> quote_split_intrinsics('int f(int x, float y)')
'.. code:: c\n\n int f(\n int x,\n float y)'
>>> quote_split_intrinsics('int f(int x)')
'.. code:: c\n\n int f(int x)\n'
"""
# Remove the suffix ')' from the intrinsic string.
intrinsic_without_ending = intrinsic[:-1]
ret_def, par, signature = intrinsic_without_ending.partition('(')
split_signature = signature.split(',')
if len(split_signature) > 1:
return f".. code:: c\n\n {ret_def}(\n " + ',\n '.join(split_signature) + ")"
else:
return f".. code:: c\n\n {intrinsic}\n"
def get_intrinsic_name(signature):
"""
Get the intrinsic name from the signature of the intrinsic.
>>> get_intrinsic_name("int8x8_t vadd_s8(int8x8_t a, int8x8_t b)")
'vadd_s8'
>>> get_intrinsic_name("int32x4_t vaddl_high_s16(int16x8_t a, int16x8_t b)")
'vaddl_high_s16'
>>> get_intrinsic_name("float64x2_t vfmsq_lane_f64(float64x2_t a, float64x2_t b, float64x1_t v, __builtin_constant_p(lane))")
'vfmsq_lane_f64'
>>> get_intrinsic_name("poly16x8_t vsriq_n_p16(poly16x8_t a, poly16x8_t b, __builtin_constant_p(n))")
'vsriq_n_p16'
>>> get_intrinsic_name("uint8x16_t [__arm_]vddupq_m[_n_u8](uint8x16_t inactive, uint32_t a, const int imm, mve_pred16_t p)")
'[__arm_]vddupq_m[_n_u8]'
"""
tmp = signature.split(' ')
tmp = tmp[1].split('(')
return tmp[0]
def clear_builtin_constant(s):
"""
>>> clear_builtin_constant("__builtin_constant_p(lane)")
'const int lane'
>>> clear_builtin_constant("__builtin_constant_p(n)")
'const int n'
>>> clear_builtin_constant("__builtin_constant_p(lane1)")
'const int lane1'
>>> clear_builtin_constant("__builtin_constant_p(lane2)")
'const int lane2'
>>> clear_builtin_constant("__builtin_constant_p(x), __builtin_constant_p(y)")
'const int x, const int y'
"""
import re
return re.sub(r'__builtin_constant_p\(([a-zA-Z0-9]+)\)', r'const int \1', s)
class Intrinsic:
def __init__(self, signature, parameter_mapping, asm, result_mapping, arch, classification):
self.signature = clear_builtin_constant(signature.strip())
self.parameter_mapping = parameter_mapping
self.asm = asm.strip()
self.result_mapping = result_mapping
self.arch = arch.strip()
self.asm_mnemonic = self.asm.split(' ')[0].strip()
self.classification = classification
def table_row(self):
return [quote_split_intrinsics(self.signature), rst_literal_quote(self.parameter_mapping), rst_literal_quote(self.asm), rst_literal_quote(self.result_mapping), quote_literal(self.arch)]
def recurse_set(parent, section_levels, value, object_type_target):
"""Recursively fills the dictinoary `parent` with the keys provided in
`section_levels`. The innermost key is mapped to a list object to which
the value of `value` is appened.
For example, given parent={}, section_levels=['a','b','c'] and value =
'text', it creates the entry
`parent['a']['b']['c']=['text']`. Another invocation with
value='text2' updates the list as
`parent['a']['b']['c']=['text', 'text2']`
>>> import json
>>> jprint = lambda x: print(json.dumps(x, indent = 2))
>>> parent = {}
>>> intrinsic_sample = lambda x : [f'intr{x}', f'arg_prep{x}', f'inst{x}', f'res{x}', f'sup_arch{x}']
>>> recurse_set(parent, ['Section 1'], intrinsic_sample(0), '__intrinsic_table')
>>> jprint(parent)
{
"Section 1": {
"__intrinsic_table": [
[
"intr0",
"arg_prep0",
"inst0",
"res0",
"sup_arch0"
]
]
}
}
>>> recurse_set(parent, ['Section 1'], intrinsic_sample(1), '__intrinsic_table')
>>> jprint(parent)
{
"Section 1": {
"__intrinsic_table": [
[
"intr0",
"arg_prep0",
"inst0",
"res0",
"sup_arch0"
],
[
"intr1",
"arg_prep1",
"inst1",
"res1",
"sup_arch1"
]
]
}
}
>>> recurse_set(parent, ['Section 2', 'Section 2.1'], intrinsic_sample(3), '__intrinsic_table')
>>> recurse_set(parent, ['Section 2', 'Section 2.1'], intrinsic_sample(4), '__intrinsic_table')
>>> recurse_set(parent, ['Section 2', 'Section 2.1'], intrinsic_sample(5), '__intrinsic_table')
>>> jprint(parent)
{
"Section 1": {
"__intrinsic_table": [
[
"intr0",
"arg_prep0",
"inst0",
"res0",
"sup_arch0"
],
[
"intr1",
"arg_prep1",
"inst1",
"res1",
"sup_arch1"
]
]
},
"Section 2": {
"Section 2.1": {
"__intrinsic_table": [
[
"intr3",
"arg_prep3",
"inst3",
"res3",
"sup_arch3"
],
[
"intr4",
"arg_prep4",
"inst4",
"res4",
"sup_arch4"
],
[
"intr5",
"arg_prep5",
"inst5",
"res5",
"sup_arch5"
]
]
}
}
}
>>> recurse_set(parent, ['Section 2', 'Section 2.2'], intrinsic_sample(6), '__intrinsic_table')
>>> jprint(parent)
{
"Section 1": {
"__intrinsic_table": [
[
"intr0",
"arg_prep0",
"inst0",
"res0",
"sup_arch0"
],
[
"intr1",
"arg_prep1",
"inst1",
"res1",
"sup_arch1"
]
]
},
"Section 2": {
"Section 2.1": {
"__intrinsic_table": [
[
"intr3",
"arg_prep3",
"inst3",
"res3",
"sup_arch3"
],
[
"intr4",
"arg_prep4",
"inst4",
"res4",
"sup_arch4"
],
[
"intr5",
"arg_prep5",
"inst5",
"res5",
"sup_arch5"
]
]
},
"Section 2.2": {
"__intrinsic_table": [
[
"intr6",
"arg_prep6",
"inst6",
"res6",
"sup_arch6"
]
]
}
}
}
>>> recurse_set(parent, ['Section 1', 'Section 1.1'], intrinsic_sample(7), '__intrinsic_table')
>>> jprint(parent)
{
"Section 1": {
"__intrinsic_table": [
[
"intr0",
"arg_prep0",
"inst0",
"res0",
"sup_arch0"
],
[
"intr1",
"arg_prep1",
"inst1",
"res1",
"sup_arch1"
]
],
"Section 1.1": {
"__intrinsic_table": [
[
"intr7",
"arg_prep7",
"inst7",
"res7",
"sup_arch7"
]
]
}
},
"Section 2": {
"Section 2.1": {
"__intrinsic_table": [
[
"intr3",
"arg_prep3",
"inst3",
"res3",
"sup_arch3"
],
[
"intr4",
"arg_prep4",
"inst4",
"res4",
"sup_arch4"
],
[
"intr5",
"arg_prep5",
"inst5",
"res5",
"sup_arch5"
]
]
},
"Section 2.2": {
"__intrinsic_table": [
[
"intr6",
"arg_prep6",
"inst6",
"res6",
"sup_arch6"
]
]
}
}
}
>>> recurse_set(parent, ['Section X', 'Section X.X'], 'somethign', '__unhandled_keyword')
Traceback (most recent call last):
...
Exception: Target is unsupported.
>>> new = {}
>>> recurse_set(new, ['Section X'], intrinsic_sample('001'), '__intrinsic_table')
>>> recurse_set(new, ['Section X'], 'Description of Section X', '__section_text')
>>> recurse_set(new, ['Section X', 'Section X.X'], 'somethign', '__section_text')
>>> recurse_set(new, ['Section X', 'Section X.X'], 'Description of Section X.X', '__section_text')
>>> jprint(new)
{
"Section X": {
"__intrinsic_table": [
[
"intr001",
"arg_prep001",
"inst001",
"res001",
"sup_arch001"
]
],
"__section_text": "Description of Section X",
"Section X.X": {
"__section_text": "Description of Section X.X"
}
}
}
"""
if object_type_target not in __SECTION_KEYWORDS:
raise Exception("Target is unsupported.")
current_section, *rest = section_levels
# No more headers to traverse: insert the value under the
# `object_type_target`.
if not rest:
if object_type_target == __INTRINSIC_TABLE_KEYWORD:
parent[current_section] = parent.get(
current_section, {__INTRINSIC_TABLE_KEYWORD: []})
parent[current_section][__INTRINSIC_TABLE_KEYWORD].append(value)
return
if object_type_target == __SECTION_TEXT_KEYWORD:
parent[current_section] = parent.get(current_section, {})
parent[current_section][__SECTION_TEXT_KEYWORD] = value
return
# If we are still traversing the section headers, we are dealing
# with a new section.
parent.setdefault(current_section, {})
recurse_set(parent[current_section], rest, value, object_type_target)
def start_new_section(item):
"""We start a new section when `key` is not handled in
`__SECTION_KEYWORDS` and `value` is a `dict` object.
"""
key, value = item
return isinstance(value, dict) and key not in __SECTION_KEYWORDS
def is_intrinsic_table(item):
"""
Determines whether a key/value pair is a table of intrinsics.
>>> is_intrinsic_table(('__intrinsic_table', [[1,2,3,4,5],['a', 'b','c', 'd', 'f']]))
True
>>> is_intrinsic_table(('__intrinsic_table', [[1,2,3,4,5],['a', 'b','c', 'd']]))
False
>>> is_intrinsic_table(('__intrinsic_tabl', [[1,2,3,4,5],['a', 'b','c', 'd', 'f']]))
False
"""
key, value = item
return key == __INTRINSIC_TABLE_KEYWORD and isinstance(value, list) and all(len(row) == 5 and isinstance(row, list) for row in value)
def is_section_text(item):
"""
Determines whether a key/value pair is a table of intrinsics.
>>> is_section_text(('__intrinsic_table', [[1,2,3,4,5],['a', 'b','c', 'd', 'f']]))
False
>>> is_section_text(('__section_text', ""))
True
"""
key, value = item
return key == __SECTION_TEXT_KEYWORD
def recurse_print_to_rst(item, section_level_list, headers=__intrinsic_table_header, tablefmt="rst"):
"""
>>> table_item = ('__intrinsic_table', [[1,2,3,4,5], [6,7,8,9, 10]])
>>> print(recurse_print_to_rst(table_item, ['=']))
<BLANKLINE>
=========== ====================== ===================== ======== =========================
Intrinsic Argument preparation AArch64 Instruction Result Supported architectures
=========== ====================== ===================== ======== =========================
1 2 3 4 5
6 7 8 9 10
=========== ====================== ===================== ======== =========================
>>> item = ('New section', {'__intrinsic_table': [[1,2,3,4,5], [6,7,8,9, 10]]})
>>> print(recurse_print_to_rst(item, ['=']))
<BLANKLINE>
New section
===========
<BLANKLINE>
=========== ====================== ===================== ======== =========================
Intrinsic Argument preparation AArch64 Instruction Result Supported architectures
=========== ====================== ===================== ======== =========================
1 2 3 4 5
6 7 8 9 10
=========== ====================== ===================== ======== =========================
>>> item = ('Section 1', {'Section 1.1': {'__intrinsic_table': [[1,2,3,4,5], [6,7,8,9, 10]]}})
>>> print(recurse_print_to_rst(item, ['=','~']))
<BLANKLINE>
Section 1
=========
<BLANKLINE>
Section 1.1
~~~~~~~~~~~
<BLANKLINE>
=========== ====================== ===================== ======== =========================
Intrinsic Argument preparation AArch64 Instruction Result Supported architectures
=========== ====================== ===================== ======== =========================
1 2 3 4 5
6 7 8 9 10
=========== ====================== ===================== ======== =========================
>>> item = ('Section 1', {'Section 1.1': { '__section_text': "Text for Section 1.1", '__intrinsic_table': [[1,2,3,4,5]]}})
>>> print(recurse_print_to_rst(item, ['=','~']))
<BLANKLINE>
Section 1
=========
<BLANKLINE>
Section 1.1
~~~~~~~~~~~
<BLANKLINE>
Text for Section 1.1
<BLANKLINE>
=========== ====================== ===================== ======== =========================
Intrinsic Argument preparation AArch64 Instruction Result Supported architectures
=========== ====================== ===================== ======== =========================
1 2 3 4 5
=========== ====================== ===================== ======== =========================
>>> item = ('Section 1', {'Section 1.1': { '__intrinsic_table': [[1,2,3,4,5]], '__section_text': "Text for Section 1.1" }})
>>> print(recurse_print_to_rst(item, ['=','~']))
<BLANKLINE>
Section 1
=========
<BLANKLINE>
Section 1.1
~~~~~~~~~~~
<BLANKLINE>
Text for Section 1.1
<BLANKLINE>
=========== ====================== ===================== ======== =========================
Intrinsic Argument preparation AArch64 Instruction Result Supported architectures
=========== ====================== ===================== ======== =========================
1 2 3 4 5
=========== ====================== ===================== ======== =========================
"""
key, value = item
if is_intrinsic_table(item):
return "\n" + str(tbl.tabulate(value, headers=headers, tablefmt=tablefmt))
body = ""
if start_new_section(item):
title, *rest = section_level_list
body += f"\n{key}"
body += "\n" + title * len(key)+""
# Print the value of the text for the section right after the
# section title.
if __SECTION_TEXT_KEYWORD in value:
body += "\n\n" + value[__SECTION_TEXT_KEYWORD]
for k, v in value.items():
# Skip the section text because it has already been processed right
# after the section title.
if k == __SECTION_TEXT_KEYWORD:
continue
body += "\n"+recurse_print_to_rst((k, v), rest, headers, tablefmt)
return body
def is_section_command(row):
"""CSV rows are cosidered new section commands if they start with
<SECTION> and consist of at least two columns column.
>>> is_section_command('<SECTION>\tSection name'.split('\t'))
True
>>> is_section_command('<other>\tSection name'.split('\t'))
False
>>> is_section_command(['<SECTION>', 'Section name', 'some more'])
True
"""
return len(row) >= 2 and row[0] == __CSV_SECTION_PREFIX
def is_comment_line(row):
"""CSV rows are cosidered new section commands if they start with
<COMMENT>
>>> is_comment_line('<COMMENT>\tSome text'.split('\t'))
True
>>> is_comment_line('<other>\tSome text'.split('\t'))
False
>>> is_comment_line('Some text'.split('\t'))
False
>>> is_comment_line('<COMMENT>\tSome text\tsome more'.split('\t'))
True
"""
return len(row) >= 2 and row[0] == __CSV_COMMENT_PREFIX
def is_table_header(row):
"""Table headers are 6-columns with the first one being
__CSV_HEADER_PREFIX.
>>> is_table_header(['<HEADER>', '', '', '', '', ''])
True
>>> is_table_header(['<EADER>', '', '', '', '', ''])
False
"""
return len(row) == 6 and row[0] == __CSV_HEADER_PREFIX
def is_intrinsic_definition(row):
return len(row) == 5
def get_section_data(row):
section_text = row[2] if len(row) == 3 else None
return [row[1], section_text]
def process_db(db, classification_db):
"""Processes a list of intrinsics and their mappings to the
classification into a sequence of sections and RST tables.
>>> intrinsics = [
... ['<HEADER>', 'T1', 'T2', 'T3', 'T4', 'T5'],
... ['<SECTION>','Section 1 title', 'Section 1 description.'],
... ['a A01()','a','aa','aaa','aaaa'],
... ['b B01()','b','bb','bbb','bbbb'],
... ['<SECTION>','Section 2 title', 'Section 2 description.'],
... ['c C01()','c','cc','ccc','cccc'],
... ]
>>> classification = {
... 'B01': 'Section 1.1|Section 1.1.1',
... 'C01': 'classX|subclassY'
... }
>>> print(process_db(intrinsics, classification))
<BLANKLINE>
<BLANKLINE>
Section 1 title
===============
<BLANKLINE>
Section 1 description.
<BLANKLINE>
No category
~~~~~~~~~~~
<BLANKLINE>
+-------------+-------+--------+---------+----------+
| T1 | T2 | T3 | T4 | T5 |
+=============+=======+========+=========+==========+
| .. code:: c | :: | :: | :: | ``aaaa`` |
| | | | | |
| a A01() | a | aa | aaa | |
+-------------+-------+--------+---------+----------+
<BLANKLINE>
Section 1.1
~~~~~~~~~~~
<BLANKLINE>
Section 1.1.1
-------------
<BLANKLINE>
+-------------+-------+--------+---------+----------+
| T1 | T2 | T3 | T4 | T5 |
+=============+=======+========+=========+==========+
| .. code:: c | :: | :: | :: | ``bbbb`` |
| | | | | |
| b B01() | b | bb | bbb | |
+-------------+-------+--------+---------+----------+
<BLANKLINE>
Section 2 title
===============
<BLANKLINE>
Section 2 description.
<BLANKLINE>
classX
~~~~~~
<BLANKLINE>
subclassY
---------
<BLANKLINE>
+-------------+-------+--------+---------+----------+
| T1 | T2 | T3 | T4 | T5 |
+=============+=======+========+=========+==========+
| .. code:: c | :: | :: | :: | ``cccc`` |
| | | | | |
| c C01() | c | cc | ccc | |
+-------------+-------+--------+---------+----------+
"""
filtered = {}
section, section_text, table_header = None, None, None
line_num = 0
for row in db:
line_num += 1
# Set section if the line in the file is starting with __CSV_SECTION_PREFIX.
if is_section_command(row):
section, section_text = get_section_data(row)
if section_text:
recurse_set(filtered, [section],
section_text, __SECTION_TEXT_KEYWORD)
print(
f"Activating {__CSV_SECTION_PREFIX} command at line {line_num}, '{section}'", file=sys.stderr)
continue
# Skip comment lines.
if is_comment_line(row):
continue
if is_table_header(row):
# Only one <HEADER> command is allowed.
assert(table_header is None)
# Set the header.
table_header = row[1:]
assert(len(table_header) == 5)
continue
if is_intrinsic_definition(row):
classification = classification_db.get(
get_intrinsic_name(row[0]), "No category")
intrinsic = Intrinsic(
row[0], row[1], row[2], row[3], row[4], classification)
classification_list = intrinsic.classification.split('|')
if section:
classification_list = [section]+classification_list
recurse_set(filtered, classification_list,
intrinsic.table_row(), __INTRINSIC_TABLE_KEYWORD)
continue
print(f"Skipping line {line_num}: row = {row}", file=sys.stderr)
# Make sure that the CSV has at a row that have set the tale
# header.
assert(table_header is not None)
body = ""
for k, v in filtered.items():
body += "\n" + \
recurse_print_to_rst((k, v), rst_header_levels,
headers=table_header, tablefmt="grid")
return body
def get_classification_map(classification_file):
classification_map = {}
with open(classification_file) as csvclassificationfile:
classification_db = csv.reader(csvclassificationfile, delimiter='\t')
classification_map = {}
for row in classification_db:
if len(row) == 2:
classification_map[row[0]] = row[1]
continue
# Skip comment lines.
if is_comment_line(row):
continue
print(
f"{classification_file}: skipping line {classification_db.line_num}: row = {row}", file=sys.stderr)
return classification_map
def read_template(path):
with open(path) as f:
return f.read()
def get_intrinsics_db(path):
with open(path) as csvfile:
return list(csv.reader(csvfile, delimiter='\t'))
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate an RST file for the intrinsics specifications.")
parser.add_argument("--intrinsic-defs", metavar="<path>", type=str,
help="CSV file with the database of the intrinsics.", required=True)
parser.add_argument("--template", metavar="<path>", type=str,
help="Template file for generating the RST of the specificaion.", required=True)
parser.add_argument("--classification", metavar="<path>", type=str,
help="CSV file that map the intrinsics to their classification.", required=True)
parser.add_argument("--outfile", metavar="<path>", type=str,
help="Output file where the RST of the specs is written.", required=True)
cli_args = parser.parse_args()
# We require version 0.8.6 to be able to print multi-line records
# in tables.
if tbl.__version__ < "0.8.6":
print(f"Your version of package tabulate is too old ({tbl.__version__}). "
"Update it to be greater or equal to 0.8.6.", file=sys.stderr)
exit(1)
classification_map = get_classification_map(cli_args.classification)
intrinsics_db = get_intrinsics_db(cli_args.intrinsic_defs)
doc_template = read_template(cli_args.template)
intrinsic_table = process_db(intrinsics_db, classification_map)
rst_output = doc_template.format(intrinsic_table=intrinsic_table)
with (open(cli_args.outfile, 'w')) as f:
f.write(rst_output)
# Always run the unit tests.
doctest.NORMALIZE_WHITESPACE
doctest.testmod()