Skip to content

Commit bffec89

Browse files
committed
W22: yield-from pattern-deopt — Alex explicit one-case bypass (C scope)
Replaces the no-op YIELD_FROM-bytecode removal in d25b2f3 with a correct pattern-deopt for CPython 3.12 'yield from' semantics. CPython 3.12 desugars 'yield from EXPR' into the SEND-loop sequence (GET_YIELD_FROM_ITER, SEND, YIELD_VALUE, RESUME 2, JUMP_BACKWARD_NO_ INTERRUPT, END_SEND). The YIELD_FROM bytecode is a stub in 3.12 (see Python/jit_common/opcode_stubs.h) and never appears in real code, so removing it from isSupportedOpcode was a no-op (caught by testkeeper + medic L3076 epistemic-decay catch). The HIR YieldFrom instruction that triggers W22 is emitted in builder_emit_c.c:hir_builder_emit_yield_value_c when the YIELD_VALUE bytecode is followed by RESUME with oparg==2 (RESUME oparg semantics: 0=function-start, 1=after-plain-yield, 2=after-yield-from, 3=after- await). This commit: 1. Restores 'case YIELD_FROM:' in isSupportedOpcode (the prior d25b2f3 removal was based on incorrect bytecode-existence analysis; the case is dead in 3.12 either way but the misleading comment is removed). 2. Adds pattern-deopt to checkTranslate: when YIELD_VALUE bytecode is followed by RESUME oparg==2, throw runtime_error so the function falls back to the interpreter. This is the EXACT 'yield from' surface — plain yield (RESUME oparg==1) and await (RESUME oparg==3) remain JIT-compiled. dis.dis verification (3 fixtures, /tmp/w22_disdis_fixtures.py): Fixture A yield_from_fn: YIELD_VALUE oparg=2, next=RESUME oparg=2 -> W22 pattern triggers: True (expected) Fixture B plain_yield_fn: YIELD_VALUE oparg=1, next=RESUME oparg=1 -> W22 pattern triggers: False (expected) Fixture C await_fn: YIELD_VALUE oparg=2, next=RESUME oparg=3 -> W22 pattern triggers: False (expected) VERIFICATION: PASS Author authority bypass per Alex L3076 + gatekeeper L3076 + theologian L3076 + supervisor L3076 concur on (C) pattern-deopt scope (narrowest correct option vs (A) deopt-all-yield or (B) deopt-all-SEND). RULE STANDS for future cases — feedback_no_workarounds.md remains in force. Refs: medic L3076 epistemic-decay catch (added discipline lesson: deopt-class commits require dis.dis bytecode-existence verification before commit).
1 parent d25b2f3 commit bffec89

1 file changed

Lines changed: 24 additions & 8 deletions

File tree

Python/jit/hir/builder.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,16 +262,9 @@ bool isSupportedOpcode(int opcode) {
262262
case UNPACK_EX:
263263
case UNPACK_SEQUENCE:
264264
case WITH_EXCEPT_START:
265+
case YIELD_FROM:
265266
case YIELD_VALUE:
266267
return true;
267-
// YIELD_FROM deopt: Alex 2026-04-23 W22 explicit one-case bypass of
268-
// feedback_no_workarounds.md ('always root-cause, never bail-out/deopt').
269-
// Refcount-pass-init n_in==0 generator-resume bug — JIT compilation of
270-
// YIELD_FROM hits an inherited CinderX defect (deopt-assertion / immortal
271-
// refcount underflow on resume) that 5 architectural fix iterations
272-
// could not resolve cleanly. Author authority bypass per Alex L3076 +
273-
// gatekeeper L3076. Functions containing YIELD_FROM fall back to the
274-
// interpreter (correct, slightly slower). RULE STANDS for future cases.
275268
default:
276269
break;
277270
}
@@ -4934,6 +4927,29 @@ void HIRBuilder::checkTranslate() {
49344927
preloader_.fullname(),
49354928
name_at(oparg))};
49364929
}
4930+
} else if (opcode == YIELD_VALUE) {
4931+
// W22 yield-from deopt: Alex 2026-04-23 explicit one-case bypass of
4932+
// feedback_no_workarounds.md. CPython 3.12 desugars 'yield from EXPR'
4933+
// into GET_YIELD_FROM_ITER + SEND-loop where the loop body is
4934+
// (YIELD_VALUE 2, RESUME 2, JUMP_BACKWARD_NO_INTERRUPT). The HIR
4935+
// YieldFrom instruction is emitted in builder_emit_c.c:hir_builder_emit_
4936+
// yield_value_c when next bytecode is RESUME with oparg==2 (the
4937+
// RESUME oparg discriminator: 0=function-start, 1=after-plain-yield,
4938+
// 2=after-yield-from, 3=after-await). Refcount-pass-init n_in==0 on
4939+
// the generator-resume bb crashes deopt with 'register not live'
4940+
// assertion (5 architectural fix iterations did not converge cleanly).
4941+
// Author authority bypass per Alex L3076 + gatekeeper L3076 + theologian
4942+
// + supervisor concur on (C) pattern-deopt scope. Plain yield (RESUME
4943+
// oparg==1) and await (RESUME oparg==3) STAY in the JIT — only the
4944+
// yield-from desugaring pattern falls back to the interpreter.
4945+
// RULE STANDS for future cases; this is the ONE explicit exception.
4946+
auto next_bc = bci.nextInstr();
4947+
if (next_bc.opcode() == RESUME && next_bc.oparg() == 2) {
4948+
throw std::runtime_error{fmt::format(
4949+
"Cannot compile {} to HIR because it uses 'yield from' "
4950+
"(W22 deopt: YIELD_VALUE+RESUME-oparg-2 pattern)",
4951+
preloader_.fullname())};
4952+
}
49374953
}
49384954
}
49394955
}

0 commit comments

Comments
 (0)