Skip to content

Commit 4caa728

Browse files
authored
gh-104909: Implement conditional stack effects for macros (#105748)
1 parent 820febc commit 4caa728

File tree

2 files changed

+80
-13
lines changed

2 files changed

+80
-13
lines changed

Tools/cases_generator/generate_cases.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -179,14 +179,12 @@ def block(self, head: str):
179179

180180
def stack_adjust(
181181
self,
182-
diff: int,
183182
input_effects: list[StackEffect],
184183
output_effects: list[StackEffect],
185184
):
186-
# TODO: Get rid of 'diff' parameter
187185
shrink, isym = list_effect_size(input_effects)
188186
grow, osym = list_effect_size(output_effects)
189-
diff += grow - shrink
187+
diff = grow - shrink
190188
if isym and isym != osym:
191189
self.emit(f"STACK_SHRINK({isym});")
192190
if diff < 0:
@@ -355,7 +353,6 @@ def write(self, out: Formatter) -> None:
355353

356354
# Write net stack growth/shrinkage
357355
out.stack_adjust(
358-
0,
359356
[ieff for ieff in self.input_effects],
360357
[oeff for oeff in self.output_effects],
361358
)
@@ -848,9 +845,14 @@ def stack_analysis(
848845
849846
Ignore cache effects.
850847
851-
Return the list of variable names and the initial stack pointer.
848+
Return the list of variables (as StackEffects) and the initial stack pointer.
852849
"""
853850
lowest = current = highest = 0
851+
conditions: dict[int, str] = {} # Indexed by 'current'.
852+
last_instr: Instruction | None = None
853+
for thing in components:
854+
if isinstance(thing, Instruction):
855+
last_instr = thing
854856
for thing in components:
855857
match thing:
856858
case Instruction() as instr:
@@ -863,9 +865,24 @@ def stack_analysis(
863865
"which are not supported in macro instructions",
864866
instr.inst, # TODO: Pass name+location of macro
865867
)
868+
if any(eff.cond for eff in instr.input_effects):
869+
self.error(
870+
f"Instruction {instr.name!r} has conditional input stack effect, "
871+
"which are not supported in macro instructions",
872+
instr.inst, # TODO: Pass name+location of macro
873+
)
874+
if any(eff.cond for eff in instr.output_effects) and instr is not last_instr:
875+
self.error(
876+
f"Instruction {instr.name!r} has conditional output stack effect, "
877+
"but is not the last instruction in a macro",
878+
instr.inst, # TODO: Pass name+location of macro
879+
)
866880
current -= len(instr.input_effects)
867881
lowest = min(lowest, current)
868-
current += len(instr.output_effects)
882+
for eff in instr.output_effects:
883+
if eff.cond:
884+
conditions[current] = eff.cond
885+
current += 1
869886
highest = max(highest, current)
870887
case parser.CacheEffect():
871888
pass
@@ -874,9 +891,9 @@ def stack_analysis(
874891
# At this point, 'current' is the net stack effect,
875892
# and 'lowest' and 'highest' are the extremes.
876893
# Note that 'lowest' may be negative.
877-
# TODO: Reverse the numbering.
878894
stack = [
879-
StackEffect(f"_tmp_{i+1}", "") for i in reversed(range(highest - lowest))
895+
StackEffect(f"_tmp_{i}", "", conditions.get(highest - i, ""))
896+
for i in reversed(range(1, highest - lowest + 1))
880897
]
881898
return stack, -lowest
882899

@@ -908,15 +925,17 @@ def effect_str(effects: list[StackEffect]) -> str:
908925
low = 0
909926
sp = 0
910927
high = 0
928+
pushed_symbolic: list[str] = []
911929
for comp in parts:
912930
for effect in comp.instr.input_effects:
913931
assert not effect.cond, effect
914932
assert not effect.size, effect
915933
sp -= 1
916934
low = min(low, sp)
917935
for effect in comp.instr.output_effects:
918-
assert not effect.cond, effect
919936
assert not effect.size, effect
937+
if effect.cond:
938+
pushed_symbolic.append(maybe_parenthesize(f"{maybe_parenthesize(effect.cond)} ? 1 : 0"))
920939
sp += 1
921940
high = max(sp, high)
922941
if high != max(0, sp):
@@ -926,7 +945,8 @@ def effect_str(effects: list[StackEffect]) -> str:
926945
# calculations to use the micro ops.
927946
self.error("Macro has virtual stack growth", thing)
928947
popped = str(-low)
929-
pushed = str(sp - low)
948+
pushed_symbolic.append(str(sp - low - len(pushed_symbolic)))
949+
pushed = " + ".join(pushed_symbolic)
930950
case parser.Pseudo():
931951
instr = self.pseudo_instrs[thing.name]
932952
popped = pushed = None
@@ -1203,16 +1223,23 @@ def wrap_macro(self, mac: MacroInstruction):
12031223
with self.out.block(f"TARGET({mac.name})"):
12041224
if mac.predicted:
12051225
self.out.emit(f"PREDICTED({mac.name});")
1206-
for i, var in reversed(list(enumerate(mac.stack))):
1226+
1227+
# The input effects should have no conditionals.
1228+
# Only the output effects do (for now).
1229+
ieffects = [
1230+
StackEffect(eff.name, eff.type) if eff.cond else eff
1231+
for eff in mac.stack
1232+
]
1233+
1234+
for i, var in reversed(list(enumerate(ieffects))):
12071235
src = None
12081236
if i < mac.initial_sp:
12091237
src = StackEffect(f"stack_pointer[-{mac.initial_sp - i}]", "")
12101238
self.out.declare(var, src)
12111239

12121240
yield
12131241

1214-
# TODO: Use slices of mac.stack instead of numeric values
1215-
self.out.stack_adjust(mac.final_sp - mac.initial_sp, [], [])
1242+
self.out.stack_adjust(ieffects[:mac.initial_sp], mac.stack[:mac.final_sp])
12161243

12171244
for i, var in enumerate(reversed(mac.stack[: mac.final_sp]), 1):
12181245
dst = StackEffect(f"stack_pointer[-{i}]", "")

Tools/cases_generator/test_generator.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,43 @@ def test_cond_effect():
432432
}
433433
"""
434434
run_cases_test(input, output)
435+
436+
def test_macro_cond_effect():
437+
input = """
438+
op(A, (left, middle, right --)) {
439+
# Body of A
440+
}
441+
op(B, (-- deep, extra if (oparg), res)) {
442+
# Body of B
443+
}
444+
macro(M) = A + B;
445+
"""
446+
output = """
447+
TARGET(M) {
448+
PyObject *_tmp_1 = stack_pointer[-1];
449+
PyObject *_tmp_2 = stack_pointer[-2];
450+
PyObject *_tmp_3 = stack_pointer[-3];
451+
{
452+
PyObject *right = _tmp_1;
453+
PyObject *middle = _tmp_2;
454+
PyObject *left = _tmp_3;
455+
# Body of A
456+
}
457+
{
458+
PyObject *deep;
459+
PyObject *extra = NULL;
460+
PyObject *res;
461+
# Body of B
462+
_tmp_3 = deep;
463+
if (oparg) { _tmp_2 = extra; }
464+
_tmp_1 = res;
465+
}
466+
STACK_SHRINK(1);
467+
STACK_GROW((oparg ? 1 : 0));
468+
stack_pointer[-1] = _tmp_1;
469+
if (oparg) { stack_pointer[-2] = _tmp_2; }
470+
stack_pointer[-3] = _tmp_3;
471+
DISPATCH();
472+
}
473+
"""
474+
run_cases_test(input, output)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy