aboutsummaryrefslogtreecommitdiff
path: root/scripts/update-ext-cpu-opt
blob: 821d3af1d13b876a928bb1e783b24ab800dc8630 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/usr/bin/env python3
"""
Update QEMU_SUPPORTED_EXTS in scripts/march-to-cpu-opt from QEMU source.

This script parses ISA_EXT_DATA_ENTRY macros from QEMU's target/riscv/cpu.c
and generates the list of supported extensions for march-to-cpu-opt.

Filtering rules:
- Excludes extensions with has_priv_* (no explicit on/off switch)
- Excludes s* extensions (supervisor extensions)
"""

import argparse
import os
import re
import sys


def parse_qemu_cpu_c(qemu_cpu_c_path):
    """Parse ISA_EXT_DATA_ENTRY and MISA_EXT_INFO from QEMU cpu.c file."""
    with open(qemu_cpu_c_path, "r") as f:
        content = f.read()

    extensions = []

    # Match ISA_EXT_DATA_ENTRY(name, version, property)
    isa_ext_pattern = r"ISA_EXT_DATA_ENTRY\((\w+),\s*\w+,\s*(\w+)\)"
    isa_ext_matches = re.findall(isa_ext_pattern, content)

    for ext_name, prop in isa_ext_matches:
        # Skip the macro definition itself (starts with _)
        if ext_name.startswith("_"):
            continue
        # Skip extensions without explicit on/off switch (has_priv_*)
        if prop.startswith("has_priv_"):
            continue
        # Skip supervisor extensions (s*)
        if ext_name.startswith("s"):
            continue
        extensions.append(ext_name)

    # Match MISA_EXT_INFO(_bit, "propname", description)
    # e.g., MISA_EXT_INFO(RVA, "a", "Atomic instructions")
    misa_ext_pattern = r'MISA_EXT_INFO\(\w+,\s*"(\w+)"'
    misa_ext_matches = re.findall(misa_ext_pattern, content)

    # Extensions to skip from MISA_EXT_INFO
    misa_skip = {"i", "e", "g", "s", "u", "h"}
    for ext_name in misa_ext_matches:
        if ext_name not in misa_skip:
            extensions.append(ext_name)

    return sorted(set(extensions))


def generate_ext_set(extensions):
    """Generate the QEMU_SUPPORTED_EXTS set string."""
    lines = []
    lines.append("# QEMU supported extensions list")
    lines.append("# This list is auto-generated by scripts/update-ext-cpu-opt from QEMU source")
    lines.append("# Only includes extensions with explicit on/off switches")
    lines.append("# Excludes: has_priv_* (no switch), s* (supervisor extensions)")
    lines.append("# fmt: off")
    lines.append("QEMU_SUPPORTED_EXTS = {")
    for ext in extensions:
        lines.append('    "{}",'.format(ext))
    lines.append("}")
    lines.append("# fmt: on")
    return "\n".join(lines)


def update_march_to_cpu_opt(march_script_path, new_ext_set):
    """Update the QEMU_SUPPORTED_EXTS in march-to-cpu-opt script."""
    with open(march_script_path, "r") as f:
        content = f.read()

    # Pattern to match the entire QEMU_SUPPORTED_EXTS block
    pattern = (
        r"# QEMU supported extensions list\n"
        r"# This list is auto-generated by scripts/update-ext-cpu-opt from QEMU source\n"
        r"# Only includes extensions with explicit on/off switches\n"
        r"# Excludes: has_priv_\* \(no switch\), s\* \(supervisor extensions\)\n"
        r"# fmt: off\n"
        r"QEMU_SUPPORTED_EXTS = \{[^}]*\}\n"
        r"# fmt: on"
    )

    if not re.search(pattern, content):
        print("Error: Could not find QEMU_SUPPORTED_EXTS block in {}".format(march_script_path))
        print("Make sure the file contains the expected format.")
        return False

    new_content = re.sub(pattern, new_ext_set, content)

    with open(march_script_path, "w") as f:
        f.write(new_content)

    return True


def main():
    parser = argparse.ArgumentParser(
        description="Update QEMU_SUPPORTED_EXTS from QEMU source"
    )
    parser.add_argument(
        "--qemu-src",
        type=str,
        default="qemu",
        help="Path to QEMU source directory (default: qemu)",
    )
    parser.add_argument(
        "--march-script",
        type=str,
        default="scripts/march-to-cpu-opt",
        help="Path to march-to-cpu-opt script (default: scripts/march-to-cpu-opt)",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print the generated extension list without updating the file",
    )
    args = parser.parse_args()

    qemu_cpu_c = os.path.join(args.qemu_src, "target", "riscv", "cpu.c")

    if not os.path.exists(qemu_cpu_c):
        print("Error: QEMU cpu.c not found at {}".format(qemu_cpu_c))
        return 1

    if not os.path.exists(args.march_script):
        print("Error: march-to-cpu-opt not found at {}".format(args.march_script))
        return 1

    print("Parsing QEMU cpu.c from: {}".format(qemu_cpu_c))
    extensions = parse_qemu_cpu_c(qemu_cpu_c)
    print("Found {} supported extensions".format(len(extensions)))

    ext_set = generate_ext_set(extensions)

    if args.dry_run:
        print("\nGenerated QEMU_SUPPORTED_EXTS:")
        print(ext_set)
        return 0

    print("Updating: {}".format(args.march_script))
    if update_march_to_cpu_opt(args.march_script, ext_set):
        print("Successfully updated QEMU_SUPPORTED_EXTS")
        return 0
    else:
        return 1


if __name__ == "__main__":
    sys.exit(main())