37
37
description = "Generate the code for the interpreter switch." ,
38
38
formatter_class = argparse .ArgumentDefaultsHelpFormatter ,
39
39
)
40
- arg_parser .add_argument (
41
- "-i" , "--input" , type = str , help = "Instruction definitions" , default = DEFAULT_INPUT
42
- )
43
40
arg_parser .add_argument (
44
41
"-o" , "--output" , type = str , help = "Generated code" , default = DEFAULT_OUTPUT
45
42
)
46
43
arg_parser .add_argument (
47
44
"-m" , "--metadata" , type = str , help = "Generated metadata" , default = DEFAULT_METADATA_OUTPUT
48
45
)
46
+ arg_parser .add_argument (
47
+ "input" , nargs = argparse .REMAINDER , help = "Instruction definition file(s)"
48
+ )
49
49
50
50
51
51
def effect_size (effect : StackEffect ) -> tuple [int , str ]:
@@ -485,39 +485,45 @@ class MacroInstruction(SuperOrMacroInstruction):
485
485
parts : list [Component | parser .CacheEffect ]
486
486
487
487
488
+ @dataclasses .dataclass
489
+ class OverriddenInstructionPlaceHolder :
490
+ name : str
491
+
492
+
488
493
AnyInstruction = Instruction | SuperInstruction | MacroInstruction
489
494
INSTR_FMT_PREFIX = "INSTR_FMT_"
490
495
491
496
492
497
class Analyzer :
493
498
"""Parse input, analyze it, and write to output."""
494
499
495
- filename : str
500
+ input_filenames : list [ str ]
496
501
output_filename : str
497
502
metadata_filename : str
498
- src : str
499
503
errors : int = 0
500
504
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 ):
502
506
"""Read the input file."""
503
- self .filename = filename
507
+ self .input_filenames = input_filenames
504
508
self .output_filename = output_filename
505
509
self .metadata_filename = metadata_filename
506
- with open (filename ) as f :
507
- self .src = f .read ()
508
510
509
511
def error (self , msg : str , node : parser .Node ) -> None :
510
512
lineno = 0
513
+ filename = "<unknown file>"
511
514
if context := node .context :
515
+ filename = context .owner .filename
512
516
# Use line number of first non-comment in the node
513
517
for token in context .owner .tokens [context .begin : context .end ]:
514
518
lineno = token .line
515
519
if token .kind != "COMMENT" :
516
520
break
517
- print (f"{ self . filename } :{ lineno } : { msg } " , file = sys .stderr )
521
+ print (f"{ filename } :{ lineno } : { msg } " , file = sys .stderr )
518
522
self .errors += 1
519
523
520
- everything : list [parser .InstDef | parser .Super | parser .Macro ]
524
+ everything : list [
525
+ parser .InstDef | parser .Super | parser .Macro | OverriddenInstructionPlaceHolder
526
+ ]
521
527
instrs : dict [str , Instruction ] # Includes ops
522
528
supers : dict [str , parser .Super ]
523
529
super_instrs : dict [str , SuperInstruction ]
@@ -531,7 +537,31 @@ def parse(self) -> None:
531
537
We only want the parser to see the stuff between the
532
538
begin and end markers.
533
539
"""
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 )
535
565
536
566
# Skip until begin marker
537
567
while tkn := psr .next (raw = True ):
@@ -551,16 +581,27 @@ def parse(self) -> None:
551
581
552
582
# Parse from start
553
583
psr .setpos (start )
554
- self .everything = []
555
- self .instrs = {}
556
- self .supers = {}
557
- self .macros = {}
558
- self .families = {}
559
584
thing : parser .InstDef | parser .Super | parser .Macro | parser .Family | None
585
+ thing_first_token = psr .peek ()
560
586
while thing := psr .definition ():
561
587
match thing :
562
588
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
+ )
563
603
self .instrs [name ] = Instruction (thing )
604
+ instrs_idx [name ] = len (self .everything )
564
605
self .everything .append (thing )
565
606
case parser .Super (name ):
566
607
self .supers [name ] = thing
@@ -573,14 +614,7 @@ def parse(self) -> None:
573
614
case _:
574
615
typing .assert_never (thing )
575
616
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 } " )
584
618
585
619
def analyze (self ) -> None :
586
620
"""Analyze the inputs.
@@ -879,6 +913,8 @@ def write_stack_effect_functions(self) -> None:
879
913
popped_data : list [tuple [AnyInstruction , str ]] = []
880
914
pushed_data : list [tuple [AnyInstruction , str ]] = []
881
915
for thing in self .everything :
916
+ if isinstance (thing , OverriddenInstructionPlaceHolder ):
917
+ continue
882
918
instr , popped , pushed = self .get_stack_effect_info (thing )
883
919
if instr is not None :
884
920
popped_data .append ((instr , popped ))
@@ -907,13 +943,22 @@ def write_function(
907
943
write_function ("pushed" , pushed_data )
908
944
self .out .emit ("" )
909
945
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
+
910
953
def write_metadata (self ) -> None :
911
954
"""Write instruction metadata to output file."""
912
955
913
956
# Compute the set of all instruction formats.
914
957
all_formats : set [str ] = set ()
915
958
for thing in self .everything :
916
959
match thing :
960
+ case OverriddenInstructionPlaceHolder ():
961
+ continue
917
962
case parser .InstDef ():
918
963
format = self .instrs [thing .name ].instr_fmt
919
964
case parser .Super ():
@@ -928,8 +973,8 @@ def write_metadata(self) -> None:
928
973
929
974
with open (self .metadata_filename , "w" ) as f :
930
975
# 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 () )
933
978
f .write (f"// Do not edit!\n " )
934
979
935
980
# Create formatter; the rest of the code uses this
@@ -959,6 +1004,8 @@ def write_metadata(self) -> None:
959
1004
# Write metadata for each instruction
960
1005
for thing in self .everything :
961
1006
match thing :
1007
+ case OverriddenInstructionPlaceHolder ():
1008
+ continue
962
1009
case parser .InstDef ():
963
1010
if thing .kind != "op" :
964
1011
self .write_metadata_for_inst (self .instrs [thing .name ])
@@ -1008,7 +1055,7 @@ def write_instructions(self) -> None:
1008
1055
with open (self .output_filename , "w" ) as f :
1009
1056
# Write provenance header
1010
1057
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 () )
1012
1059
f .write (f"// Do not edit!\n " )
1013
1060
1014
1061
# Create formatter; the rest of the code uses this
@@ -1020,6 +1067,8 @@ def write_instructions(self) -> None:
1020
1067
n_macros = 0
1021
1068
for thing in self .everything :
1022
1069
match thing :
1070
+ case OverriddenInstructionPlaceHolder ():
1071
+ self .write_overridden_instr_place_holder (thing )
1023
1072
case parser .InstDef ():
1024
1073
if thing .kind != "op" :
1025
1074
n_instrs += 1
@@ -1039,9 +1088,17 @@ def write_instructions(self) -> None:
1039
1088
file = sys .stderr ,
1040
1089
)
1041
1090
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
+
1042
1097
def write_instr (self , instr : Instruction ) -> None :
1043
1098
name = instr .name
1044
1099
self .out .emit ("" )
1100
+ if instr .inst .override :
1101
+ self .out .emit ("// Override" )
1045
1102
with self .out .block (f"TARGET({ name } )" ):
1046
1103
if instr .predicted :
1047
1104
self .out .emit (f"PREDICTED({ name } );" )
@@ -1190,6 +1247,8 @@ def variable_used(node: parser.Node, name: str) -> bool:
1190
1247
def main ():
1191
1248
"""Parse command line, parse input, analyze, write output."""
1192
1249
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 )
1193
1252
a = Analyzer (args .input , args .output , args .metadata ) # Raises OSError if input unreadable
1194
1253
a .parse () # Raises SyntaxError on failure
1195
1254
a .analyze () # Prints messages and sets a.errors on failure
0 commit comments