2023-12-08 11:48:30 +00:00
|
|
|
"""Generate the list of opcode IDs.
|
|
|
|
|
Reads the instruction definitions from bytecodes.c.
|
|
|
|
|
Writes the IDs to opcode._ids.h by default.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import os.path
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
from analyzer import (
|
|
|
|
|
Analysis,
|
|
|
|
|
Instruction,
|
|
|
|
|
analyze_files,
|
|
|
|
|
)
|
|
|
|
|
from generators_common import (
|
|
|
|
|
DEFAULT_INPUT,
|
|
|
|
|
ROOT,
|
|
|
|
|
write_header,
|
|
|
|
|
)
|
|
|
|
|
from cwriter import CWriter
|
|
|
|
|
from typing import TextIO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_OUTPUT = ROOT / "Include/opcode_ids.h"
|
|
|
|
|
|
|
|
|
|
|
2023-12-12 12:12:17 +00:00
|
|
|
def generate_opcode_header(filenames: list[str], analysis: Analysis, outfile: TextIO) -> None:
|
2023-12-08 11:48:30 +00:00
|
|
|
write_header(__file__, filenames, outfile)
|
|
|
|
|
out = CWriter(outfile, 0, False)
|
|
|
|
|
out.emit("\n")
|
|
|
|
|
instmap: dict[str, int] = {}
|
|
|
|
|
|
|
|
|
|
# 0 is reserved for cache entries. This helps debugging.
|
|
|
|
|
instmap["CACHE"] = 0
|
|
|
|
|
|
|
|
|
|
# 17 is reserved as it is the initial value for the specializing counter.
|
|
|
|
|
# This helps catch cases where we attempt to execute a cache.
|
|
|
|
|
instmap["RESERVED"] = 17
|
|
|
|
|
|
|
|
|
|
# 149 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py
|
|
|
|
|
instmap["RESUME"] = 149
|
|
|
|
|
instmap["INSTRUMENTED_LINE"] = 254
|
|
|
|
|
|
|
|
|
|
instrumented = [
|
|
|
|
|
name for name in analysis.instructions if name.startswith("INSTRUMENTED")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Special case: this instruction is implemented in ceval.c
|
|
|
|
|
# rather than bytecodes.c, so we need to add it explicitly
|
|
|
|
|
# here (at least until we add something to bytecodes.c to
|
|
|
|
|
# declare external instructions).
|
|
|
|
|
instrumented.append("INSTRUMENTED_LINE")
|
|
|
|
|
|
|
|
|
|
specialized: set[str] = set()
|
|
|
|
|
no_arg: list[str] = []
|
|
|
|
|
has_arg: list[str] = []
|
|
|
|
|
|
|
|
|
|
for family in analysis.families.values():
|
|
|
|
|
specialized.update(inst.name for inst in family.members)
|
|
|
|
|
|
|
|
|
|
for inst in analysis.instructions.values():
|
|
|
|
|
name = inst.name
|
|
|
|
|
if name in specialized:
|
|
|
|
|
continue
|
|
|
|
|
if name in instrumented:
|
|
|
|
|
continue
|
|
|
|
|
if inst.properties.oparg:
|
|
|
|
|
has_arg.append(name)
|
|
|
|
|
else:
|
|
|
|
|
no_arg.append(name)
|
|
|
|
|
|
|
|
|
|
# Specialized ops appear in their own section
|
|
|
|
|
# Instrumented opcodes are at the end of the valid range
|
|
|
|
|
min_internal = 150
|
|
|
|
|
min_instrumented = 254 - (len(instrumented) - 1)
|
|
|
|
|
assert min_internal + len(specialized) < min_instrumented
|
|
|
|
|
|
|
|
|
|
next_opcode = 1
|
|
|
|
|
|
|
|
|
|
def add_instruction(name: str) -> None:
|
|
|
|
|
nonlocal next_opcode
|
|
|
|
|
if name in instmap:
|
|
|
|
|
return # Pre-defined name
|
|
|
|
|
while next_opcode in instmap.values():
|
|
|
|
|
next_opcode += 1
|
|
|
|
|
instmap[name] = next_opcode
|
|
|
|
|
next_opcode += 1
|
|
|
|
|
|
|
|
|
|
for name in sorted(no_arg):
|
|
|
|
|
add_instruction(name)
|
|
|
|
|
for name in sorted(has_arg):
|
|
|
|
|
add_instruction(name)
|
|
|
|
|
# For compatibility
|
|
|
|
|
next_opcode = min_internal
|
|
|
|
|
for name in sorted(specialized):
|
|
|
|
|
add_instruction(name)
|
|
|
|
|
next_opcode = min_instrumented
|
|
|
|
|
for name in instrumented:
|
|
|
|
|
add_instruction(name)
|
|
|
|
|
|
|
|
|
|
for op, name in enumerate(sorted(analysis.pseudos), 256):
|
|
|
|
|
instmap[name] = op
|
|
|
|
|
|
|
|
|
|
assert 255 not in instmap.values()
|
|
|
|
|
|
|
|
|
|
out.emit(
|
|
|
|
|
"""#ifndef Py_OPCODE_IDS_H
|
|
|
|
|
#define Py_OPCODE_IDS_H
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
|
extern "C" {
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* Instruction opcodes for compiled code */
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def write_define(name: str, op: int) -> None:
|
|
|
|
|
out.emit(f"#define {name:<38} {op:>3}\n")
|
|
|
|
|
|
|
|
|
|
for op, name in sorted([(op, name) for (name, op) in instmap.items()]):
|
|
|
|
|
write_define(name, op)
|
|
|
|
|
|
|
|
|
|
out.emit("\n")
|
|
|
|
|
write_define("HAVE_ARGUMENT", len(no_arg))
|
|
|
|
|
write_define("MIN_INSTRUMENTED_OPCODE", min_instrumented)
|
|
|
|
|
|
|
|
|
|
out.emit("\n")
|
|
|
|
|
out.emit("#ifdef __cplusplus\n")
|
|
|
|
|
out.emit("}\n")
|
|
|
|
|
out.emit("#endif\n")
|
|
|
|
|
out.emit("#endif /* !Py_OPCODE_IDS_H */\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
arg_parser = argparse.ArgumentParser(
|
|
|
|
|
description="Generate the header file with all opcode IDs.",
|
|
|
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
arg_parser.add_argument(
|
|
|
|
|
"-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
arg_parser.add_argument(
|
|
|
|
|
"input", nargs=argparse.REMAINDER, help="Instruction definition file(s)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
args = arg_parser.parse_args()
|
|
|
|
|
if len(args.input) == 0:
|
|
|
|
|
args.input.append(DEFAULT_INPUT)
|
|
|
|
|
data = analyze_files(args.input)
|
|
|
|
|
with open(args.output, "w") as outfile:
|
|
|
|
|
generate_opcode_header(args.input, data, outfile)
|