-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
gh-121459: Deferred LOAD_GLOBAL #123128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-121459: Deferred LOAD_GLOBAL #123128
Changes from 13 commits
803e9f7
b213b6f
11d2fb6
ea8d855
af0c032
1a431de
48bebe9
6e948b4
e23567a
dbbebf9
dbdee10
6d176c0
4ff9f17
c644ebf
64f932a
bfd4400
0c1ee7e
ad33dd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1496,6 +1496,70 @@ _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyOb | |
return ix; | ||
} | ||
|
||
Py_ssize_t | ||
_Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) | ||
{ | ||
PyDictKeysObject *dk; | ||
DictKeysKind kind; | ||
Py_ssize_t ix; | ||
|
||
ensure_shared_on_read(mp); | ||
|
||
dk = _Py_atomic_load_ptr(&mp->ma_keys); | ||
kind = dk->dk_kind; | ||
|
||
if (kind != DICT_KEYS_GENERAL) { | ||
|
||
PyObject *value; | ||
ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); | ||
assert (ix >= 0 || value == NULL); | ||
*value_addr = PyStackRef_FromPyObjectSteal(value); | ||
} | ||
else { | ||
ix = dictkeys_generic_lookup_threadsafe(mp, dk, key, hash); | ||
if (ix == DKIX_KEY_CHANGED) { | ||
goto read_failed; | ||
} | ||
if (ix >= 0) { | ||
PyObject **addr_of_value = &(DK_ENTRIES(dk)[ix].me_value); | ||
PyObject *value = _Py_atomic_load_ptr(addr_of_value); | ||
if (value == NULL) { | ||
*value_addr = PyStackRef_NULL; | ||
} | ||
else if (_Py_IsImmortal(value) || | ||
_PyObject_HasDeferredRefcount(value)) { | ||
*value_addr = PyStackRef_FromPyObjectNew(value); | ||
} | ||
else { | ||
*value_addr = PyStackRef_FromPyObjectSteal( | ||
_Py_TryXGetRef(addr_of_value)); | ||
Fidget-Spinner marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
if (PyStackRef_IsNull(*value_addr)) { | ||
goto read_failed; | ||
} | ||
if (dk != _Py_atomic_load_ptr(&mp->ma_keys)) { | ||
goto read_failed; | ||
} | ||
} | ||
else { | ||
*value_addr = PyStackRef_NULL; | ||
} | ||
} | ||
|
||
return ix; | ||
|
||
PyObject *value; | ||
picnixz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
read_failed: | ||
// In addition to the normal races of the dict being modified the _Py_TryXGetRef | ||
// can all fail if they don't yet have a shared ref count. That can happen here | ||
// or in the *_lookup_* helper. In that case we need to take the lock to avoid | ||
// mutation and do a normal incref which will make them shared. | ||
Py_BEGIN_CRITICAL_SECTION(mp); | ||
ix = _Py_dict_lookup(mp, key, hash, &value); | ||
*value_addr = value == NULL ? PyStackRef_NULL : PyStackRef_FromPyObjectNew(value); | ||
|
||
Py_END_CRITICAL_SECTION(); | ||
return ix; | ||
} | ||
|
||
#else // Py_GIL_DISABLED | ||
|
||
Py_ssize_t | ||
|
@@ -1506,6 +1570,15 @@ _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyOb | |
return ix; | ||
} | ||
|
||
Py_ssize_t | ||
_Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) | ||
{ | ||
PyObject *val; | ||
Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &val); | ||
*value_addr = val == NULL ? PyStackRef_NULL : PyStackRef_FromPyObjectNew(val); | ||
return ix; | ||
} | ||
|
||
#endif | ||
|
||
int | ||
|
@@ -2418,6 +2491,31 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key) | |
return value; | ||
} | ||
|
||
void | ||
_PyDict_LoadGlobalStackRef(PyDictObject *globals, PyDictObject *builtins, PyObject *key, _PyStackRef *res) | ||
{ | ||
Py_ssize_t ix; | ||
Py_hash_t hash; | ||
|
||
hash = _PyObject_HashFast(key); | ||
if (hash == -1) { | ||
*res = PyStackRef_NULL; | ||
Fidget-Spinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/* namespace 1: globals */ | ||
ix = _Py_dict_lookup_threadsafe_stackref(globals, key, hash, res); | ||
if (ix == DKIX_ERROR) { | ||
*res = PyStackRef_NULL; | ||
} | ||
if (ix != DKIX_EMPTY && !PyStackRef_IsNull(*res)) { | ||
return; | ||
} | ||
|
||
/* namespace 2: builtins */ | ||
ix = _Py_dict_lookup_threadsafe_stackref(builtins, key, hash, res); | ||
assert(ix >= 0 || PyStackRef_IsNull(*res)); | ||
} | ||
|
||
/* Consumes references to key and value */ | ||
static int | ||
setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1448,8 +1448,8 @@ dummy_func( | |
&& PyDict_CheckExact(BUILTINS())) | ||
{ | ||
v_o = _PyDict_LoadGlobal((PyDictObject *)GLOBALS(), | ||
(PyDictObject *)BUILTINS(), | ||
name); | ||
(PyDictObject *)BUILTINS(), | ||
name); | ||
if (v_o == NULL) { | ||
if (!_PyErr_Occurred(tstate)) { | ||
/* _PyDict_LoadGlobal() returns NULL without raising | ||
|
@@ -1507,10 +1507,9 @@ dummy_func( | |
|
||
op(_LOAD_GLOBAL, ( -- res, null if (oparg & 1))) { | ||
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); | ||
PyObject *res_o = _PyEval_LoadGlobal(GLOBALS(), BUILTINS(), name); | ||
ERROR_IF(res_o == NULL, error); | ||
_PyEval_LoadGlobalStackRef(GLOBALS(), BUILTINS(), name, STACK_ENTRY(res)); | ||
|
||
ERROR_IF(PyStackRef_IsNull(res), error); | ||
null = PyStackRef_NULL; | ||
res = PyStackRef_FromPyObjectSteal(res_o); | ||
} | ||
|
||
macro(LOAD_GLOBAL) = | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,10 +47,10 @@ def write_header( | |
) | ||
|
||
|
||
def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str) -> None: | ||
def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str, *, allow_unbalanced_parens: bool = False) -> None: | ||
|
||
parens = 0 | ||
for tkn in tkn_iter: | ||
if tkn.kind == end and parens == 0: | ||
if tkn.kind == end and (parens == 0 or allow_unbalanced_parens): | ||
return | ||
if tkn.kind == "LPAREN": | ||
parens += 1 | ||
|
@@ -77,6 +77,7 @@ def __init__(self, out: CWriter): | |
"DECREF_INPUTS": self.decref_inputs, | ||
"SYNC_SP": self.sync_sp, | ||
"PyStackRef_FromPyObjectNew": self.py_stack_ref_from_py_object_new, | ||
"STACK_ENTRY": self.stack_entry, | ||
} | ||
self.out = out | ||
|
||
|
@@ -211,6 +212,33 @@ def py_stack_ref_from_py_object_new( | |
# unused portions of the stack to NULL. | ||
stack.flush_single_var(self.out, target, uop.stack.outputs) | ||
|
||
def stack_entry( | ||
|
||
self, | ||
tkn: Token, | ||
tkn_iter: Iterator[Token], | ||
uop: Uop, | ||
stack: Stack, | ||
inst: Instruction | None, | ||
) -> None: | ||
emit_to(self.out, tkn_iter, "LPAREN") | ||
target = next(tkn_iter) | ||
size = "0" | ||
for output in uop.stack.inputs: | ||
size += f" - {output.size or 1}" | ||
for output in uop.stack.outputs: | ||
if output.name == target.text: | ||
self.out.emit(f" &stack_pointer[{size}]") | ||
break | ||
size += f" + {output.size or 1}" | ||
else: | ||
raise analysis_error("STACK_ENTRY operand is not a stack output", target) | ||
|
||
next(tkn_iter) # Consume ) | ||
emit_to(self.out, tkn_iter, "SEMI", allow_unbalanced_parens=True) | ||
self.emit(";\n") | ||
# Update the variable | ||
self.out.emit(f"{target.text} = stack_pointer[{size}];\n") | ||
|
||
def emit_tokens( | ||
self, | ||
uop: Uop, | ||
|
Uh oh!
There was an error while loading. Please reload this page.