Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions cinderx/Interpreter/3.14/Includes/generated_cases.c.h
Original file line number Diff line number Diff line change
Expand Up @@ -10047,7 +10047,18 @@
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
/* Skip 2 cache entries */
// _GUARD_KEYS_VERSION
{
uint32_t keys_version = read_u32(&this_instr[4].cache);
PyTypeObject *owner_cls = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls;
PyDictKeysObject *keys = owner_heap_type->ht_cached_keys;
if (FT_ATOMIC_LOAD_UINT32_RELAXED(keys->dk_version) != keys_version) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
// _LOAD_ATTR_METHOD_WITH_VALUES
{
PyObject *descr = read_obj(&this_instr[6].cache);
Expand Down Expand Up @@ -10230,7 +10241,18 @@
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
/* Skip 2 cache entries */
// _GUARD_KEYS_VERSION
{
uint32_t keys_version = read_u32(&this_instr[4].cache);
PyTypeObject *owner_cls = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls;
PyDictKeysObject *keys = owner_heap_type->ht_cached_keys;
if (FT_ATOMIC_LOAD_UINT32_RELAXED(keys->dk_version) != keys_version) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
// _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES
{
PyObject *descr = read_obj(&this_instr[6].cache);
Expand Down
26 changes: 24 additions & 2 deletions cinderx/Interpreter/3.15/Includes/generated_cases.c.h
Original file line number Diff line number Diff line change
Expand Up @@ -10240,7 +10240,18 @@
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
/* Skip 2 cache entries */
// _GUARD_KEYS_VERSION
{
uint32_t keys_version = read_u32(&this_instr[4].cache);
PyTypeObject *owner_cls = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls;
PyDictKeysObject *keys = owner_heap_type->ht_cached_keys;
if (FT_ATOMIC_LOAD_UINT32_RELAXED(keys->dk_version) != keys_version) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
// _LOAD_ATTR_METHOD_WITH_VALUES
{
PyObject *descr = read_obj(&this_instr[6].cache);
Expand Down Expand Up @@ -10423,7 +10434,18 @@
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
/* Skip 2 cache entries */
// _GUARD_KEYS_VERSION
{
uint32_t keys_version = read_u32(&this_instr[4].cache);
PyTypeObject *owner_cls = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner));
PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls;
PyDictKeysObject *keys = owner_heap_type->ht_cached_keys;
if (FT_ATOMIC_LOAD_UINT32_RELAXED(keys->dk_version) != keys_version) {
UPDATE_MISS_STATS(LOAD_ATTR);
assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR));
JUMP_TO_PREDICTED(LOAD_ATTR);
}
}
// _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES
{
PyObject *descr = read_obj(&this_instr[6].cache);
Expand Down
109 changes: 109 additions & 0 deletions cinderx/PythonLib/test_cinderx/test_load_attr_keys_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

# pyre-strict

"""Regression tests for gh-115: a load site specialized to
LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES or LOAD_ATTR_METHOD_WITH_VALUES must
deopt once the attribute is stored on an instance. Runs in a subprocess
because it depends on precise interpreter warm-up state."""

import subprocess
import sys
import unittest

import cinderx

cinderx.init()

from cinderx.test_support import ENCODING, subprocess_env


def run_snippet(source: str) -> "subprocess.CompletedProcess[str]":
return subprocess.run(
[sys.executable, "-c", source],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding=ENCODING,
env=subprocess_env(),
)


class LoadAttrKeysVersionTests(unittest.TestCase):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feedback as #129, put this in test_jit_specialization.py and drop the cinderx.init().

@unittest.skipUnless(sys.version_info >= (3, 14), "3.14+ opcodes")
def test_instance_attr_shadows_class_default(self) -> None:
proc = run_snippet(
"""if 1:
import cinderx.jit
cinderx.jit.auto()
import dis

class C:
rcs = None

def make_without():
o = C.__new__(C)
o.x1 = 1
return o

def make_with():
o = C.__new__(C)
o.x1 = 1
o.rcs = (1, 2, 3)
return o

def read(o):
return o.rcs

for _ in range(300):
assert read(make_without()) is None

names = {i.opname for i in dis.get_instructions(read, adaptive=True)}
assert "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES" in names, sorted(names)

got = read(make_with())
assert got == (1, 2, 3), f"stale class default returned: {got!r}"
"""
)
self.assertEqual(proc.returncode, 0, proc.stdout)

@unittest.skipUnless(sys.version_info >= (3, 14), "3.14+ opcodes")
def test_instance_attr_shadows_method(self) -> None:
proc = run_snippet(
"""if 1:
import cinderx.jit
cinderx.jit.auto()
import dis

class C:
def m(self):
return "class-method"

def make_plain():
o = C.__new__(C)
o.x1 = 1
return o

def make_shadowed():
o = C.__new__(C)
o.x1 = 1
o.m = lambda: "instance-attr"
return o

def call_m(o):
return o.m()

for _ in range(300):
assert call_m(make_plain()) == "class-method"

names = {i.opname for i in dis.get_instructions(call_m, adaptive=True)}
assert "LOAD_ATTR_METHOD_WITH_VALUES" in names, sorted(names)

got = call_m(make_shadowed())
assert got == "instance-attr", f"shadowed method ignored: {got!r}"
"""
)
self.assertEqual(proc.returncode, 0, proc.stdout)


if __name__ == "__main__":
unittest.main()
Loading