mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
Currently, `lit-magic-strings.inc.h` is manually maintained. This has several drawbacks: - keeping the list of magic strings sorted first by length then alphabetically is error prone, - it is easy to leave unused magic strings in the list by accident (e.g., `LIT_MAGIC_STRING_JERRY_UL` is defined as a magic string but not used anywhere in the code) and, - it is very hard to add `CONFIG_DISABLE_*_BUILTIN` guards to the list, even though there are several magic strings, which are used in some of the configurations only (e.g, "setPrototypeOf" is used in ES2015 only). To ease the maintenance of magic strings, this commit moves the definition of magic strings to a config file (`lit-magic-strings.ini`), and adds `tools/gen-magic-strings.py` to generate the `.inc.h` file from this config file and from the use cases of the strings in the code. - The magic strings in the config file can appear in any order, the generator will ensure that they are correctly sorted. - The generator skips those definitions that are not used anywhere (and emits a warning to signal that such definitions can be removed). - The generator applies the same guards to the definitions in the `.inc.h` file as found in the code around the use of the strings to optimize for size. The commit also changes some builtin-related `.inc.h` files by adding guards that don't affect functionality but improve the results of the generator. To ensure that the invocation of the generator does not get forgotten, the commit also adds `tools/check-magic-strings.sh` and binds it into the testing infrastructure. JerryScript-DCO-1.0-Signed-off-by: Akos Kiss akiss@inf.u-szeged.hu
290 lines
10 KiB
Python
Executable File
290 lines
10 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright JS Foundation and other contributors, http://js.foundation
|
|
#
|
|
# 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.
|
|
|
|
from __future__ import print_function
|
|
|
|
try:
|
|
from configparser import ConfigParser
|
|
except ImportError:
|
|
from ConfigParser import ConfigParser
|
|
|
|
import argparse
|
|
import fileinput
|
|
import json
|
|
import os
|
|
import re
|
|
|
|
from settings import PROJECT_DIR
|
|
|
|
|
|
MAGIC_STRINGS_INI = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.ini')
|
|
MAGIC_STRINGS_INC_H = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.inc.h')
|
|
|
|
|
|
def debug_dump(obj):
|
|
def deepcopy(obj):
|
|
if isinstance(obj, (list, tuple)):
|
|
return [deepcopy(e) for e in obj]
|
|
if isinstance(obj, set):
|
|
return [repr(e) for e in obj]
|
|
if isinstance(obj, dict):
|
|
return {repr(k): deepcopy(e) for k, e in obj.items()}
|
|
return obj
|
|
return json.dumps(deepcopy(obj), indent=4)
|
|
|
|
|
|
def read_magic_string_defs(debug=False):
|
|
# Read the `jerry-core/lit/lit-magic-strings.ini` file and returns the magic
|
|
# string definitions found therein in the form of
|
|
# [LIT_MAGIC_STRINGS]
|
|
# LIT_MAGIC_STRING_xxx = "vvv"
|
|
# ...
|
|
# as
|
|
# [('LIT_MAGIC_STRING_xxx', 'vvv'), ...]
|
|
# sorted by length and alpha.
|
|
ini_parser = ConfigParser()
|
|
ini_parser.optionxform = str # case sensitive options (magic string IDs)
|
|
ini_parser.read(MAGIC_STRINGS_INI)
|
|
|
|
defs = [(str_ref, json.loads(str_value) if str_value != '' else '')
|
|
for str_ref, str_value in ini_parser.items('LIT_MAGIC_STRINGS')]
|
|
defs = sorted(defs, key=lambda ref_value: (len(ref_value[1]), ref_value[1]))
|
|
|
|
if debug:
|
|
print('debug: magic string definitions: {dump}'
|
|
.format(dump=debug_dump(defs)))
|
|
|
|
return defs
|
|
|
|
|
|
def extract_magic_string_refs(debug=False):
|
|
results = {}
|
|
|
|
def process_line(fname, lnum, line, guard_stack):
|
|
# Build `results` dictionary as
|
|
# results['LIT_MAGIC_STRING_xxx'][('!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...)]
|
|
# = [('zzz.c', 123), ...]
|
|
# meaning that the given literal is referenced under the given guards at
|
|
# the listed (file, line number) locations.
|
|
for str_ref in re.findall('LIT_MAGIC_STRING_[a-zA-Z0-9_]+', line):
|
|
if str_ref in ['LIT_MAGIC_STRING_DEF',
|
|
'LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE',
|
|
'LIT_MAGIC_STRING_LENGTH_LIMIT',
|
|
'LIT_MAGIC_STRING__COUNT']:
|
|
continue
|
|
|
|
guard_set = set()
|
|
for guards in guard_stack:
|
|
guard_set.update(guards)
|
|
guard_tuple = tuple(sorted(guard_set))
|
|
|
|
if str_ref not in results:
|
|
results[str_ref] = {}
|
|
str_guards = results[str_ref]
|
|
|
|
if guard_tuple not in str_guards:
|
|
str_guards[guard_tuple] = []
|
|
file_list = str_guards[guard_tuple]
|
|
|
|
file_list.append((fname, lnum))
|
|
|
|
def process_guard(guard):
|
|
# Transform `#ifndef MACRO` to `#if !defined (MACRO)` and
|
|
# `#ifdef MACRO` to `#if defined (MACRO)` to enable or-ing/and-ing the
|
|
# conditions later on.
|
|
if guard.startswith('ndef '):
|
|
guard = guard.replace('ndef ', '!defined (', 1) + ')'
|
|
elif guard.startswith('def '):
|
|
guard = guard.replace('def ', 'defined (', 1) + ')'
|
|
return guard
|
|
|
|
def process_file(fname):
|
|
# Builds `guard_stack` list for each line of a file as
|
|
# [['!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...], ...]
|
|
# meaning that all the listed guards (conditionals) have to hold for the
|
|
# line to be kept by the preprocessor.
|
|
guard_stack = []
|
|
|
|
for line in fileinput.input(fname):
|
|
if_match = re.match('^# *if(.*)', line)
|
|
elif_match = re.match('^# *elif(.*)', line)
|
|
else_match = re.match('^# *else', line)
|
|
endif_match = re.match('^# *endif', line)
|
|
if if_match is not None:
|
|
guard_stack.append([process_guard(if_match.group(1))])
|
|
elif elif_match is not None:
|
|
guards = guard_stack[-1]
|
|
guards[-1] = '!(%s)' % guards[-1]
|
|
guards.append(process_guard(elif_match.group(1)))
|
|
elif else_match is not None:
|
|
guards = guard_stack[-1]
|
|
guards[-1] = '!(%s)' % guards[-1]
|
|
elif endif_match is not None:
|
|
guard_stack.pop()
|
|
|
|
lnum = fileinput.filelineno()
|
|
process_line(fname, lnum, line, guard_stack)
|
|
|
|
if guard_stack:
|
|
print('warning: {fname}: unbalanced preprocessor conditional '
|
|
'directives (analysis finished with no closing `#endif` '
|
|
'for {guard_stack})'
|
|
.format(fname=fname, guard_stack=guard_stack))
|
|
|
|
for root, _, files in os.walk(os.path.join(PROJECT_DIR, 'jerry-core')):
|
|
for fname in files:
|
|
if (fname.endswith('.c') or fname.endswith('.h')) \
|
|
and fname != 'lit-magic-strings.inc.h':
|
|
process_file(os.path.join(root, fname))
|
|
|
|
if debug:
|
|
print('debug: magic string references: {dump}'
|
|
.format(dump=debug_dump(results)))
|
|
|
|
return results
|
|
|
|
|
|
def calculate_magic_string_guards(defs, uses, debug=False):
|
|
extended_defs = []
|
|
|
|
for str_ref, str_value in defs:
|
|
if str_ref not in uses:
|
|
print('warning: unused magic string {str_ref}'
|
|
.format(str_ref=str_ref))
|
|
continue
|
|
|
|
# Calculate the most compact guard, i.e., if a magic string is
|
|
# referenced under various guards, keep the one that is more generic.
|
|
# E.g.,
|
|
# guard1 = A and B and C and D and E and F
|
|
# guard2 = A and B and C
|
|
# then guard1 or guard2 == guard2.
|
|
guards = [set(guard_tuple) for guard_tuple in uses[str_ref].keys()]
|
|
for i, guard_i in enumerate(guards):
|
|
if guard_i is None:
|
|
continue
|
|
for j, guard_j in enumerate(guards):
|
|
if j == i or guard_j is None:
|
|
continue
|
|
if guard_i < guard_j:
|
|
guards[j] = None
|
|
guards = {tuple(sorted(guard)) for guard in guards if guard is not None}
|
|
|
|
extended_defs.append((str_ref, str_value, guards))
|
|
|
|
if debug:
|
|
print('debug: magic string definitions (with guards): {dump}'
|
|
.format(dump=debug_dump(extended_defs)))
|
|
|
|
return extended_defs
|
|
|
|
|
|
def guards_to_str(guards):
|
|
return ' \\\n|| '.join(' && '.join(g for g in sorted(guard))
|
|
for guard in sorted(guards))
|
|
|
|
|
|
def generate_header(gen_file):
|
|
header = \
|
|
"""/* Copyright JS Foundation and other contributors, http://js.foundation
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/* This file is automatically generated by the %s script
|
|
* from %s. Do not edit! */
|
|
""" % (os.path.basename(__file__), os.path.basename(MAGIC_STRINGS_INI))
|
|
print(header, file=gen_file)
|
|
|
|
|
|
def generate_magic_string_defs(gen_file, defs):
|
|
print(file=gen_file) # empty line separator
|
|
|
|
last_guards = set([()])
|
|
for str_ref, str_value, guards in defs:
|
|
if last_guards != guards:
|
|
if () not in last_guards:
|
|
print('#endif', file=gen_file)
|
|
if () not in guards:
|
|
print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
|
|
|
|
print('LIT_MAGIC_STRING_DEF ({str_ref}, {str_value})'
|
|
.format(str_ref=str_ref, str_value=json.dumps(str_value)), file=gen_file)
|
|
|
|
last_guards = guards
|
|
|
|
if () not in last_guards:
|
|
print('#endif', file=gen_file)
|
|
|
|
|
|
def generate_first_magic_strings(gen_file, defs):
|
|
print(file=gen_file) # empty line separator
|
|
|
|
max_size = len(defs[-1][1])
|
|
for size in range(max_size + 1):
|
|
last_guards = set([()])
|
|
for str_ref, str_value, guards in defs:
|
|
if len(str_value) >= size:
|
|
if () not in guards and () in last_guards:
|
|
print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
|
|
elif () not in guards and () not in last_guards:
|
|
if guards == last_guards:
|
|
continue
|
|
print('#elif {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
|
|
elif () in guards and () not in last_guards:
|
|
print('#else', file=gen_file)
|
|
|
|
print('LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE ({size}, {str_ref})'
|
|
.format(size=size, str_ref=str_ref), file=gen_file)
|
|
|
|
if () in guards:
|
|
break
|
|
|
|
last_guards = guards
|
|
|
|
if () not in last_guards:
|
|
print('#endif', file=gen_file)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='lit-magic-strings.inc.h generator')
|
|
parser.add_argument('--debug', action='store_true', help='enable debug output')
|
|
args = parser.parse_args()
|
|
|
|
defs = read_magic_string_defs(debug=args.debug)
|
|
uses = extract_magic_string_refs(debug=args.debug)
|
|
|
|
extended_defs = calculate_magic_string_guards(defs, uses, debug=args.debug)
|
|
|
|
with open(MAGIC_STRINGS_INC_H, 'w') as gen_file:
|
|
generate_header(gen_file)
|
|
generate_magic_string_defs(gen_file, extended_defs)
|
|
generate_first_magic_strings(gen_file, extended_defs)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|