Skip to content

Commit 8de59c1

Browse files
authored
gh-102021 : Allow multiple input files for interpreter loop generator (#102022)
The input files no longer use `-i`.
1 parent cb944d0 commit 8de59c1

File tree

6 files changed

+105
-41
lines changed

6 files changed

+105
-41
lines changed

Makefile.pre.in

+2-2
Original file line numberDiff line numberDiff line change
@@ -1485,9 +1485,9 @@ regen-cases:
14851485
PYTHONPATH=$(srcdir)/Tools/cases_generator \
14861486
$(PYTHON_FOR_REGEN) \
14871487
$(srcdir)/Tools/cases_generator/generate_cases.py \
1488-
-i $(srcdir)/Python/bytecodes.c \
14891488
-o $(srcdir)/Python/generated_cases.c.h.new \
1490-
-m $(srcdir)/Python/opcode_metadata.h.new
1489+
-m $(srcdir)/Python/opcode_metadata.h.new \
1490+
$(srcdir)/Python/bytecodes.c
14911491
$(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new
14921492
$(UPDATE_FILE) $(srcdir)/Python/opcode_metadata.h $(srcdir)/Python/opcode_metadata.h.new
14931493

Python/generated_cases.c.h

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/opcode_metadata.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
// This file is generated by Tools/cases_generator/generate_cases.py --metadata
2-
// from Python/bytecodes.c
1+
// This file is generated by Tools/cases_generator/generate_cases.py
2+
// from:
3+
// Python/bytecodes.c
34
// Do not edit!
45

56
#ifndef NEED_OPCODE_TABLES

Tools/cases_generator/generate_cases.py

+87-28
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@
3737
description="Generate the code for the interpreter switch.",
3838
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
3939
)
40-
arg_parser.add_argument(
41-
"-i", "--input", type=str, help="Instruction definitions", default=DEFAULT_INPUT
42-
)
4340
arg_parser.add_argument(
4441
"-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT
4542
)
4643
arg_parser.add_argument(
4744
"-m", "--metadata", type=str, help="Generated metadata", default=DEFAULT_METADATA_OUTPUT
4845
)
46+
arg_parser.add_argument(
47+
"input", nargs=argparse.REMAINDER, help="Instruction definition file(s)"
48+
)
4949

5050

5151
def effect_size(effect: StackEffect) -> tuple[int, str]:
@@ -485,39 +485,45 @@ class MacroInstruction(SuperOrMacroInstruction):
485485
parts: list[Component | parser.CacheEffect]
486486

487487

488+
@dataclasses.dataclass
489+
class OverriddenInstructionPlaceHolder:
490+
name: str
491+
492+
488493
AnyInstruction = Instruction | SuperInstruction | MacroInstruction
489494
INSTR_FMT_PREFIX = "INSTR_FMT_"
490495

491496

492497
class Analyzer:
493498
"""Parse input, analyze it, and write to output."""
494499

495-
filename: str
500+
input_filenames: list[str]
496501
output_filename: str
497502
metadata_filename: str
498-
src: str
499503
errors: int = 0
500504

501-
def __init__(self, filename: str, output_filename: str, metadata_filename: str):
505+
def __init__(self, input_filenames: list[str], output_filename: str, metadata_filename: str):
502506
"""Read the input file."""
503-
self.filename = filename
507+
self.input_filenames = input_filenames
504508
self.output_filename = output_filename
505509
self.metadata_filename = metadata_filename
506-
with open(filename) as f:
507-
self.src = f.read()
508510

509511
def error(self, msg: str, node: parser.Node) -> None:
510512
lineno = 0
513+
filename = "<unknown file>"
511514
if context := node.context:
515+
filename = context.owner.filename
512516
# Use line number of first non-comment in the node
513517
for token in context.owner.tokens[context.begin : context.end]:
514518
lineno = token.line
515519
if token.kind != "COMMENT":
516520
break
517-
print(f"{self.filename}:{lineno}: {msg}", file=sys.stderr)
521+
print(f"{filename}:{lineno}: {msg}", file=sys.stderr)
518522
self.errors += 1
519523

520-
everything: list[parser.InstDef | parser.Super | parser.Macro]
524+
everything: list[
525+
parser.InstDef | parser.Super | parser.Macro | OverriddenInstructionPlaceHolder
526+
]
521527
instrs: dict[str, Instruction] # Includes ops
522528
supers: dict[str, parser.Super]
523529
super_instrs: dict[str, SuperInstruction]
@@ -531,7 +537,31 @@ def parse(self) -> None:
531537
We only want the parser to see the stuff between the
532538
begin and end markers.
533539
"""
534-
psr = parser.Parser(self.src, filename=self.filename)
540+
541+
self.everything = []
542+
self.instrs = {}
543+
self.supers = {}
544+
self.macros = {}
545+
self.families = {}
546+
547+
instrs_idx: dict[str, int] = dict()
548+
549+
for filename in self.input_filenames:
550+
self.parse_file(filename, instrs_idx)
551+
552+
files = " + ".join(self.input_filenames)
553+
print(
554+
f"Read {len(self.instrs)} instructions/ops, "
555+
f"{len(self.supers)} supers, {len(self.macros)} macros, "
556+
f"and {len(self.families)} families from {files}",
557+
file=sys.stderr,
558+
)
559+
560+
def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None:
561+
with open(filename) as file:
562+
src = file.read()
563+
564+
psr = parser.Parser(src, filename=filename)
535565

536566
# Skip until begin marker
537567
while tkn := psr.next(raw=True):
@@ -551,16 +581,27 @@ def parse(self) -> None:
551581

552582
# Parse from start
553583
psr.setpos(start)
554-
self.everything = []
555-
self.instrs = {}
556-
self.supers = {}
557-
self.macros = {}
558-
self.families = {}
559584
thing: parser.InstDef | parser.Super | parser.Macro | parser.Family | None
585+
thing_first_token = psr.peek()
560586
while thing := psr.definition():
561587
match thing:
562588
case parser.InstDef(name=name):
589+
if name in self.instrs:
590+
if not thing.override:
591+
raise psr.make_syntax_error(
592+
f"Duplicate definition of '{name}' @ {thing.context} "
593+
f"previous definition @ {self.instrs[name].inst.context}",
594+
thing_first_token,
595+
)
596+
self.everything[instrs_idx[name]] = OverriddenInstructionPlaceHolder(name=name)
597+
if name not in self.instrs and thing.override:
598+
raise psr.make_syntax_error(
599+
f"Definition of '{name}' @ {thing.context} is supposed to be "
600+
"an override but no previous definition exists.",
601+
thing_first_token,
602+
)
563603
self.instrs[name] = Instruction(thing)
604+
instrs_idx[name] = len(self.everything)
564605
self.everything.append(thing)
565606
case parser.Super(name):
566607
self.supers[name] = thing
@@ -573,14 +614,7 @@ def parse(self) -> None:
573614
case _:
574615
typing.assert_never(thing)
575616
if not psr.eof():
576-
raise psr.make_syntax_error("Extra stuff at the end")
577-
578-
print(
579-
f"Read {len(self.instrs)} instructions/ops, "
580-
f"{len(self.supers)} supers, {len(self.macros)} macros, "
581-
f"and {len(self.families)} families from {self.filename}",
582-
file=sys.stderr,
583-
)
617+
raise psr.make_syntax_error(f"Extra stuff at the end of {filename}")
584618

585619
def analyze(self) -> None:
586620
"""Analyze the inputs.
@@ -879,6 +913,8 @@ def write_stack_effect_functions(self) -> None:
879913
popped_data: list[tuple[AnyInstruction, str]] = []
880914
pushed_data: list[tuple[AnyInstruction, str]] = []
881915
for thing in self.everything:
916+
if isinstance(thing, OverriddenInstructionPlaceHolder):
917+
continue
882918
instr, popped, pushed = self.get_stack_effect_info(thing)
883919
if instr is not None:
884920
popped_data.append((instr, popped))
@@ -907,13 +943,22 @@ def write_function(
907943
write_function("pushed", pushed_data)
908944
self.out.emit("")
909945

946+
def from_source_files(self) -> str:
947+
paths = "\n// ".join(
948+
os.path.relpath(filename, ROOT).replace(os.path.sep, posixpath.sep)
949+
for filename in self.input_filenames
950+
)
951+
return f"// from:\n// {paths}\n"
952+
910953
def write_metadata(self) -> None:
911954
"""Write instruction metadata to output file."""
912955

913956
# Compute the set of all instruction formats.
914957
all_formats: set[str] = set()
915958
for thing in self.everything:
916959
match thing:
960+
case OverriddenInstructionPlaceHolder():
961+
continue
917962
case parser.InstDef():
918963
format = self.instrs[thing.name].instr_fmt
919964
case parser.Super():
@@ -928,8 +973,8 @@ def write_metadata(self) -> None:
928973

929974
with open(self.metadata_filename, "w") as f:
930975
# Write provenance header
931-
f.write(f"// This file is generated by {THIS} --metadata\n")
932-
f.write(f"// from {os.path.relpath(self.filename, ROOT).replace(os.path.sep, posixpath.sep)}\n")
976+
f.write(f"// This file is generated by {THIS}\n")
977+
f.write(self.from_source_files())
933978
f.write(f"// Do not edit!\n")
934979

935980
# Create formatter; the rest of the code uses this
@@ -959,6 +1004,8 @@ def write_metadata(self) -> None:
9591004
# Write metadata for each instruction
9601005
for thing in self.everything:
9611006
match thing:
1007+
case OverriddenInstructionPlaceHolder():
1008+
continue
9621009
case parser.InstDef():
9631010
if thing.kind != "op":
9641011
self.write_metadata_for_inst(self.instrs[thing.name])
@@ -1008,7 +1055,7 @@ def write_instructions(self) -> None:
10081055
with open(self.output_filename, "w") as f:
10091056
# Write provenance header
10101057
f.write(f"// This file is generated by {THIS}\n")
1011-
f.write(f"// from {os.path.relpath(self.filename, ROOT).replace(os.path.sep, posixpath.sep)}\n")
1058+
f.write(self.from_source_files())
10121059
f.write(f"// Do not edit!\n")
10131060

10141061
# Create formatter; the rest of the code uses this
@@ -1020,6 +1067,8 @@ def write_instructions(self) -> None:
10201067
n_macros = 0
10211068
for thing in self.everything:
10221069
match thing:
1070+
case OverriddenInstructionPlaceHolder():
1071+
self.write_overridden_instr_place_holder(thing)
10231072
case parser.InstDef():
10241073
if thing.kind != "op":
10251074
n_instrs += 1
@@ -1039,9 +1088,17 @@ def write_instructions(self) -> None:
10391088
file=sys.stderr,
10401089
)
10411090

1091+
def write_overridden_instr_place_holder(self,
1092+
place_holder: OverriddenInstructionPlaceHolder) -> None:
1093+
self.out.emit("")
1094+
self.out.emit(
1095+
f"// TARGET({place_holder.name}) overridden by later definition")
1096+
10421097
def write_instr(self, instr: Instruction) -> None:
10431098
name = instr.name
10441099
self.out.emit("")
1100+
if instr.inst.override:
1101+
self.out.emit("// Override")
10451102
with self.out.block(f"TARGET({name})"):
10461103
if instr.predicted:
10471104
self.out.emit(f"PREDICTED({name});")
@@ -1190,6 +1247,8 @@ def variable_used(node: parser.Node, name: str) -> bool:
11901247
def main():
11911248
"""Parse command line, parse input, analyze, write output."""
11921249
args = arg_parser.parse_args() # Prints message and sys.exit(2) on error
1250+
if len(args.input) == 0:
1251+
args.input.append(DEFAULT_INPUT)
11931252
a = Analyzer(args.input, args.output, args.metadata) # Raises OSError if input unreadable
11941253
a.parse() # Raises SyntaxError on failure
11951254
a.analyze() # Prints messages and sets a.errors on failure

Tools/cases_generator/lexer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def choice(*opts):
119119
kwds = (
120120
'AUTO', 'BREAK', 'CASE', 'CHAR', 'CONST',
121121
'CONTINUE', 'DEFAULT', 'DO', 'DOUBLE', 'ELSE', 'ENUM', 'EXTERN',
122-
'FLOAT', 'FOR', 'GOTO', 'IF', 'INLINE', 'INT', 'LONG',
122+
'FLOAT', 'FOR', 'GOTO', 'IF', 'INLINE', 'INT', 'LONG', 'OVERRIDE',
123123
'REGISTER', 'OFFSETOF',
124124
'RESTRICT', 'RETURN', 'SHORT', 'SIGNED', 'SIZEOF', 'STATIC', 'STRUCT',
125125
'SWITCH', 'TYPEDEF', 'UNION', 'UNSIGNED', 'VOID',

Tools/cases_generator/parser.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Context(NamedTuple):
3333
owner: PLexer
3434

3535
def __repr__(self):
36-
return f"<{self.begin}-{self.end}>"
36+
return f"<{self.owner.filename}: {self.begin}-{self.end}>"
3737

3838

3939
@dataclass
@@ -99,6 +99,7 @@ class OpName(Node):
9999

100100
@dataclass
101101
class InstHeader(Node):
102+
override: bool
102103
register: bool
103104
kind: Literal["inst", "op", "legacy"] # Legacy means no (inputs -- outputs)
104105
name: str
@@ -108,6 +109,7 @@ class InstHeader(Node):
108109

109110
@dataclass
110111
class InstDef(Node):
112+
override: bool
111113
register: bool
112114
kind: Literal["inst", "op", "legacy"]
113115
name: str
@@ -152,17 +154,18 @@ def inst_def(self) -> InstDef | None:
152154
if hdr := self.inst_header():
153155
if block := self.block():
154156
return InstDef(
155-
hdr.register, hdr.kind, hdr.name, hdr.inputs, hdr.outputs, block
157+
hdr.override, hdr.register, hdr.kind, hdr.name, hdr.inputs, hdr.outputs, block
156158
)
157159
raise self.make_syntax_error("Expected block")
158160
return None
159161

160162
@contextual
161163
def inst_header(self) -> InstHeader | None:
162-
# inst(NAME)
163-
# | [register] inst(NAME, (inputs -- outputs))
164-
# | [register] op(NAME, (inputs -- outputs))
164+
# [override] inst(NAME)
165+
# | [override] [register] inst(NAME, (inputs -- outputs))
166+
# | [override] [register] op(NAME, (inputs -- outputs))
165167
# TODO: Make INST a keyword in the lexer.
168+
override = bool(self.expect(lx.OVERRIDE))
166169
register = bool(self.expect(lx.REGISTER))
167170
if (tkn := self.expect(lx.IDENTIFIER)) and (kind := tkn.text) in ("inst", "op"):
168171
if self.expect(lx.LPAREN) and (tkn := self.expect(lx.IDENTIFIER)):
@@ -171,10 +174,10 @@ def inst_header(self) -> InstHeader | None:
171174
inp, outp = self.io_effect()
172175
if self.expect(lx.RPAREN):
173176
if (tkn := self.peek()) and tkn.kind == lx.LBRACE:
174-
return InstHeader(register, kind, name, inp, outp)
177+
return InstHeader(override, register, kind, name, inp, outp)
175178
elif self.expect(lx.RPAREN) and kind == "inst":
176179
# No legacy stack effect if kind is "op".
177-
return InstHeader(register, "legacy", name, [], [])
180+
return InstHeader(override, register, "legacy", name, [], [])
178181
return None
179182

180183
def io_effect(self) -> tuple[list[InputEffect], list[OutputEffect]]:

0 commit comments

Comments
 (0)