Skip to content

Commit f0891d7

Browse files
committed
builder: LOAD_ATTR_MODULE + LOAD_ATTR_INSTANCE_VALUE in C
Push 43 (85/144 → 87/144 emit methods, 60.4%): bundles MODULE + INSTANCE_VALUE specialization conversion + 1 thin-wrapper bridge accessor per supervisor 02:11:09Z bundle decision + theologian 02:10:50Z concur. Re-audit at 02:09:21Z found that 4 of 5 originally-identified "new" bridges already exist (hir_c_create_bit_cast, hir_c_create_int_binary_op, hir_c_set_descr, hir_c_set_guilty_reg) — only ~5 LOC of new infra needed (jit_rt_load_module_dict_entry_addr accessor). ================================================================ PART 1: BRIDGE ACCESSOR (LITE TEMPLATE) ================================================================ Bridge: jit_rt_load_module_dict_entry_addr Purpose: extern C accessor returning JITRT_LoadModuleDictEntry function pointer for CallStatic in LOAD_ATTR_MODULE. C++ source: Python/jit/jit_rt.h:656 (declaration), jit_rt.cpp:2587 (defn) PRIOR DECISIONS: NONE (mirrors existing jit_rt_invoke_iter_next_addr) INVARIANTS PRESERVED: 1. Returns the unwrapped C++ function pointer (no copy, no transformation) 2. Function signature: (PyDictKeysObject* keys, Py_ssize_t index) → PyObject* 3. Caller must pass exactly 2 operands to CallStatic (keys, index) Falsifier: bridge result called via CallStatic with valid (keys, index) returns the dict entry; mismatch indicates bridge returned wrong pointer. ================================================================ PART 2: LOAD_ATTR_MODULE WIRING ================================================================ builder_emit_c.c: hir_builder_emit_load_attr_module_c, ~70 lines. builder.cpp: emitLoadAttr LOAD_ATTR_MODULE case shrinks to delegating stub (66 lines deleted, 7 lines added). Per-spec rubric (theologian 00:24:00Z + 02:06:35Z): Common items: C1. Source structural diff: C body mirrors C++ logic line-for-line (guard module type → cache lookup → dict_version!=0 fast path → load md_dict/ma_keys/dk_version → version Guard → CallStatic JITRT_LoadModuleDictEntry → CheckField). C2. Bridge usage audit: hir_c_create_guard_type_reg, hir_c_create_load_field_reg, hir_c_create_load_const, hir_c_create_primitive_compare, hir_c_create_guard + hir_deopt_set_frame_state, hir_c_create_call_static_reg + hir_c_set_operand, hir_c_create_check_field_reg + hir_c_set_guilty_reg. All bridges exist; ordering matches C++. C3. Reference annotation: receiver borrowed; intermediates (dict, keys, loaded_version) typed correctly per C++ original; result is OBJECT|NULL from CallStatic, narrowed to OBJECT by CheckField. C4. FrameState capture: emitSnapshot is in builder.cpp before the switch (unchanged); Guard + CheckField receive &tc->frame. C5. Wiring exercise: test_phoenix_jit_loadattr_golden harness exercises sys.maxsize (LOAD_ATTR_MODULE) — covered. C6. Golden diff exact match (HARD GATE): MODULE block of docs/golden/loadattr_hir.txt must remain byte-identical. test_phoenix_jit_loadattr_golden internally diffs. C7. Py_REF_DEBUG delta: deferred per W8 retroactive trigger (pydebug infra block, supervisor 01:53:52Z). C8. ARM64 commit-match + 4 stash markers + BINARY_MATCH (clean) on both arches: testkeeper to verify. MODULE-specific items: M1. CallStatic argument order EXACTLY matches C++: (keys, index_reg) via hir_c_set_operand(call, 0, keys) + hir_c_set_operand(call, 1, index_reg). M2. CheckField fallback target is the DEOPT path (FrameState replay), NOT raise-NameError directly. Achieved via hir_c_create_check_field_reg + tc->frame. M3. guiltyReg attached to CheckField via hir_c_set_guilty_reg(cf, receiver) for nice runtime errors. ================================================================ PART 3: LOAD_ATTR_INSTANCE_VALUE WIRING ================================================================ builder_emit_c.c: hir_builder_emit_load_attr_instance_value_c, ~92 lines. builder.cpp: emitLoadAttr LOAD_ATTR_INSTANCE_VALUE case shrinks to delegating stub (98 lines deleted, 7 lines added). Per-spec rubric: Common items C1-C8 same pattern as MODULE; key differences: C1. Mirrors C++ split-dict path: guard slot_type → load dorv from managed-dict slot (offset -3*sizeof(PyObject*)) → CheckField NULL → BitCast dorv to CUInt64 → IntBinaryOp kAnd 1 (low-bit test) → Guard with descr "dict values check" → IntBinaryOp kAdd 1 (strip tag) → BitCast to OptObject → LoadField at index*sizeof(PyObject*) → CheckField NULL. C3. dorv loaded with borrowed=1 (matches C++ — dorv may be tagged pointer, not valid PyObject*); attr loaded as OptObject (becomes owned via CheckField narrowing, then refcount_insertion adds Incref). INSTANCE_VALUE-specific items: V1. Dispatch: handles split-dict (managed_dict + values pointer) ONLY, regular __dict__ deopts via Guard "dict values check". Matches C++ single-branch pattern. V2. Values-pointer offset embedded as CUInt64[1] (the constant 1 used in the AND mask + tag-strip add). Code-state, preserved by canonicalization rule (NOT CUInt32 which is dk_version). V3. Py_NewRef on loaded value: handled implicitly by LoadField OptObject type; refcount_insertion pass adds Incref after CheckField result. Verify by checking golden HIR shows Incref after LoadField in the INSTANCE_VALUE block. Static-builtin / subclass-fallback semantics: matches C++ early-return behavior — if slot_type lookup fails OR has subclasses OR lacks TPFLAGS_MANAGED_DICT OR ht_cached_keys is NULL, returns 0 (fallback to generic LoadAttr). Optionally emits GuardType-only as the C++ does. ================================================================ VERIFICATION (compile-clean pre-commit) ================================================================ cmake --build phoenix_jit: PASS, 0 errors (warnings pre-existing). make python: link PASS (no undefined references). Includes added to builder_emit_c.c: - internal/pycore_moduleobject.h (PyModuleObject) - internal/pycore_dict.h (PyDictKeysObject) Diff stat: Python/jit/hir/builder.cpp | -159 +11 (-148 net, MODULE+INSTANCE_VALUE → stubs) Python/jit/hir/builder_emit_c.c | +163 (MODULE wiring + INSTANCE_VALUE wiring + extern decls) Python/jit/hir/hir_c_api.cpp | +4 (jit_rt_load_module_dict_entry_addr) Python/jit/hir/hir_c_api.h | +6 (accessor declaration + doc) 4 files changed, 184 insertions(+), 158 deletions(-) Item 6 HARD GATE: testkeeper to re-capture loadattr_hir.txt and verify MODULE + INSTANCE_VALUE blocks byte-identical to C++ baseline. If either block diverges, HALT push 43. Item 7 deferred per W8 retroactive trigger.
1 parent 8f9642d commit f0891d7

4 files changed

Lines changed: 184 additions & 158 deletions

File tree

Python/jit/hir/builder.cpp

Lines changed: 11 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -3849,6 +3849,8 @@ void HIRBuilder::emitDeleteAttr(
38493849

38503850
extern "C" void hir_builder_emit_load_attr_generic_c(void *tc, void *func, void *receiver, int name_idx);
38513851
extern "C" int hir_builder_emit_load_attr_slot_c(void *tc, void *func, void *builder, void *receiver, PyCodeObject *code, int name_idx, int instr_idx);
3852+
extern "C" int hir_builder_emit_load_attr_module_c(void *tc, void *func, void *builder, void *receiver, PyCodeObject *code, int name_idx, int instr_idx);
3853+
extern "C" int hir_builder_emit_load_attr_instance_value_c(void *tc, void *func, void *builder, void *receiver, PyCodeObject *code, int name_idx, int instr_idx);
38523854

38533855
void HIRBuilder::emitLoadAttr(
38543856
TranslationContext& tc,
@@ -3874,70 +3876,10 @@ void HIRBuilder::emitLoadAttr(
38743876
if (jit_get_config()->specialized_opcodes) {
38753877
switch (bc_instr.specializedOpcode()) {
38763878
case LOAD_ATTR_MODULE: {
3877-
// Guard receiver is a module
3878-
Type mod_type = Type::fromTypeExact(&PyModule_Type);
3879-
tc.emitGuardType(receiver, mod_type, receiver);
3880-
3881-
// Read dict_version and index from CPython's inline cache
3882-
_Py_CODEUNIT* code_units = codeUnit(code_);
3883-
int instr_idx = bc_instr.opcodeIndex().value();
3884-
const _PyAttrCache* cache =
3885-
reinterpret_cast<const _PyAttrCache*>(
3886-
&code_units[instr_idx + 1]);
3887-
uint32_t dict_version = cache->version[0] |
3888-
(static_cast<uint32_t>(cache->version[1]) << 16);
3889-
uint16_t index = cache->index;
3890-
3891-
if (dict_version != 0) {
3892-
// Inline dict access: module->md_dict->ma_keys->dk_version
3893-
Register* dict = temps_.AllocateStack();
3894-
tc.emitLoadField(
3895-
dict, receiver, "md_dict",
3896-
offsetof(PyModuleObject, md_dict), TObject);
3897-
3898-
Register* keys = temps_.AllocateStack();
3899-
tc.emitLoadField(
3900-
keys, dict, "ma_keys",
3901-
offsetof(PyDictObject, ma_keys), TCPtr);
3902-
3903-
Register* loaded_version = temps_.AllocateStack();
3904-
tc.emitLoadField(
3905-
loaded_version, keys, "dk_version",
3906-
offsetof(PyDictKeysObject, dk_version), TCUInt32);
3907-
3908-
Register* expected_version = temps_.AllocateStack();
3909-
tc.emitLoadConst(
3910-
expected_version,
3911-
Type::fromCUInt(dict_version, TCUInt32));
3912-
3913-
Register* version_match = temps_.AllocateStack();
3914-
tc.emitPrimitiveCompare(
3915-
version_match, PrimitiveCompareOp::kEqual,
3916-
loaded_version, expected_version);
3917-
tc.emitGuard(version_match, tc.frame);
3918-
3919-
// Load entry value via helper (computes DK_UNICODE_ENTRIES)
3920-
Register* index_reg = temps_.AllocateStack();
3921-
tc.emitLoadConst(
3922-
index_reg,
3923-
Type::fromCInt(static_cast<int64_t>(index), TCInt64));
3924-
3925-
Register* result = temps_.AllocateStack();
3926-
auto call = tc.emitCallStatic(
3927-
2, result,
3928-
reinterpret_cast<void*>(JITRT_LoadModuleDictEntry),
3929-
TOptObject);
3930-
call->SetOperand(0, keys);
3931-
call->SetOperand(1, index_reg);
3932-
3933-
// Deopt if value is NULL (attribute deleted)
3934-
PyObject* attr_name =
3935-
PyTuple_GET_ITEM(code_->co_names, name_idx);
3936-
auto cf = tc.emitCheckField(
3937-
result, result, attr_name, tc.frame);
3938-
cf->setGuiltyReg(receiver);
3939-
3940-
phx_ptr_arr_push(&tc.frame.stack, result);
3879+
if (hir_builder_emit_load_attr_module_c(
3880+
static_cast<void*>(&tc), static_cast<void*>(current_func_),
3881+
static_cast<void*>(this), static_cast<void*>(receiver),
3882+
code_, name_idx, bc_instr.opcodeIndex().value())) {
39413883
return;
39423884
}
39433885
break;
@@ -3952,100 +3894,11 @@ void HIRBuilder::emitLoadAttr(
39523894
break;
39533895
}
39543896
case LOAD_ATTR_INSTANCE_VALUE: {
3955-
// LOAD_ATTR_INSTANCE_VALUE direct LoadField specialisation.
3956-
// Instead of falling through to generic LoadAttr (which goes through
3957-
// C++ inline cache -> dict lookup -> branch mispredictions), emit
3958-
// a direct field load at the cached offset from CPython's adaptive
3959-
// cache. This matches what simplifyLoadAttrSplitDict does in the
3960-
// Simplify pass, but at build time.
3961-
_Py_CODEUNIT* code_units = codeUnit(code_);
3962-
int instr_idx = bc_instr.opcodeIndex().value();
3963-
const _PyAttrCache* cache =
3964-
reinterpret_cast<const _PyAttrCache*>(&code_units[instr_idx + 1]);
3965-
uint32_t type_version =
3966-
cache->version[0] |
3967-
(static_cast<uint32_t>(cache->version[1]) << 16);
3968-
uint16_t index = cache->index;
3969-
3970-
PyTypeObject* slot_type = findTypeByVersionTag(type_version);
3971-
if (slot_type != nullptr &&
3972-
(slot_type->tp_subclasses == nullptr ||
3973-
PyDict_GET_SIZE(slot_type->tp_subclasses) == 0) &&
3974-
PyType_HasFeature(slot_type, Py_TPFLAGS_MANAGED_DICT)) {
3975-
3976-
PyHeapTypeObject* ht = reinterpret_cast<PyHeapTypeObject*>(slot_type);
3977-
if (ht->ht_cached_keys != nullptr) {
3978-
// Root the type so it lives as long as the compiled code.
3979-
current_func_->env.addReference((PyObject*)(
3980-
reinterpret_cast<PyObject*>(slot_type)));
3981-
Type type = Type::fromTypeExact(slot_type);
3982-
tc.emitGuardType(receiver, type, receiver);
3983-
3984-
PyObject* attr_name =
3985-
PyTuple_GET_ITEM(code_->co_names, name_idx);
3986-
3987-
// Load PyDictOrValues from managed dict slot (offset -3).
3988-
// In CPython 3.12, managed dict pointer is at
3989-
// -3 * sizeof(PyObject*) from the object.
3990-
// Must remain borrowed: dorv can be a tagged pointer (low bit
3991-
// set for inline values) which is not a valid PyObject*.
3992-
Register* dorv = temps_.AllocateStack();
3993-
tc.emitLoadField(
3994-
dorv, receiver, "__dict__",
3995-
-3 * static_cast<int>(sizeof(PyObject*)), TOptDict, true);
3996-
3997-
// Check dorv is not NULL (object has dict/values allocated).
3998-
Register* checked_dorv = temps_.AllocateStack();
3999-
auto cf = tc.emitCheckField(
4000-
checked_dorv, dorv, attr_name, tc.frame);
4001-
cf->setGuiltyReg(receiver);
4002-
4003-
// Check low bit: 1 means inline values, 0 means regular dict.
4004-
Register* one = temps_.AllocateStack();
4005-
tc.emitLoadConst(one, Type::fromCUInt(1, TCUInt64));
4006-
Register* dorv_int = temps_.AllocateStack();
4007-
tc.emitBitCast(dorv_int, checked_dorv, TCUInt64);
4008-
Register* is_values = temps_.AllocateStack();
4009-
tc.emitIntBinaryOp(
4010-
is_values, BinaryOpKind::kAnd, dorv_int, one);
4011-
auto* guard = static_cast<DeoptBase*>(tc.emitGuard(is_values, tc.frame));
4012-
guard->setGuiltyReg(receiver);
4013-
guard->setDescr("dict values check");
4014-
4015-
// Get values pointer: dorv + 1 (see simplifyLoadAttrSplitDict).
4016-
// The tagged pointer layout makes dorv+1 point to the start
4017-
// of the values array for LoadField indexing.
4018-
Register* values_ptr = temps_.AllocateStack();
4019-
tc.emitIntBinaryOp(
4020-
values_ptr, BinaryOpKind::kAdd, dorv_int, one);
4021-
Register* values_obj = temps_.AllocateStack();
4022-
tc.emitBitCast(values_obj, values_ptr, TOptObject);
4023-
4024-
// Load attribute at cached index.
4025-
Register* attr = temps_.AllocateStack();
4026-
tc.emitLoadField(
4027-
attr, values_obj, "attr",
4028-
static_cast<int>(index) * static_cast<int>(sizeof(PyObject*)),
4029-
TOptObject);
4030-
4031-
// Check attribute is not NULL (not deleted).
4032-
Register* result = temps_.AllocateStack();
4033-
auto cf2 = tc.emitCheckField(
4034-
result, attr, attr_name, tc.frame);
4035-
cf2->setGuiltyReg(receiver);
4036-
4037-
phx_ptr_arr_push(&tc.frame.stack, result);
4038-
return;
4039-
}
4040-
}
4041-
// Fallback: emit GuardType only if we found the type but couldn't
4042-
// do the direct load (e.g., no MANAGED_DICT, has subclasses).
4043-
if (slot_type != nullptr) {
4044-
if (slot_type->tp_subclasses == nullptr ||
4045-
PyDict_GET_SIZE(slot_type->tp_subclasses) == 0) {
4046-
Type type = Type::fromTypeExact(slot_type);
4047-
tc.emitGuardType(receiver, type, receiver);
4048-
}
3897+
if (hir_builder_emit_load_attr_instance_value_c(
3898+
static_cast<void*>(&tc), static_cast<void*>(current_func_),
3899+
static_cast<void*>(this), static_cast<void*>(receiver),
3900+
code_, name_idx, bc_instr.opcodeIndex().value())) {
3901+
return;
40493902
}
40503903
break;
40513904
}

Python/jit/hir/builder_emit_c.c

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "cinderx/Jit/hir/hir_type_c.h"
1313
#include "cinderx/Jit/jit_config_c.h"
1414
#include "Python.h"
15+
#include "internal/pycore_moduleobject.h" /* PyModuleObject (LOAD_ATTR_MODULE) */
16+
#include "internal/pycore_dict.h" /* PyDictKeysObject (LOAD_ATTR_MODULE) */
1517
#include "opcode.h"
1618

1719
/* ---- PhxTranslationContext ---- */
@@ -711,6 +713,167 @@ int hir_builder_emit_load_attr_slot_c(
711713
return 1;
712714
}
713715

716+
/* emitLoadAttrModule — LOAD_ATTR_MODULE specialization */
717+
extern void *jit_rt_load_module_dict_entry_addr(void);
718+
extern void *hir_c_create_call_static_reg(size_t n_operands, void *dst,
719+
void *addr, HirType ret_type);
720+
extern void *hir_c_create_guard(void *src);
721+
extern void *hir_c_create_load_const(void *dst, HirType type);
722+
extern void hir_c_set_descr(void *instr, const char *descr);
723+
724+
int hir_builder_emit_load_attr_module_c(
725+
PhxTranslationContext *tc, void *func, void *builder,
726+
void *receiver, PyCodeObject *code, int name_idx, int instr_idx) {
727+
HirType t_module = hir_type_from_pytype(&PyModule_Type, 1);
728+
phx_tc_emit(tc, hir_c_create_guard_type_reg(receiver, t_module, receiver));
729+
730+
uint32_t dict_version;
731+
uint16_t index;
732+
hir_builder_get_attr_cache(builder, instr_idx, &dict_version, &index);
733+
734+
if (dict_version == 0) {
735+
return 0; /* fallback to generic LoadAttr */
736+
}
737+
738+
HirType t_object = (HirType)HIR_TYPE_OBJECT;
739+
HirType t_cptr = (HirType)HIR_TYPE_CPTR;
740+
HirType t_cuint32 = (HirType)HIR_TYPE_CUINT32;
741+
HirType t_cint64 = (HirType)HIR_TYPE_CINT64;
742+
HirType t_optobj = hir_type_union((HirType)HIR_TYPE_OBJECT, (HirType)HIR_TYPE_NULLPTR);
743+
744+
void *dict = hir_func_alloc_register(func);
745+
phx_tc_emit(tc, hir_c_create_load_field_reg(dict, receiver, "md_dict",
746+
(intptr_t)offsetof(PyModuleObject, md_dict), t_object, 0));
747+
748+
void *keys = hir_func_alloc_register(func);
749+
phx_tc_emit(tc, hir_c_create_load_field_reg(keys, dict, "ma_keys",
750+
(intptr_t)offsetof(PyDictObject, ma_keys), t_cptr, 0));
751+
752+
void *loaded_version = hir_func_alloc_register(func);
753+
phx_tc_emit(tc, hir_c_create_load_field_reg(loaded_version, keys, "dk_version",
754+
(intptr_t)offsetof(PyDictKeysObject, dk_version), t_cuint32, 0));
755+
756+
void *expected_version = hir_func_alloc_register(func);
757+
phx_tc_emit(tc, hir_c_create_load_const(expected_version,
758+
hir_type_from_cuint((uint64_t)dict_version, t_cuint32)));
759+
760+
void *version_match = hir_func_alloc_register(func);
761+
phx_tc_emit(tc, hir_c_create_primitive_compare(version_match,
762+
HIR_PCMP_Equal, loaded_version, expected_version));
763+
764+
void *guard = hir_c_create_guard(version_match);
765+
hir_deopt_set_frame_state(guard, &tc->frame);
766+
phx_tc_emit(tc, guard);
767+
768+
void *index_reg = hir_func_alloc_register(func);
769+
phx_tc_emit(tc, hir_c_create_load_const(index_reg,
770+
hir_type_from_cint((int64_t)index, t_cint64)));
771+
772+
void *result = hir_func_alloc_register(func);
773+
void *call = hir_c_create_call_static_reg(2, result,
774+
jit_rt_load_module_dict_entry_addr(), t_optobj);
775+
hir_c_set_operand(call, 0, keys);
776+
hir_c_set_operand(call, 1, index_reg);
777+
phx_tc_emit(tc, call);
778+
779+
PyObject *attr_name = PyTuple_GET_ITEM(code->co_names, name_idx);
780+
void *cf = hir_c_create_check_field_reg(result, result, attr_name, &tc->frame);
781+
hir_c_set_guilty_reg(cf, receiver);
782+
phx_tc_emit(tc, cf);
783+
784+
phx_ptr_arr_push(&tc->frame.stack, result);
785+
return 1;
786+
}
787+
788+
/* emitLoadAttrInstanceValue — LOAD_ATTR_INSTANCE_VALUE specialization */
789+
int hir_builder_emit_load_attr_instance_value_c(
790+
PhxTranslationContext *tc, void *func, void *builder,
791+
void *receiver, PyCodeObject *code, int name_idx, int instr_idx) {
792+
uint32_t type_version;
793+
uint16_t index;
794+
hir_builder_get_attr_cache(builder, instr_idx, &type_version, &index);
795+
796+
PyTypeObject *slot_type = hir_find_type_by_version_tag(type_version);
797+
if (slot_type == NULL ||
798+
(slot_type->tp_subclasses != NULL &&
799+
PyDict_GET_SIZE((PyObject *)slot_type->tp_subclasses) > 0) ||
800+
!PyType_HasFeature(slot_type, Py_TPFLAGS_MANAGED_DICT)) {
801+
/* Optional fallback: emit GuardType only if type found and no subclasses */
802+
if (slot_type != NULL &&
803+
(slot_type->tp_subclasses == NULL ||
804+
PyDict_GET_SIZE((PyObject *)slot_type->tp_subclasses) == 0)) {
805+
HirType t_only = hir_type_from_pytype(slot_type, 1);
806+
phx_tc_emit(tc, hir_c_create_guard_type_reg(receiver, t_only, receiver));
807+
}
808+
return 0;
809+
}
810+
811+
PyHeapTypeObject *ht = (PyHeapTypeObject *)slot_type;
812+
if (ht->ht_cached_keys == NULL) {
813+
HirType t_only = hir_type_from_pytype(slot_type, 1);
814+
phx_tc_emit(tc, hir_c_create_guard_type_reg(receiver, t_only, receiver));
815+
return 0;
816+
}
817+
818+
hir_func_add_reference(func, (PyObject *)slot_type);
819+
HirType t_type = hir_type_from_pytype(slot_type, 1);
820+
phx_tc_emit(tc, hir_c_create_guard_type_reg(receiver, t_type, receiver));
821+
822+
PyObject *attr_name = PyTuple_GET_ITEM(code->co_names, name_idx);
823+
824+
/* Load PyDictOrValues from managed-dict slot at offset -3 * sizeof(PyObject*).
825+
* borrowed=true: dorv may be a tagged pointer (low bit = inline values) which is
826+
* not a valid PyObject*. */
827+
HirType t_optdict = hir_type_union((HirType)HIR_TYPE_DICT, (HirType)HIR_TYPE_NULLPTR);
828+
void *dorv = hir_func_alloc_register(func);
829+
phx_tc_emit(tc, hir_c_create_load_field_reg(dorv, receiver, "__dict__",
830+
(intptr_t)(-3 * (intptr_t)sizeof(PyObject *)), t_optdict, 1));
831+
832+
/* CheckField dorv != NULL */
833+
void *checked_dorv = hir_func_alloc_register(func);
834+
void *cf1 = hir_c_create_check_field_reg(checked_dorv, dorv, attr_name, &tc->frame);
835+
hir_c_set_guilty_reg(cf1, receiver);
836+
phx_tc_emit(tc, cf1);
837+
838+
/* dorv low bit: 1 = inline values, 0 = regular dict (deopt the regular case) */
839+
HirType t_cuint64 = (HirType)HIR_TYPE_CUINT64;
840+
HirType t_optobj = hir_type_union((HirType)HIR_TYPE_OBJECT, (HirType)HIR_TYPE_NULLPTR);
841+
void *one = hir_func_alloc_register(func);
842+
phx_tc_emit(tc, hir_c_create_load_const(one, hir_type_from_cuint(1, t_cuint64)));
843+
844+
void *dorv_int = hir_func_alloc_register(func);
845+
phx_tc_emit(tc, hir_c_create_bit_cast(dorv_int, checked_dorv, t_cuint64));
846+
847+
void *is_values = hir_func_alloc_register(func);
848+
phx_tc_emit(tc, hir_c_create_int_binary_op(is_values, /*kAnd*/1, dorv_int, one));
849+
850+
void *guard = hir_c_create_guard(is_values);
851+
hir_deopt_set_frame_state(guard, &tc->frame);
852+
hir_c_set_guilty_reg(guard, receiver);
853+
hir_c_set_descr(guard, "dict values check");
854+
phx_tc_emit(tc, guard);
855+
856+
/* values_ptr = dorv + 1 (tag stripped → pointer to values array) */
857+
void *values_ptr = hir_func_alloc_register(func);
858+
phx_tc_emit(tc, hir_c_create_int_binary_op(values_ptr, /*kAdd*/0, dorv_int, one));
859+
860+
void *values_obj = hir_func_alloc_register(func);
861+
phx_tc_emit(tc, hir_c_create_bit_cast(values_obj, values_ptr, t_optobj));
862+
863+
/* Load attribute at cached index */
864+
void *attr = hir_func_alloc_register(func);
865+
phx_tc_emit(tc, hir_c_create_load_field_reg(attr, values_obj, "attr",
866+
(intptr_t)((intptr_t)index * (intptr_t)sizeof(PyObject *)), t_optobj, 0));
867+
868+
void *result = hir_func_alloc_register(func);
869+
void *cf2 = hir_c_create_check_field_reg(result, attr, attr_name, &tc->frame);
870+
hir_c_set_guilty_reg(cf2, receiver);
871+
phx_tc_emit(tc, cf2);
872+
873+
phx_ptr_arr_push(&tc->frame.stack, result);
874+
return 1;
875+
}
876+
714877
/* emitLoadAttr generic — non-specialized LoadAttr2 fallback */
715878
extern void *hir_c_create_load_attr_reg2(void *dst, void *receiver, int32_t name_idx, void *fs);
716879

Python/jit/hir/hir_c_api.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,10 @@ void *jit_rt_invoke_iter_next_addr(void) {
526526
return reinterpret_cast<void*>(JITRT_InvokeIterNext);
527527
}
528528

529+
void *jit_rt_load_module_dict_entry_addr(void) {
530+
return reinterpret_cast<void*>(JITRT_LoadModuleDictEntry);
531+
}
532+
529533
const char *jit_builtins_find(void *method_def) {
530534
auto* meth = static_cast<PyMethodDef*>(method_def);
531535
auto result = ::jit::getContext()->builtins().find(meth);

Python/jit/hir/hir_c_api.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,12 @@ void *hir_phi_create_2way(HirFunction func, void *bb1, void *reg1,
760760
* Lifetime: static (function pointer, never changes). */
761761
void *jit_rt_invoke_iter_next_addr(void);
762762

763+
/* Returns function pointer for JITRT_LoadModuleDictEntry. Used by the
764+
* LOAD_ATTR_MODULE specialization to invoke a CallStatic against the
765+
* 2-arg runtime helper (PyDictKeysObject* keys, Py_ssize_t index)
766+
* → PyObject*. Lifetime: static. */
767+
void *jit_rt_load_module_dict_entry_addr(void);
768+
763769
/* ---- Context/Preload bridges ---- */
764770

765771
/* Check if a PyMethodDef is a known builtin. Returns the builtin name

0 commit comments

Comments
 (0)