Skip to content

Commit 9630005

Browse files
committed
builder: emitCallExceptionHandler → C (101/144 → 102/144 = 70.8%)
Sibling of emitInlineExceptionMatch (~95% shared per theologian PTE pre-audit chat 2026-04-22 16:53Z). Shared-helper design (option a) — D-1774910012 PA invariant lives in EXACTLY ONE C body across both callers, eliminating invariant-drift surface. Python/jit/hir/builder.cpp: emitCallExceptionHandler C++ body (160 lines) → C++ stub (~75 lines): D1 pre-amble: setSuppressExceptionDeopt(true) + pop result Build OpcodeArrayEntry_CXX vector (same shape as inline-match) Call new C body via hir_builder_emit_call_exception_handler_c Net: -167 (replaces inline body with stub call) Python/jit/hir/builder_emit_c.c: NEW static helper emit_except_match_body_c (~140 lines): P2: exc_tc setup + depth-trim P2 inv 8-13: exc_type_reg + JITRT_MatchAndClearException CallStatic + CondBranch P3: match_tc setup + (PA D-1774910012) Py_None push + dispatch loop (10 cases + default→deopt) P4: deopt_tc setup (origin = tc.frame per PB.b) + optional left/right re-push + Snapshot + Deopt PE: phx_frame_state_destroy on EXACTLY 3 TCs (exc_tc, match_tc, deopt_tc) REFACTORED hir_builder_emit_inline_exception_match_c (~30 lines): P1 (getitem CallStatic) + ok_block alloc + CondBranch + helper-call + P5 RefineType Passes left/right as deopt_repush args (BINARY_SUBSCR offset expects them). NEW hir_builder_emit_call_exception_handler_c (~30 lines): ok_block alloc + CondBranch on result + helper-call + D3 P5 RefineType + push result Passes NULL/NULL deopt_repush args (call-result already popped on C++ side). Net: +85 (helper extraction + new C function) PTE-trial invariant inventory verification per theologian [chat L1899]: 29 invariants total = 26 SHARED (in helper, single-source) + 3 UNIQUE (D1/D2/D3). PA D-1774910012: helper guarantees Py_None push + POP_EXCEPT pop, no callsite can violate. Mutation-test sensitivity preserved (same code path as push 59). PB pitfalls: helper enforces deopt_origin = tc (not exc_tc). PE: helper destroys all 3 TCs unconditionally before return. ZERO new bridges per theologian [chat L1899] gate verdict: Reuses hir_c_create_call_static_reg, hir_c_create_load_const, hir_c_create_cond_branch_cpp, hir_c_create_branch_cpp, hir_c_create_snapshot, hir_c_create_deopt, hir_c_create_refine_type_reg, hir_c_create_return, hir_builder_emit_swap_c/load_fast_c/store_fast_c/binary_op_c. Bundled W25 metadata update per supervisor [chat L1894] decision (a): docs/w25-typed-bridges.md §7 PARKED — Owner+Schedule+Falsification+Closure criteria fields added per pythia python#76 python#3 fallow-workstream discipline. Theologian- authored, gen-staged + bundled into this commit (gate-cycle efficiency). testkeeper x86_64 compile-only PASS verified pre-commit at binary timestamp 1776877216 (chat L1900). Awaiting ARM64 pydebug --clean + 8-test gate + push 58 baseline regression check before push. Authorization chain: - theologian PTE pre-audit + STRONG (a) lean: chat L1899 - emitCallExceptionHandler queue unblocked: supervisor chat L1894 (b1 outcome) - W25 metadata bundle option (a): supervisor chat L1894
1 parent 68b9b86 commit 9630005

3 files changed

Lines changed: 423 additions & 176 deletions

File tree

Python/jit/hir/builder.cpp

Lines changed: 64 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,165 +1423,85 @@ void HIRBuilder::emitInlineExceptionMatch(
14231423
}
14241424

14251425

1426+
extern "C" void hir_builder_emit_call_exception_handler_c(
1427+
void* tc, void* func, void* builder,
1428+
int bc_base_offset,
1429+
int handler_depth,
1430+
void* exc_type_obj,
1431+
int except_body_offset,
1432+
HirType return_type,
1433+
void* result,
1434+
void* match_and_clear_fn,
1435+
const OpcodeArrayEntry_CXX* opcodes,
1436+
size_t opcode_count);
1437+
14261438
void HIRBuilder::emitCallExceptionHandler(
1427-
CFG& cfg,
1439+
CFG& /*cfg*/,
14281440
TranslationContext& tc,
14291441
const jit::BytecodeInstruction& bc_instr,
14301442
const ExceptionTableEntry& handler,
14311443
const SimpleExceptInfo& info,
14321444
DeoptBase* call_instr,
14331445
Register* result) {
1434-
// Suppress the auto null-check deopt in LIR codegen. We handle
1435-
// exceptions inline via CondBranch instead. FrameState is preserved
1436-
// for Simplify, register allocation, and other deopt paths.
1446+
// (D1) Pre-amble — must happen on C++ side BEFORE the C body runs:
1447+
// 1. Suppress the auto null-check deopt in LIR codegen. The C body
1448+
// handles exceptions inline via CondBranch. FrameState is
1449+
// preserved for Simplify, register allocation, other deopt paths.
1450+
// 2. Pop the result that emitAnyCall pushed onto the stack — the C
1451+
// body's deopt path expects a clean stack at the CALL offset.
14371452
call_instr->setSuppressExceptionDeopt(true);
1438-
1439-
// Pop the result that emitAnyCall pushed onto the stack.
14401453
static_cast<Register*>(phx_ptr_arr_pop(&tc.frame.stack));
14411454

1442-
BasicBlock* ok_block = cfg.AllocateBlock();
1443-
BasicBlock* exc_match_block = cfg.AllocateBlock();
1444-
1445-
// Branch: non-null -> success, null -> exception path
1446-
tc.emitCondBranch(result, ok_block, exc_match_block);
1447-
1448-
// === Exception match block ===
1449-
{
1450-
TranslationContext exc_tc{exc_match_block, tc.frame};
1451-
1452-
// Pop excess stack items above handler depth.
1453-
// Refcount insertion will handle decrement automatically.
1454-
while (static_cast<int>(exc_tc.frame.stack.count) > handler.depth) {
1455-
static_cast<Register*>(phx_ptr_arr_pop(&exc_tc.frame.stack));
1455+
// Build OpcodeArray identical-shape to emitInlineExceptionMatch — the
1456+
// dispatch loop in the shared C helper expects this layout. Pre-resolves
1457+
// const_obj (LOAD_CONST/RETURN_CONST) + jump_target_block (JUMP_BACKWARD*)
1458+
// to keep PyCodeObject + getBlockAtOff access on the C++ side.
1459+
std::vector<OpcodeArrayEntry_CXX> opcodes;
1460+
BytecodeInstruction ebc{code_, info.except_body};
1461+
bool emitted_terminator = false;
1462+
while (!emitted_terminator) {
1463+
OpcodeArrayEntry_CXX entry{
1464+
ebc.opcode(),
1465+
ebc.oparg(),
1466+
ebc.baseOffset().value(),
1467+
nullptr,
1468+
nullptr};
1469+
int op = entry.opcode;
1470+
if (op == LOAD_CONST || op == RETURN_CONST) {
1471+
entry.const_obj = PyTuple_GET_ITEM(code_->co_consts, entry.oparg);
14561472
}
1457-
1458-
// Load exception type as a constant (resolved at compile time).
1459-
Register* exc_type_reg = temps_.AllocateNonStack();
1460-
exc_tc.emitLoadConst(exc_type_reg, Type::fromObject(info.exc_type));
1461-
1462-
// Call JITRT_MatchAndClearException(exc_type) via CallStatic.
1463-
// Returns int (TCInt32): 1 = matched, 0 = no match.
1464-
Register* match_result = temps_.AllocateNonStack();
1465-
auto match_call = exc_tc.emitCallStatic(
1466-
1, match_result,
1467-
reinterpret_cast<void*>(JITRT_MatchAndClearException),
1468-
TCInt32);
1469-
match_call->SetOperand(0, exc_type_reg);
1470-
1471-
BasicBlock* match_block = cfg.AllocateBlock();
1472-
BasicBlock* deopt_block = cfg.AllocateBlock();
1473-
exc_tc.emitCondBranch(match_result, match_block, deopt_block);
1474-
1475-
// === Match block: emit except body bytecodes inline ===
1476-
{
1477-
TranslationContext match_tc{match_block, exc_tc.frame};
1478-
match_tc.frame.cur_instr_offs = info.except_body;
1479-
1480-
// Push Py_None as "previous exception" placeholder — see comment
1481-
// in the first emitInlineExceptionMatch for full rationale.
1482-
Register* prev_exc_reg = temps_.AllocateStack();
1483-
match_tc.emitLoadConst(prev_exc_reg, TNoneType);
1484-
phx_ptr_arr_push(&match_tc.frame.stack, prev_exc_reg);
1485-
1486-
BytecodeInstruction ebc{code_, info.except_body};
1487-
bool emitted_terminator = false;
1488-
1489-
while (!emitted_terminator) {
1490-
switch (ebc.opcode()) {
1491-
case POP_EXCEPT:
1492-
// Pop the prev_exc placeholder.
1493-
static_cast<Register*>(phx_ptr_arr_pop(&match_tc.frame.stack));
1494-
break;
1495-
1496-
case POP_TOP:
1497-
static_cast<Register*>(phx_ptr_arr_pop(&match_tc.frame.stack));
1498-
break;
1499-
1500-
case SWAP:
1501-
emitSwap(match_tc, ebc.oparg());
1502-
break;
1503-
1504-
case LOAD_FAST:
1505-
case LOAD_FAST_CHECK:
1506-
case LOAD_FAST_AND_CLEAR:
1507-
emitLoadFast(match_tc, ebc);
1508-
break;
1509-
1510-
case LOAD_CONST: {
1511-
Register* reg = temps_.AllocateStack();
1512-
Type type = Type::fromObject(
1513-
PyTuple_GET_ITEM(code_->co_consts, ebc.oparg()));
1514-
match_tc.emitLoadConst(reg, type);
1515-
phx_ptr_arr_push(&match_tc.frame.stack, reg);
1516-
break;
1517-
}
1518-
1519-
case STORE_FAST:
1520-
emitStoreFast(match_tc, ebc);
1521-
break;
1522-
1523-
case BINARY_OP:
1524-
emitBinaryOp(cfg, match_tc, ebc);
1525-
break;
1526-
1527-
case RETURN_CONST: {
1528-
Register* ret_reg = temps_.AllocateStack();
1529-
Type type = Type::fromObject(
1530-
PyTuple_GET_ITEM(code_->co_consts, ebc.oparg()));
1531-
match_tc.emitLoadConst(ret_reg, type);
1532-
match_tc.emitReturn(ret_reg, type);
1533-
emitted_terminator = true;
1534-
break;
1535-
}
1536-
1537-
case RETURN_VALUE: {
1538-
Register* ret_val = static_cast<Register*>(phx_ptr_arr_pop(&match_tc.frame.stack));
1539-
match_tc.emitReturn(ret_val, preloader_.returnType());
1540-
emitted_terminator = true;
1541-
break;
1542-
}
1543-
1544-
case JUMP_BACKWARD:
1545-
case JUMP_BACKWARD_NO_INTERRUPT: {
1546-
BCOffset target = ebc.getJumpTarget();
1547-
auto* target_block = getBlockAtOff(target);
1548-
match_tc.emitBranch(target_block);
1549-
emitted_terminator = true;
1550-
break;
1551-
}
1552-
1553-
default:
1554-
// Unsupported opcode: deopt to interpreter.
1555-
match_tc.frame.cur_instr_offs = ebc.baseOffset();
1556-
match_tc.emitSnapshot();
1557-
match_tc.emitDeopt();
1558-
emitted_terminator = true;
1559-
break;
1560-
}
1561-
1562-
if (!emitted_terminator) {
1563-
ebc = ebc.nextInstr();
1564-
}
1565-
}
1473+
if (op == JUMP_BACKWARD || op == JUMP_BACKWARD_NO_INTERRUPT) {
1474+
entry.jump_target_block =
1475+
static_cast<void*>(getBlockAtOff(ebc.getJumpTarget()));
15661476
}
1567-
1568-
// === Deopt block (no match -- let interpreter handle) ===
1569-
// JITRT_MatchAndClearException returned 0 and restored the pending
1570-
// exception. Deopt back to the interpreter at the CALL offset.
1571-
// The interpreter calls exception_unwind, walks co_exceptiontable,
1572-
// and finds the correct handler.
1573-
{
1574-
TranslationContext deopt_tc{deopt_block, tc.frame};
1575-
deopt_tc.frame.cur_instr_offs = bc_instr.baseOffset();
1576-
deopt_tc.emitSnapshot();
1577-
deopt_tc.emitDeopt();
1477+
if (op == RETURN_VALUE || op == RETURN_CONST
1478+
|| op == JUMP_BACKWARD || op == JUMP_BACKWARD_NO_INTERRUPT) {
1479+
emitted_terminator = true;
1480+
} else if (op != POP_EXCEPT && op != POP_TOP && op != SWAP
1481+
&& op != LOAD_FAST && op != LOAD_FAST_CHECK
1482+
&& op != LOAD_FAST_AND_CLEAR && op != LOAD_CONST
1483+
&& op != STORE_FAST && op != BINARY_OP) {
1484+
emitted_terminator = true;
1485+
}
1486+
opcodes.push_back(entry);
1487+
if (!emitted_terminator) {
1488+
ebc = ebc.nextInstr();
15781489
}
15791490
}
15801491

1581-
// === OK block (no exception, result is non-null) ===
1582-
tc.block = ok_block;
1583-
tc.emitRefineType(result, TObject, result);
1584-
phx_ptr_arr_push(&tc.frame.stack, result);
1492+
hir_builder_emit_call_exception_handler_c(
1493+
static_cast<void*>(&tc),
1494+
static_cast<void*>(current_func_),
1495+
static_cast<void*>(this),
1496+
bc_instr.baseOffset().value(),
1497+
static_cast<int>(handler.depth),
1498+
static_cast<void*>(info.exc_type),
1499+
info.except_body.value(),
1500+
Type::toHirType(preloader_.returnType()),
1501+
static_cast<void*>(result),
1502+
reinterpret_cast<void*>(JITRT_MatchAndClearException),
1503+
opcodes.data(),
1504+
opcodes.size());
15851505
}
15861506

15871507
BasicBlock* HIRBuilder::getBlockAtOff(BCOffset off) {

0 commit comments

Comments
 (0)