xref: /netbsd-src/external/gpl3/gdb/dist/sim/common/gennltvals.py (revision 88241920d21b339bf319c0e979ffda80c49a2936)
1#!/usr/bin/env python3
2# Copyright (C) 1996-2024 Free Software Foundation, Inc.
3#
4# This file is part of the GNU simulators.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19"""Helper to generate target-newlib-* files.
20
21target-newlib-* are files that describe various newlib/libgloss values used
22by the host/target interface.  This needs to be rerun whenever the newlib source
23changes.  Developers manually run it.
24
25If the path to newlib is not specified, it will be searched for in:
26- the root of this source tree
27- alongside this source tree
28"""
29
30import argparse
31from pathlib import Path
32import re
33import subprocess
34import sys
35from typing import Iterable, List, TextIO
36
37
38PROG = Path(__file__).name
39
40# Unfortunately, many newlib/libgloss ports have seen fit to define their own
41# syscall.h file.  This means that system call numbers can vary for each port.
42# Support for all this crud is kept here, rather than trying to get too fancy.
43# If you want to try to improve this, please do, but don't break anything.
44#
45# If a target isn't listed here, it gets the standard syscall.h file (see
46# libgloss/syscall.h) which hopefully new targets will use.
47#
48# NB: New ports should use libgloss, not newlib.
49TARGET_DIRS = {
50    'cr16': 'libgloss/cr16/sys',
51    'd10v': 'newlib/libc/sys/d10v/sys',
52    # Port removed from the tree years ago.
53    #'i960': 'libgloss/i960',
54    'mcore': 'libgloss/mcore',
55    'riscv': 'libgloss/riscv/machine',
56    'sh': 'newlib/libc/sys/sh/sys',
57    'v850': 'libgloss/v850/sys',
58}
59
60
61# The header for the generated def file.
62FILE_HEADER = f"""\
63/* Newlib/libgloss macro values needed by remote target support.  */
64/* This file is machine generated by {PROG}.  */\
65"""
66
67# Used to update sections of files.
68START_MARKER = 'gennltvals: START'
69END_MARKER = 'gennltvals: END'
70
71
72def extract_syms(cpp: str, srcdir: Path,
73                 headers: Iterable[str],
74                 pattern: str,
75                 filter: str = r'^$') -> dict:
76    """Extract all the symbols from |headers| matching |pattern| using |cpp|."""
77    srcfile = ''.join(f'#include <{x}>\n' for x in headers)
78    syms = set()
79    define_pattern = re.compile(r'^#\s*define\s+(' + pattern + ')')
80    filter_pattern = re.compile(filter)
81    for header in headers:
82        with open(srcdir / header, 'r', encoding='utf-8') as fp:
83            data = fp.read()
84        for line in data.splitlines():
85            m = define_pattern.match(line)
86            if m and not filter_pattern.search(line):
87                syms.add(m.group(1))
88    for sym in syms:
89        srcfile += f'#ifdef {sym}\nDEFVAL "{sym}" {sym}\n#endif\n'
90
91    result = subprocess.run(
92        f'{cpp} -E -I"{srcdir}" -', shell=True, check=True, encoding='utf-8',
93        input=srcfile, capture_output=True)
94    ret = {}
95    for line in result.stdout.splitlines():
96        if line.startswith('DEFVAL '):
97            _, sym, val = line.split()
98            ret[sym.strip('"')] = val
99    return ret
100
101
102def gentvals(output_dir: Path,
103             cpp: str, srctype: str, srcdir: Path,
104             headers: Iterable[str],
105             pattern: str,
106             filter: str = r'^$',
107             target: str = None):
108    """Extract constants from the specified files using a regular expression.
109
110    We'll run things through the preprocessor.
111    """
112    headers = tuple(headers)
113
114    # Require all files exist in order to regenerate properly.
115    for header in headers:
116        fullpath = srcdir / header
117        assert fullpath.exists(), f'{fullpath} does not exist'
118
119    syms = extract_syms(cpp, srcdir, headers, pattern, filter)
120
121    target_map = output_dir / f'target-newlib-{srctype}.c'
122    assert target_map.exists(), f'{target_map}: Missing skeleton'
123    old_lines = target_map.read_text().splitlines()
124    start_i = end_i = None
125    for i, line in enumerate(old_lines):
126        if START_MARKER in line:
127            start_i = i
128        if END_MARKER in line:
129            end_i = i
130    assert start_i and end_i
131    new_lines = old_lines[0:start_i + 1]
132    new_lines.extend(
133        f'#ifdef {sym}\n'
134        f'  {{ "{sym}", {sym}, {val} }},\n'
135        f'#endif' for sym, val in sorted(syms.items()))
136    new_lines.extend(old_lines[end_i:])
137    target_map.write_text('\n'.join(new_lines) + '\n')
138
139
140def gen_common(output_dir: Path, newlib: Path, cpp: str):
141    """Generate the common C library constants.
142
143    No arch should override these.
144    """
145    # Enable Linux errno extensions since the newlib values are designed to
146    # not conflict with each other.
147    gentvals(output_dir,
148             cpp + ' -D__LINUX_ERRNO_EXTENSIONS__',
149             'errno', newlib / 'newlib/libc/include',
150             ('errno.h', 'sys/errno.h'), 'E[A-Z0-9]*')
151
152    gentvals(output_dir, cpp, 'signal', newlib / 'newlib/libc/include',
153             ('signal.h', 'sys/signal.h'), r'SIG[A-Z0-9]*', filter=r'SIGSTKSZ')
154
155    gentvals(output_dir, cpp, 'open', newlib / 'newlib/libc/include',
156             ('fcntl.h', 'sys/fcntl.h', 'sys/_default_fcntl.h'), r'O_[A-Z0-9]*')
157
158
159def gen_target_syscall(output_dir: Path, newlib: Path, cpp: str):
160    """Generate the target-specific syscall lists."""
161    target_map_c = output_dir / 'target-newlib-syscall.c'
162    old_lines_c = target_map_c.read_text().splitlines()
163    start_i = end_i = None
164    for i, line in enumerate(old_lines_c):
165        if START_MARKER in line:
166            start_i = i
167        if END_MARKER in line:
168            end_i = i
169    assert start_i and end_i, f'{target_map_c}: Unable to find markers'
170    new_lines_c = old_lines_c[0:start_i + 1]
171    new_lines_c_end = old_lines_c[end_i:]
172
173    target_map_h = output_dir / 'target-newlib-syscall.h'
174    old_lines_h = target_map_h.read_text().splitlines()
175    start_i = end_i = None
176    for i, line in enumerate(old_lines_h):
177        if START_MARKER in line:
178            start_i = i
179        if END_MARKER in line:
180            end_i = i
181    assert start_i and end_i, f'{target_map_h}: Unable to find markers'
182    new_lines_h = old_lines_h[0:start_i + 1]
183    new_lines_h_end = old_lines_h[end_i:]
184
185    headers = ('syscall.h',)
186    pattern = r'SYS_[_a-zA-Z0-9]*'
187
188    # Output the target-specific syscalls.
189    for target, subdir in sorted(TARGET_DIRS.items()):
190        syms = extract_syms(cpp, newlib / subdir, headers, pattern)
191        new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_{target}_syscall_map[] = {{')
192        new_lines_c.extend(
193            f'#ifdef CB_{sym}\n'
194            '  { '
195            f'"{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{target.upper()}_{sym}'
196            ' },\n'
197            '#endif' for sym in sorted(syms))
198        new_lines_c.append('  {NULL, -1, -1},')
199        new_lines_c.append('};\n')
200
201        new_lines_h.append(
202            f'extern CB_TARGET_DEFS_MAP cb_{target}_syscall_map[];')
203        new_lines_h.extend(
204            f'#define TARGET_NEWLIB_{target.upper()}_{sym} {val}'
205            for sym, val in sorted(syms.items()))
206        new_lines_h.append('')
207
208    # Then output the common syscall targets.
209    syms = extract_syms(cpp, newlib / 'libgloss', headers, pattern)
210    new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_init_syscall_map[] = {{')
211    new_lines_c.extend(
212        f'#ifdef CB_{sym}\n'
213        f'  {{ "{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{sym} }},\n'
214        f'#endif' for sym in sorted(syms))
215    new_lines_c.append('  {NULL, -1, -1},')
216    new_lines_c.append('};')
217
218    new_lines_h.append('extern CB_TARGET_DEFS_MAP cb_init_syscall_map[];')
219    new_lines_h.extend(
220        f'#define TARGET_NEWLIB_{sym} {val}'
221        for sym, val in sorted(syms.items()))
222
223    new_lines_c.extend(new_lines_c_end)
224    target_map_c.write_text('\n'.join(new_lines_c) + '\n')
225
226    new_lines_h.extend(new_lines_h_end)
227    target_map_h.write_text('\n'.join(new_lines_h) + '\n')
228
229
230def gen_targets(output_dir: Path, newlib: Path, cpp: str):
231    """Generate the target-specific lists."""
232    gen_target_syscall(output_dir, newlib, cpp)
233
234
235def gen(output_dir: Path, newlib: Path, cpp: str):
236    """Generate all the things!"""
237    gen_common(output_dir, newlib, cpp)
238    gen_targets(output_dir, newlib, cpp)
239
240
241def get_parser() -> argparse.ArgumentParser:
242    """Get CLI parser."""
243    parser = argparse.ArgumentParser(
244        description=__doc__,
245        formatter_class=argparse.RawDescriptionHelpFormatter)
246    parser.add_argument(
247        '-o', '--output', type=Path,
248        help='write to the specified directory')
249    parser.add_argument(
250        '--cpp', type=str, default='cpp',
251        help='the preprocessor to use')
252    parser.add_argument(
253        '--srcroot', type=Path,
254        help='the root of this source tree')
255    parser.add_argument(
256        'newlib', nargs='?', type=Path,
257        help='path to the newlib+libgloss source tree')
258    return parser
259
260
261def parse_args(argv: List[str]) -> argparse.Namespace:
262    """Process the command line & default options."""
263    parser = get_parser()
264    opts = parser.parse_args(argv)
265
266    if opts.output is None:
267        # Default to where the script lives.
268        opts.output = Path(__file__).resolve().parent
269
270    if opts.srcroot is None:
271        opts.srcroot = Path(__file__).resolve().parent.parent.parent
272    else:
273        opts.srcroot = opts.srcroot.resolve()
274
275    if opts.newlib is None:
276        # Try to find newlib relative to our source tree.
277        if (opts.srcroot / 'newlib').is_dir():
278            # If newlib is manually in the same source tree, use it.
279            if (opts.srcroot / 'libgloss').is_dir():
280                opts.newlib = opts.srcroot
281            else:
282                opts.newlib = opts.srcroot / 'newlib'
283        elif (opts.srcroot.parent / 'newlib').is_dir():
284            # Or see if it's alongside the gdb/binutils repo.
285            opts.newlib = opts.srcroot.parent / 'newlib'
286    if opts.newlib is None or not opts.newlib.is_dir():
287        parser.error('unable to find newlib')
288
289    return opts
290
291
292def main(argv: List[str]) -> int:
293    """The main entry point for scripts."""
294    opts = parse_args(argv)
295
296    gen(opts.output, opts.newlib, opts.cpp)
297    return 0
298
299
300if __name__ == '__main__':
301    sys.exit(main(sys.argv[1:]))
302