Skip to content

Commit 95c9f9b

Browse files
committed
W26 Step B: emitAnyCall full conversion + 149b7e2 PartialConversion REABSORB
Per docs/w26-emitanycall-full-conversion.md (push 82) + theologian L2462 + L2466 ((B) folded LITE SPECs design + iterator-bridge architecture). Changes: - Python/jit/hir/builder_emit_c.c: new C body hir_builder_emit_any_call_c (~150 lines). Handles all 5 opcode paths (CALL_FUNCTION/CALL_FUNCTION_KW, CALL_FUNCTION_EX, CALL/CALL_KW/ CALL_METHOD, INVOKE_FUNCTION, INVOKE_NATIVE, INVOKE_METHOD) plus the await-tail dispatch INLINED (149b7e2 PartialConversion bridge body REABSORBED into this C body — the bridge function and REABSORB-WHEN comment block are REMOVED). - Added cinder_opcode.h include BEFORE opcode.h (header-guard ordering issue: both use Py_OPCODE_H, so cinder must come first to define INVOKE_FUNCTION/_NATIVE/_METHOD). - Added opcode_stubs.h for CALL_FUNCTION/CALL_FUNCTION_KW/CALL_KW/ CALL_METHOD/YIELD_FROM (3.10/3.11 opcodes stubbed at >=40000 in 3.12 so case labels remain valid but unreachable on 3.12 codepath). - Python/jit/hir/builder.cpp: HIRBuilder::emitAnyCall becomes a ~30-line C++ stub that: - Extracts opcode/oparg/baseOffset upfront (int values) - Computes is_awaited via in-stub iterator peek + PY_VERSION_HEX conditional - Pre-extracts const_arg for INVOKE_* paths (NULL otherwise) - Delegates to hir_builder_emit_any_call_c with opaque iterator + bytecode block pointers (for in-C-body iterator advancement) Adds 4 NEW C bridges (W26 inventory per theologian L2466): - hir_builder_emit_call_method_exception_handler_inline_c: combined findExceptionHandler + getSimpleExceptInfo + emitCallExceptionHandler (folded per (B) decision; NULL-safe internally) - hir_builder_check_async_with_error_c: bridges static checkAsyncWithError via opaque iterator pointer - hir_builder_bc_it_advance_and_opcode_c: combined ++bc_it + opcode read for await-tail JIT_CHECK sequence - hir_builder_bc_it_oparg_c: read bc_it->oparg() (used once for load_const_oparg) REMOVES: hir_builder_emit_awaited_call_tail_c declaration (was at builder.cpp:2861-2867 with PartialConversion comment); body fully inlined into the await-tail section of hir_builder_emit_any_call_c. - Python/jit/hir/builder.h: extern "C" forward decls for the 4 new bridges + 1 new friend declaration (combined exception-handler bridge needs access to private findExceptionHandler/getSimpleExceptInfo/ emitCallExceptionHandler/code_). W25c-mini compliance per supervisor L2441 refined invariant: - Combined exception-handler bridge: opaque void* params (builder/tc/ cfg/call_instr/result_reg are opaque handles; base_offset is int). - Iterator bridges: opaque void* bc_it/bc_instrs + int return. - All NEW bridges typed at boundaries-with-typed-destinations; void* intermediates allowed within all-void* call chains. Empirical scope-reduction from spec §3 5-bridge estimate to 4 actual NEW bridges (theologian L2466 confirmed): emitVariadic<VectorCall>, emitCallMethod, kwnames_ all covered by EXISTING C primitives (hir_c_create_vectorcall_reg, hir_c_create_call_method_reg, hir_builder_get_kwnames/hir_builder_set_kwnames respectively). Verification: cmake --build target jit/phoenix_jit clean (BUILD_EXIT=0). PartialConversion artifact verified REMOVED: grep "hir_builder_emit_awaited_call_tail_c" Python/jit/hir/*.{c,cpp,h} → 0 declarations + 0 implementations remain (only historical comments explaining the reabsorb action are left).
1 parent 0fdb658 commit 95c9f9b

3 files changed

Lines changed: 291 additions & 180 deletions

File tree

Python/jit/hir/builder.cpp

Lines changed: 83 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,163 +2854,108 @@ void HIRBuilder::emitPushNull(TranslationContext& tc) {
28542854
hir_builder_emit_push_null_c(static_cast<void*>(&tc), static_cast<void*>(current_func_));
28552855
}
28562856

2857-
// PartialConversion (theologian PTE pre-audit chat 2026-04-22 17:15Z):
2858-
// emitAnyCall await-tail dispatch (lines 2963-2993 in pre-conversion source)
2859-
// extracted into C body. Opcode-switch + INVOKE_* dispatch stay C++ until
2860-
// Tier 6 INVOKE_* re-architecture. Bridge spec ratified [chat L1931].
2861-
extern "C" void hir_builder_emit_awaited_call_tail_c(
2862-
void* tc, void* func, void* builder,
2863-
PyCodeObject* code,
2864-
int code_flags,
2865-
int get_awaitable_error_aenter,
2866-
int get_awaitable_error_aexit,
2867-
int load_const_oparg);
2868-
2869-
// Forward decl — checkAsyncWithError is defined static at builder.cpp:~4856,
2870-
// below the emitAnyCall call site. Pre-PartialConversion the call lived
2871-
// inline within the await-tail; refactor hoisted it above the definition.
2857+
/* W26 (theologian L2462+L2466): emitAnyCall full conversion + 149b7e2d40
2858+
* PartialConversion REABSORB. The await-tail dispatch is now inlined into
2859+
* the C body of hir_builder_emit_any_call_c via the iterator + checkAsync
2860+
* bridges below. The PartialConversion artifact bridge
2861+
* hir_builder_emit_awaited_call_tail_c is REMOVED; its body lives in the
2862+
* C body of emit_any_call now. REABSORB-WHEN trigger fired post push 81.
2863+
*
2864+
* Forward decl — checkAsyncWithError is defined static at builder.cpp:~4856,
2865+
* below the emitAnyCall call site. */
28722866
static std::pair<bool, bool> checkAsyncWithError(
28732867
const BytecodeInstructionBlock&, BytecodeInstruction);
28742868

2869+
extern "C" void hir_builder_emit_call_method_exception_handler_inline_c(
2870+
void *builder, void *tc, void *cfg, int base_offset,
2871+
void *call_instr, void *result_reg) {
2872+
auto *self = static_cast<HIRBuilder*>(builder);
2873+
BCOffset cur_off{base_offset};
2874+
const auto *handler = self->findExceptionHandler(cur_off);
2875+
if (handler == nullptr) {
2876+
return;
2877+
}
2878+
HIRBuilder::SimpleExceptInfo info;
2879+
if (!self->getSimpleExceptInfo(*handler, info)) {
2880+
return;
2881+
}
2882+
// Reconstruct BytecodeInstruction from base_offset for the C++ method.
2883+
BytecodeInstruction bc_instr{self->code_, cur_off};
2884+
self->emitCallExceptionHandler(
2885+
*static_cast<CFG*>(cfg),
2886+
*static_cast<HIRBuilder::TranslationContext*>(tc),
2887+
bc_instr, *handler, info,
2888+
static_cast<DeoptBase*>(call_instr),
2889+
static_cast<Register*>(result_reg));
2890+
}
2891+
2892+
extern "C" void hir_builder_check_async_with_error_c(
2893+
void *bc_instrs, void *bc_it,
2894+
int *out_error_aenter, int *out_error_aexit) {
2895+
auto& it = *static_cast<jit::BytecodeInstructionBlock::Iterator*>(bc_it);
2896+
auto& bcb = *static_cast<const jit::BytecodeInstructionBlock*>(bc_instrs);
2897+
auto [aenter, aexit] = checkAsyncWithError(bcb, *it);
2898+
*out_error_aenter = aenter ? 1 : 0;
2899+
*out_error_aexit = aexit ? 1 : 0;
2900+
}
2901+
2902+
extern "C" int hir_builder_bc_it_advance_and_opcode_c(void *bc_it) {
2903+
auto& it = *static_cast<jit::BytecodeInstructionBlock::Iterator*>(bc_it);
2904+
++it;
2905+
return it->opcode();
2906+
}
2907+
2908+
extern "C" int hir_builder_bc_it_oparg_c(void *bc_it) {
2909+
auto& it = *static_cast<jit::BytecodeInstructionBlock::Iterator*>(bc_it);
2910+
return it->oparg();
2911+
}
2912+
2913+
extern "C" void hir_builder_emit_any_call_c(
2914+
void *tc, void *cfg, void *func, void *builder,
2915+
void *bc_instrs, void *bc_it,
2916+
int opcode, int oparg, int base_offset,
2917+
int is_awaited,
2918+
void *code, int code_flags,
2919+
PyObject *const_arg);
2920+
28752921
void HIRBuilder::emitAnyCall(
28762922
CFG& cfg,
28772923
TranslationContext& tc,
28782924
jit::BytecodeInstructionBlock::Iterator& bc_it,
28792925
const jit::BytecodeInstructionBlock& bc_instrs) {
28802926
BytecodeInstruction bc_instr = *bc_it;
2881-
bool is_awaited;
2927+
int opcode = bc_instr.opcode();
2928+
int oparg = bc_instr.oparg();
2929+
int base_offset = bc_instr.baseOffset().value();
2930+
2931+
int is_awaited;
28822932
if constexpr (PY_VERSION_HEX >= 0x030C0000) {
2883-
is_awaited = false;
2933+
is_awaited = 0;
28842934
} else {
2885-
is_awaited = code_->co_flags & CO_COROUTINE &&
2935+
is_awaited = (code_->co_flags & CO_COROUTINE) &&
28862936
// We only need to be followed by GET_AWAITABLE to know we are awaited,
28872937
// but we also need to ensure the following LOAD_CONST and YIELD_FROM
28882938
// are inside this BytecodeInstructionBlock. This may not be the case if
28892939
// the 'await' is shared as in 'await (x if y else z)'.
28902940
bc_it.remainingIndices() >= 3 &&
2891-
bc_instr.nextInstr().opcode() == GET_AWAITABLE;
2941+
bc_instr.nextInstr().opcode() == GET_AWAITABLE ? 1 : 0;
28922942
}
2893-
auto flags = is_awaited ? CallFlags::Awaited : CallFlags::None;
2894-
bool call_used_is_awaited = true;
28952943

2896-
auto opcode = bc_instr.opcode();
2897-
switch (opcode) {
2898-
case CALL_FUNCTION:
2899-
case CALL_FUNCTION_KW: {
2900-
// Operands include the function arguments plus the function itself.
2901-
auto num_operands = static_cast<std::size_t>(bc_instr.oparg()) + 1;
2902-
// Add one more operand for the kwnames tuple at the end.
2903-
if (opcode == CALL_FUNCTION_KW) {
2904-
num_operands++;
2905-
flags |= CallFlags::KwArgs;
2906-
}
2907-
tc.emitVariadic<VectorCall>(temps_, num_operands, flags);
2908-
break;
2909-
}
2910-
case CALL_FUNCTION_EX: {
2911-
emitCallEx(tc, bc_instr, flags);
2912-
break;
2913-
}
2914-
case CALL:
2915-
case CALL_KW:
2916-
case CALL_METHOD: {
2917-
auto num_operands = static_cast<std::size_t>(bc_instr.oparg()) + 2;
2918-
auto num_stack_inputs = num_operands;
2919-
bool is_call_kw = opcode == CALL_KW;
2920-
if (kwnames_ != nullptr || is_call_kw) {
2921-
if (is_call_kw) {
2922-
num_stack_inputs++;
2923-
}
2924-
num_operands++;
2925-
flags |= CallFlags::KwArgs;
2926-
}
2927-
2928-
// Manually set up the instruction instead of using emitVariadic.
2929-
// kwnames_ isn't on the stack, but it has to be part of the operand
2930-
// count.
2931-
Register* out = temps_.AllocateStack();
2932-
auto call = tc.emitCallMethod(num_operands, out, flags);
2933-
for (auto i = num_stack_inputs; i > 0; i--) {
2934-
Register* arg = static_cast<Register*>(phx_ptr_arr_pop(&tc.frame.stack));
2935-
call->SetOperand(i - 1, arg);
2936-
}
2937-
if (kwnames_ != nullptr) {
2938-
JIT_CHECK(
2939-
call->GetOperand(num_operands - 1) == nullptr,
2940-
"Somehow already set the kwnames argument");
2941-
call->SetOperand(num_operands - 1, kwnames_);
2942-
kwnames_ = nullptr;
2943-
}
2944-
call->setFrameState(tc.frame);
2945-
2946-
phx_ptr_arr_push(&tc.frame.stack, out);
2947-
2948-
// B2: If this CALL is inside a try block with a simple except pattern,
2949-
// inline the exception handler instead of deopting on exception.
2950-
// This prevents the deopt cascade that occurs when JIT-compiled
2951-
// functions raise caught exceptions (e.g. StopIteration from next()
2952-
// in contextlib._GeneratorContextManager.__exit__).
2953-
{
2954-
BCOffset cur_off = bc_instr.baseOffset();
2955-
auto* handler = findExceptionHandler(cur_off);
2956-
if (handler != nullptr) {
2957-
SimpleExceptInfo info;
2958-
if (getSimpleExceptInfo(*handler, info)) {
2959-
emitCallExceptionHandler(
2960-
cfg, tc, bc_instr, *handler, info, call, out);
2961-
}
2962-
}
2963-
}
2964-
break;
2965-
}
2966-
case INVOKE_FUNCTION: {
2967-
call_used_is_awaited = emitInvokeFunction(tc, bc_instr, flags);
2968-
break;
2969-
}
2970-
case INVOKE_NATIVE: {
2971-
call_used_is_awaited = emitInvokeNative(tc, bc_instr);
2972-
break;
2973-
}
2974-
case INVOKE_METHOD: {
2975-
call_used_is_awaited = emitInvokeMethod(tc, bc_instr, is_awaited);
2976-
break;
2977-
}
2978-
default:
2979-
JIT_ABORT("Unhandled call opcode {} ({})", opcode, opcodeName(opcode));
2980-
}
2981-
if (is_awaited && call_used_is_awaited) {
2982-
// (D1+A4) Advance bc_it three times + assert opcode sequence on the C++
2983-
// side BEFORE the C body runs. Iterator state never crosses the boundary
2984-
// (PB invariant) — only int baseOffsets/oparg/error flags pass through.
2985-
++bc_it;
2986-
JIT_CHECK(
2987-
bc_it->opcode() == GET_AWAITABLE,
2988-
"Awaited function call must be followed by GET_AWAITABLE");
2989-
auto get_awaitable_bc = *bc_it;
2990-
auto [error_aenter, error_aexit] =
2991-
checkAsyncWithError(bc_instrs, get_awaitable_bc);
2992-
2993-
++bc_it;
2994-
JIT_CHECK(
2995-
bc_it->opcode() == LOAD_CONST,
2996-
"GET_AWAITABLE must be followed by LOAD_CONST");
2997-
int load_const_oparg = bc_it->oparg();
2998-
2999-
++bc_it;
3000-
JIT_CHECK(
3001-
bc_it->opcode() == YIELD_FROM,
3002-
"GET_AWAITABLE should always be followed by LOAD_CONST+YIELD_FROM");
3003-
3004-
hir_builder_emit_awaited_call_tail_c(
3005-
static_cast<void*>(&tc),
3006-
static_cast<void*>(current_func_),
3007-
static_cast<void*>(this),
3008-
code_,
3009-
static_cast<int>(code_->co_flags),
3010-
static_cast<int>(error_aenter),
3011-
static_cast<int>(error_aexit),
3012-
load_const_oparg);
2944+
// Pre-extract const_arg for INVOKE_* paths — only used if opcode is one
2945+
// of INVOKE_FUNCTION/NATIVE/METHOD; NULL otherwise.
2946+
PyObject *const_arg = nullptr;
2947+
if (opcode == INVOKE_FUNCTION || opcode == INVOKE_NATIVE
2948+
|| opcode == INVOKE_METHOD) {
2949+
const_arg = constArg(bc_instr);
30132950
}
2951+
2952+
hir_builder_emit_any_call_c(
2953+
&tc, &cfg, current_func_, this,
2954+
const_cast<void*>(static_cast<const void*>(&bc_instrs)),
2955+
&bc_it,
2956+
opcode, oparg, base_offset, is_awaited,
2957+
code_, static_cast<int>(code_->co_flags),
2958+
const_arg);
30142959
}
30152960

30162961
extern "C" void hir_builder_emit_call_intrinsic_c(void *tc, void *func, int opcode, int oparg);

Python/jit/hir/builder.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ void hir_builder_setup_static_args_c(
4141
void **out_arg_regs, size_t *out_count);
4242
void *hir_builder_static_method_stack_pop_c(void *builder);
4343

44+
/* W26 (theologian L2462+L2466): bridges for emitAnyCall full conversion +
45+
* 149b7e2d40 PartialConversion reabsorb. 4 NEW bridges: combined exception-
46+
* handler emit (folds findExceptionHandler+getSimpleExceptInfo+emit per (B)
47+
* decision), checkAsyncWithError, bc_it advance+opcode, bc_it oparg. */
48+
void hir_builder_emit_call_method_exception_handler_inline_c(
49+
void *builder, void *tc, void *cfg, int base_offset,
50+
void *call_instr, void *result_reg);
51+
void hir_builder_check_async_with_error_c(
52+
void *bc_instrs, void *bc_it,
53+
int *out_error_aenter, int *out_error_aexit);
54+
int hir_builder_bc_it_advance_and_opcode_c(void *bc_it);
55+
int hir_builder_bc_it_oparg_c(void *bc_it);
56+
4457
/* INVOKE_* Phase 2 #3 (theologian L2430): bridges for emitInvokeFunction C
4558
* body. Function-target variants of #2 bridges (different preloader lookup
4659
* path: invokeFunctionTarget vs invokeMethodTarget) + invoke-target query +
@@ -169,6 +182,11 @@ class HIRBuilder {
169182
void*, void*, PyObject*, long, int, void**, size_t*);
170183
friend int ::hir_builder_is_static_rand_and_try_emit_c(
171184
void*, void*, PyObject*, long);
185+
// W26 (theologian L2462+L2466): emitAnyCall full conversion + 149b7e2d40
186+
// reabsorb. Friends grant private-member access (exception_table_,
187+
// findExceptionHandler, getSimpleExceptInfo, emitCallExceptionHandler).
188+
friend void ::hir_builder_emit_call_method_exception_handler_inline_c(
189+
void*, void*, void*, int, void*, void*);
172190
public:
173191
const Preloader& preloader() const { return preloader_; }
174192
explicit HIRBuilder(const Preloader& preloader)

0 commit comments

Comments
 (0)