Skip to content

Commit 7a37523

Browse files
authored
GH-101369: Allow macros as family members (#101399)
Also check for instructions straddling families (this includes macro parts).
1 parent e11fc03 commit 7a37523

File tree

2 files changed

+93
-36
lines changed

2 files changed

+93
-36
lines changed

Tools/cases_generator/generate_cases.py

+57-18
Original file line numberDiff line numberDiff line change
@@ -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:

Tools/cases_generator/test_generator.py

+36-18
Original file line numberDiff line numberDiff line change
@@ -339,46 +339,64 @@ def test_super_instruction():
339339

340340
def test_macro_instruction():
341341
input = """
342-
inst(OP1, (counter/1, arg1 -- interim)) {
343-
interim = op1(arg1);
342+
inst(OP1, (counter/1, left, right -- left, right)) {
343+
op1(left, right);
344344
}
345-
op(OP2, (extra/2, arg2, interim -- res)) {
346-
res = op2(arg2, interim);
345+
op(OP2, (extra/2, arg2, left, right -- res)) {
346+
res = op2(arg2, left, right);
347347
}
348348
macro(OP) = OP1 + cache/2 + OP2;
349+
inst(OP3, (unused/5, arg2, left, right -- res)) {
350+
res = op3(arg2, left, right);
351+
}
352+
family(op, INLINE_CACHE_ENTRIES_OP) = { OP, OP3 };
349353
"""
350354
output = """
351355
TARGET(OP1) {
352-
PyObject *arg1 = PEEK(1);
353-
PyObject *interim;
356+
PyObject *right = PEEK(1);
357+
PyObject *left = PEEK(2);
354358
uint16_t counter = read_u16(&next_instr[0].cache);
355-
interim = op1(arg1);
356-
POKE(1, interim);
359+
op1(left, right);
357360
JUMPBY(1);
358361
DISPATCH();
359362
}
360363
361364
TARGET(OP) {
362365
PyObject *_tmp_1 = PEEK(1);
363366
PyObject *_tmp_2 = PEEK(2);
367+
PyObject *_tmp_3 = PEEK(3);
364368
{
365-
PyObject *arg1 = _tmp_1;
366-
PyObject *interim;
369+
PyObject *right = _tmp_1;
370+
PyObject *left = _tmp_2;
367371
uint16_t counter = read_u16(&next_instr[0].cache);
368-
interim = op1(arg1);
369-
_tmp_1 = interim;
372+
op1(left, right);
373+
_tmp_2 = left;
374+
_tmp_1 = right;
370375
}
371376
{
372-
PyObject *interim = _tmp_1;
373-
PyObject *arg2 = _tmp_2;
377+
PyObject *right = _tmp_1;
378+
PyObject *left = _tmp_2;
379+
PyObject *arg2 = _tmp_3;
374380
PyObject *res;
375381
uint32_t extra = read_u32(&next_instr[3].cache);
376-
res = op2(arg2, interim);
377-
_tmp_2 = res;
382+
res = op2(arg2, left, right);
383+
_tmp_3 = res;
378384
}
379385
JUMPBY(5);
380-
STACK_SHRINK(1);
381-
POKE(1, _tmp_2);
386+
STACK_SHRINK(2);
387+
POKE(1, _tmp_3);
388+
DISPATCH();
389+
}
390+
391+
TARGET(OP3) {
392+
PyObject *right = PEEK(1);
393+
PyObject *left = PEEK(2);
394+
PyObject *arg2 = PEEK(3);
395+
PyObject *res;
396+
res = op3(arg2, left, right);
397+
STACK_SHRINK(2);
398+
POKE(1, res);
399+
JUMPBY(5);
382400
DISPATCH();
383401
}
384402
"""

0 commit comments

Comments
 (0)