@@ -426,10 +426,12 @@ class Component:
426426
427427 def write_body (self , out : Formatter , cache_adjust : int ) -> None :
428428 with out .block ("" ):
429+ input_names = {ieffect .name for _ , ieffect in self .input_mapping }
429430 for var , ieffect in self .input_mapping :
430431 out .declare (ieffect , var )
431432 for _ , oeffect in self .output_mapping :
432- out .declare (oeffect , None )
433+ if oeffect .name not in input_names :
434+ out .declare (oeffect , None )
433435
434436 self .instr .write_body (out , dedent = - 4 , cache_adjust = cache_adjust )
435437
@@ -565,10 +567,10 @@ def analyze(self) -> None:
565567 Raises SystemExit if there is an error.
566568 """
567569 self .find_predictions ()
568- self .map_families ()
569- self .check_families ()
570570 self .analyze_register_instrs ()
571571 self .analyze_supers_and_macros ()
572+ self .map_families ()
573+ self .check_families ()
572574
573575 def find_predictions (self ) -> None :
574576 """Find the instructions that need PREDICTED() labels."""
@@ -587,11 +589,30 @@ def find_predictions(self) -> None:
587589 )
588590
589591 def map_families (self ) -> None :
590- """Make instruction names back to their family, if they have one."""
592+ """Link instruction names back to their family, if they have one."""
591593 for family in self .families .values ():
592594 for member in family .members :
593595 if member_instr := self .instrs .get (member ):
594- member_instr .family = family
596+ if member_instr .family not in (family , None ):
597+ self .error (
598+ f"Instruction { member } is a member of multiple families "
599+ f"({ member_instr .family .name } , { family .name } )." ,
600+ family
601+ )
602+ else :
603+ member_instr .family = family
604+ elif member_macro := self .macro_instrs .get (member ):
605+ for part in member_macro .parts :
606+ if isinstance (part , Component ):
607+ if part .instr .family not in (family , None ):
608+ self .error (
609+ f"Component { part .instr .name } of macro { member } "
610+ f"is a member of multiple families "
611+ f"({ part .instr .family .name } , { family .name } )." ,
612+ family
613+ )
614+ else :
615+ part .instr .family = family
595616 else :
596617 self .error (
597618 f"Unknown instruction { member !r} referenced in family { family .name !r} " ,
@@ -608,32 +629,50 @@ def check_families(self) -> None:
608629 for family in self .families .values ():
609630 if len (family .members ) < 2 :
610631 self .error (f"Family { family .name !r} has insufficient members" , family )
611- members = [member for member in family .members if member in self .instrs ]
632+ members = [member for member in family .members if member in self .instrs or member in self . macro_instrs ]
612633 if members != family .members :
613634 unknown = set (family .members ) - set (members )
614635 self .error (
615636 f"Family { family .name !r} has unknown members: { unknown } " , family
616637 )
617638 if len (members ) < 2 :
618639 continue
619- head = self .instrs [members [0 ]]
620- cache = head .cache_offset
621- input = len (head .input_effects )
622- output = len (head .output_effects )
640+ expected_effects = self .effect_counts (members [0 ])
623641 for member in members [1 :]:
624- instr = self .instrs [member ]
625- c = instr .cache_offset
626- i = len (instr .input_effects )
627- o = len (instr .output_effects )
628- if (c , i , o ) != (cache , input , output ):
642+ member_effects = self .effect_counts (member )
643+ if member_effects != expected_effects :
629644 self .error (
630645 f"Family { family .name !r} has inconsistent "
631- f"(cache, inputs, outputs ) effects:\n "
632- f" { family .members [0 ]} = { ( cache , input , output ) } ; "
633- f"{ member } = { ( c , i , o ) } " ,
646+ f"(cache, input, output ) effects:\n "
647+ f" { family .members [0 ]} = { expected_effects } ; "
648+ f"{ member } = { member_effects } " ,
634649 family ,
635650 )
636651
652+ def effect_counts (self , name : str ) -> tuple [int , int , int ]:
653+ if instr := self .instrs .get (name ):
654+ cache = instr .cache_offset
655+ input = len (instr .input_effects )
656+ output = len (instr .output_effects )
657+ elif macro := self .macro_instrs .get (name ):
658+ cache , input , output = 0 , 0 , 0
659+ for part in macro .parts :
660+ if isinstance (part , Component ):
661+ cache += part .instr .cache_offset
662+ # A component may pop what the previous component pushed,
663+ # so we offset the input/output counts by that.
664+ delta_i = len (part .instr .input_effects )
665+ delta_o = len (part .instr .output_effects )
666+ offset = min (delta_i , output )
667+ input += delta_i - offset
668+ output += delta_o - offset
669+ else :
670+ assert isinstance (part , parser .CacheEffect ), part
671+ cache += part .size
672+ else :
673+ assert False , f"Unknown instruction { name !r} "
674+ return cache , input , output
675+
637676 def analyze_register_instrs (self ) -> None :
638677 for instr in self .instrs .values ():
639678 if instr .register :
0 commit comments