1#!/usr/bin/env python3
2# Copyright (C) 1996-2023 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    gentvals(output_dir, cpp, 'errno', newlib / 'newlib/libc/include',
146             ('errno.h', 'sys/errno.h'), 'E[A-Z0-9]*')
147
148    gentvals(output_dir, cpp, 'signal', newlib / 'newlib/libc/include',
149             ('signal.h', 'sys/signal.h'), r'SIG[A-Z0-9]*', filter=r'SIGSTKSZ')
150
151    gentvals(output_dir, cpp, 'open', newlib / 'newlib/libc/include',
152             ('fcntl.h', 'sys/fcntl.h', 'sys/_default_fcntl.h'), r'O_[A-Z0-9]*')
153
154
155def gen_target_syscall(output_dir: Path, newlib: Path, cpp: str):
156    """Generate the target-specific syscall lists."""
157    target_map_c = output_dir / 'target-newlib-syscall.c'
158    old_lines_c = target_map_c.read_text().splitlines()
159    start_i = end_i = None
160    for i, line in enumerate(old_lines_c):
161        if START_MARKER in line:
162            start_i = i
163        if END_MARKER in line:
164            end_i = i
165    assert start_i and end_i, f'{target_map_c}: Unable to find markers'
166    new_lines_c = old_lines_c[0:start_i + 1]
167    new_lines_c_end = old_lines_c[end_i:]
168
169    target_map_h = output_dir / 'target-newlib-syscall.h'
170    old_lines_h = target_map_h.read_text().splitlines()
171    start_i = end_i = None
172    for i, line in enumerate(old_lines_h):
173        if START_MARKER in line:
174            start_i = i
175        if END_MARKER in line:
176            end_i = i
177    assert start_i and end_i, f'{target_map_h}: Unable to find markers'
178    new_lines_h = old_lines_h[0:start_i + 1]
179    new_lines_h_end = old_lines_h[end_i:]
180
181    headers = ('syscall.h',)
182    pattern = r'SYS_[_a-zA-Z0-9]*'
183
184    # Output the target-specific syscalls.
185    for target, subdir in sorted(TARGET_DIRS.items()):
186        syms = extract_syms(cpp, newlib / subdir, headers, pattern)
187        new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_{target}_syscall_map[] = {{')
188        new_lines_c.extend(
189            f'#ifdef CB_{sym}\n'
190            '  { '
191            f'"{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{target.upper()}_{sym}'
192            ' },\n'
193            '#endif' for sym in sorted(syms))
194        new_lines_c.append('  {NULL, -1, -1},')
195        new_lines_c.append('};\n')
196
197        new_lines_h.append(
198            f'extern CB_TARGET_DEFS_MAP cb_{target}_syscall_map[];')
199        new_lines_h.extend(
200            f'#define TARGET_NEWLIB_{target.upper()}_{sym} {val}'
201            for sym, val in sorted(syms.items()))
202        new_lines_h.append('')
203
204    # Then output the common syscall targets.
205    syms = extract_syms(cpp, newlib / 'libgloss', headers, pattern)
206    new_lines_c.append(f'CB_TARGET_DEFS_MAP cb_init_syscall_map[] = {{')
207    new_lines_c.extend(
208        f'#ifdef CB_{sym}\n'
209        f'  {{ "{sym[4:]}", CB_{sym}, TARGET_NEWLIB_{sym} }},\n'
210        f'#endif' for sym in sorted(syms))
211    new_lines_c.append('  {NULL, -1, -1},')
212    new_lines_c.append('};')
213
214    new_lines_h.append('extern CB_TARGET_DEFS_MAP cb_init_syscall_map[];')
215    new_lines_h.extend(
216        f'#define TARGET_NEWLIB_{sym} {val}'
217        for sym, val in sorted(syms.items()))
218
219    new_lines_c.extend(new_lines_c_end)
220    target_map_c.write_text('\n'.join(new_lines_c) + '\n')
221
222    new_lines_h.extend(new_lines_h_end)
223    target_map_h.write_text('\n'.join(new_lines_h) + '\n')
224
225
226def gen_targets(output_dir: Path, newlib: Path, cpp: str):
227    """Generate the target-specific lists."""
228    gen_target_syscall(output_dir, newlib, cpp)
229
230
231def gen(output_dir: Path, newlib: Path, cpp: str):
232    """Generate all the things!"""
233    gen_common(output_dir, newlib, cpp)
234    gen_targets(output_dir, newlib, cpp)
235
236
237def get_parser() -> argparse.ArgumentParser:
238    """Get CLI parser."""
239    parser = argparse.ArgumentParser(
240        description=__doc__,
241        formatter_class=argparse.RawDescriptionHelpFormatter)
242    parser.add_argument(
243        '-o', '--output', type=Path,
244        help='write to the specified directory')
245    parser.add_argument(
246        '--cpp', type=str, default='cpp',
247        help='the preprocessor to use')
248    parser.add_argument(
249        '--srcroot', type=Path,
250        help='the root of this source tree')
251    parser.add_argument(
252        'newlib', nargs='?', type=Path,
253        help='path to the newlib+libgloss source tree')
254    return parser
255
256
257def parse_args(argv: List[str]) -> argparse.Namespace:
258    """Process the command line & default options."""
259    parser = get_parser()
260    opts = parser.parse_args(argv)
261
262    if opts.output is None:
263        # Default to where the script lives.
264        opts.output = Path(__file__).resolve().parent
265
266    if opts.srcroot is None:
267        opts.srcroot = Path(__file__).resolve().parent.parent.parent
268    else:
269        opts.srcroot = opts.srcroot.resolve()
270
271    if opts.newlib is None:
272        # Try to find newlib relative to our source tree.
273        if (opts.srcroot / 'newlib').is_dir():
274            # If newlib is manually in the same source tree, use it.
275            if (opts.srcroot / 'libgloss').is_dir():
276                opts.newlib = opts.srcroot
277            else:
278                opts.newlib = opts.srcroot / 'newlib'
279        elif (opts.srcroot.parent / 'newlib').is_dir():
280            # Or see if it's alongside the gdb/binutils repo.
281            opts.newlib = opts.srcroot.parent / 'newlib'
282    if opts.newlib is None or not opts.newlib.is_dir():
283        parser.error('unable to find newlib')
284
285    return opts
286
287
288def main(argv: List[str]) -> int:
289    """The main entry point for scripts."""
290    opts = parse_args(argv)
291
292    gen(opts.output, opts.newlib, opts.cpp)
293    return 0
294
295
296if __name__ == '__main__':
297    sys.exit(main(sys.argv[1:]))
298