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- )
4340arg_parser .add_argument (
4441 "-o" , "--output" , type = str , help = "Generated code" , default = DEFAULT_OUTPUT
4542)
4643arg_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
5151def 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+
488493AnyInstruction = Instruction | SuperInstruction | MacroInstruction
489494INSTR_FMT_PREFIX = "INSTR_FMT_"
490495
491496
492497class 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:
11901247def 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
0 commit comments