1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8gperf C file post-processor
9
10We use gperf to build up a perfect hashtable of pointer values. The way gperf
11does this is to create a table 'wordlist' indexed by a string representation
12of a pointer address, and then doing memcmp() on a string passed in for
13comparison
14
15We are exclusively working with 4-byte pointer values. This script adjusts
16the generated code so that we work with pointers directly and not strings.
17This saves a considerable amount of space.
18"""
19
20import argparse
21import os
22import re
23import sys
24
25from packaging import version
26
27# --- debug stuff ---
28
29
30def debug(text):
31    if not args.verbose:
32        return
33    sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n")
34
35
36def error(text):
37    sys.exit(os.path.basename(sys.argv[0]) + " ERROR: " + text)
38
39
40def warn(text):
41    sys.stdout.write(os.path.basename(sys.argv[0]) + " WARNING: " + text + "\n")
42
43
44def reformat_str(match_obj):
45    addr_str = match_obj.group(0)
46
47    # Nip quotes
48    addr_str = addr_str[1:-1]
49    addr_vals = [0, 0, 0, 0, 0, 0, 0, 0]
50    ctr = 7
51    i = 0
52
53    while True:
54        if i >= len(addr_str):
55            break
56
57        if addr_str[i] == "\\":
58            if addr_str[i + 1].isdigit():
59                # Octal escape sequence
60                val_str = addr_str[i + 1 : i + 4]
61                addr_vals[ctr] = int(val_str, 8)
62                i += 4
63            else:
64                # Char value that had to be escaped by C string rules
65                addr_vals[ctr] = ord(addr_str[i + 1])
66                i += 2
67
68        else:
69            addr_vals[ctr] = ord(addr_str[i])
70            i += 1
71
72        ctr -= 1
73
74    return f"(char *)0x{bytes(addr_vals).hex()}"
75
76
77def process_line(line, fp):
78    if line.startswith("#"):
79        fp.write(line)
80        return
81
82    # Set the lookup function to static inline so it gets rolled into
83    # k_object_find(), nothing else will use it
84    if re.search(args.pattern + " [*]$", line):
85        fp.write("static inline " + line)
86        return
87
88    m = re.search("gperf version (.*) [*][/]$", line)
89    if m:
90        v = version.parse(m.groups()[0])
91        v_lo = version.parse("3.0")
92        v_hi = version.parse("3.1")
93        if v < v_lo or v > v_hi:
94            warn(f"gperf {v} is not tested, versions {v_lo} through {v_hi} supported")
95
96    # Replace length lookups with constant len since we're always
97    # looking at pointers
98    line = re.sub(r'lengthtable\[key\]', r'sizeof(void *)', line)
99
100    # Empty wordlist entries to have NULLs instead of ""
101    line = re.sub(r'[{]["]["][}]', r'{}', line)
102
103    # Suppress a compiler warning since this table is no longer necessary
104    line = re.sub(
105        r'static unsigned char lengthtable', r'static unsigned char __unused lengthtable', line
106    )
107
108    # drop all use of register keyword, let compiler figure that out,
109    # we have to do this since we change stuff to take the address of some
110    # parameters
111    line = re.sub(r'register', r'', line)
112
113    # Hashing the address of the string
114    line = re.sub(r"hash [(]str, len[)]", r"hash((const char *)&str, len)", line)
115
116    # Just compare pointers directly instead of using memcmp
117    if re.search("if [(][*]str", line):
118        fp.write("            if (str == s)\n")
119        return
120
121    # Take the strings with the binary information for the pointer values,
122    # and just turn them into pointers
123    line = re.sub(r'["].*["]', reformat_str, line)
124
125    # Use a bigger data type for the asso_values table to provide some margin
126    line = re.sub(r'char asso_values', r'short asso_values', line)
127
128    fp.write(line)
129
130
131def parse_args():
132    global args
133
134    parser = argparse.ArgumentParser(
135        description=__doc__,
136        formatter_class=argparse.RawDescriptionHelpFormatter,
137        allow_abbrev=False,
138    )
139
140    parser.add_argument("-i", "--input", required=True, help="Input C file from gperf")
141    parser.add_argument("-o", "--output", required=True, help="Output C file with processing done")
142    parser.add_argument("-p", "--pattern", required=True, help="Search pattern for objects")
143    parser.add_argument(
144        "-v", "--verbose", action="store_true", help="Print extra debugging information"
145    )
146    args = parser.parse_args()
147    if "VERBOSE" in os.environ:
148        args.verbose = 1
149
150
151def main():
152    parse_args()
153
154    with open(args.input) as in_fp, open(args.output, "w") as out_fp:
155        for line in in_fp.readlines():
156            process_line(line, out_fp)
157
158
159if __name__ == "__main__":
160    main()
161