mirror of
https://github.com/python/cpython.git
synced 2025-10-19 16:03:42 +00:00
Merge branch 'main' into hy/close_issue_137942
This commit is contained in:
commit
a2d5480a46
27 changed files with 2445 additions and 542 deletions
|
@ -971,10 +971,17 @@ as globals. It would be impossible to assign to a global variable without
|
|||
:keyword:`!global`, although free variables may refer to globals without being
|
||||
declared global.
|
||||
|
||||
The :keyword:`global` statement applies to the entire scope of a function or
|
||||
class body. A :exc:`SyntaxError` is raised if a variable is used or
|
||||
The :keyword:`!global` statement applies to the entire current scope
|
||||
(module, function body or class definition).
|
||||
A :exc:`SyntaxError` is raised if a variable is used or
|
||||
assigned to prior to its global declaration in the scope.
|
||||
|
||||
At the module level, all variables are global, so a :keyword:`!global`
|
||||
statement has no effect.
|
||||
However, variables must still not be used or
|
||||
assigned to prior to their :keyword:`!global` declaration.
|
||||
This requirement is relaxed in the interactive prompt (:term:`REPL`).
|
||||
|
||||
.. index::
|
||||
pair: built-in function; exec
|
||||
pair: built-in function; eval
|
||||
|
|
|
@ -359,7 +359,7 @@ Usually, a method is called right after it is bound::
|
|||
|
||||
x.f()
|
||||
|
||||
In the :class:`!MyClass` example, this will return the string ``'hello world'``.
|
||||
If ``x = MyClass()``, as above, this will return the string ``'hello world'``.
|
||||
However, it is not necessary to call a method right away: ``x.f`` is a method
|
||||
object, and can be stored away and called at a later time. For example::
|
||||
|
||||
|
|
|
@ -169,6 +169,45 @@ production systems where traditional profiling approaches would be too intrusive
|
|||
(Contributed by Pablo Galindo and László Kiss Kollár in :gh:`135953`.)
|
||||
|
||||
|
||||
Improved error messages
|
||||
-----------------------
|
||||
|
||||
* The interpreter now provides more helpful suggestions in :exc:`AttributeError`
|
||||
exceptions when accessing an attribute on an object that does not exist, but
|
||||
a similar attribute is available through one of its members.
|
||||
|
||||
For example, if the object has an attribute that itself exposes the requested
|
||||
name, the error message will suggest accessing it via that inner attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@dataclass
|
||||
class Circle:
|
||||
radius: float
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return pi * self.radius**2
|
||||
|
||||
class Container:
|
||||
def __init__(self, inner: Circle) -> None:
|
||||
self.inner = inner
|
||||
|
||||
circle = Circle(radius=4.0)
|
||||
container = Container(circle)
|
||||
print(container.area)
|
||||
|
||||
Running this code now produces a clearer suggestion:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "/home/pablogsal/github/python/main/lel.py", line 42, in <module>
|
||||
print(container.area)
|
||||
^^^^^^^^^^^^^^
|
||||
AttributeError: 'Container' object has no attribute 'area'. Did you mean: 'inner.area'?
|
||||
|
||||
|
||||
Other language changes
|
||||
======================
|
||||
|
||||
|
|
|
@ -123,6 +123,22 @@ _PyEval_EvalFrame(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwfl
|
|||
return tstate->interp->eval_frame(tstate, frame, throwflag);
|
||||
}
|
||||
|
||||
#ifdef _Py_TIER2
|
||||
#ifdef _Py_JIT
|
||||
_Py_CODEUNIT *_Py_LazyJitTrampoline(
|
||||
struct _PyExecutorObject *current_executor, _PyInterpreterFrame *frame,
|
||||
_PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
);
|
||||
#else
|
||||
_Py_CODEUNIT *_PyTier2Interpreter(
|
||||
struct _PyExecutorObject *current_executor, _PyInterpreterFrame *frame,
|
||||
_PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern _PyJitEntryFuncPtr _Py_jit_entry;
|
||||
|
||||
extern PyObject*
|
||||
_PyEval_Vector(PyThreadState *tstate,
|
||||
PyFunctionObject *func, PyObject *locals,
|
||||
|
|
|
@ -765,6 +765,7 @@ struct _Py_unique_id_pool {
|
|||
|
||||
#endif
|
||||
|
||||
typedef _Py_CODEUNIT *(*_PyJitEntryFuncPtr)(struct _PyExecutorObject *exec, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate);
|
||||
|
||||
/* PyInterpreterState holds the global state for one of the runtime's
|
||||
interpreters. Typically the initial (main) interpreter is the only one.
|
||||
|
|
|
@ -82,7 +82,6 @@ typedef struct _PyExecutorObject {
|
|||
uint32_t code_size;
|
||||
size_t jit_size;
|
||||
void *jit_code;
|
||||
void *jit_side_entry;
|
||||
_PyExitData exits[1];
|
||||
} _PyExecutorObject;
|
||||
|
||||
|
|
|
@ -4262,6 +4262,184 @@ def __getattribute__(self, attr):
|
|||
self.assertIn("Did you mean", actual)
|
||||
self.assertIn("bluch", actual)
|
||||
|
||||
def test_getattr_nested_attribute_suggestions(self):
|
||||
# Test that nested attributes are suggested when no direct match
|
||||
class Inner:
|
||||
def __init__(self):
|
||||
self.value = 42
|
||||
self.data = "test"
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.inner = Inner()
|
||||
|
||||
# Should suggest 'inner.value'
|
||||
actual = self.get_suggestion(Outer(), 'value')
|
||||
self.assertIn("Did you mean: 'inner.value'", actual)
|
||||
|
||||
# Should suggest 'inner.data'
|
||||
actual = self.get_suggestion(Outer(), 'data')
|
||||
self.assertIn("Did you mean: 'inner.data'", actual)
|
||||
|
||||
def test_getattr_nested_prioritizes_direct_matches(self):
|
||||
# Test that direct attribute matches are prioritized over nested ones
|
||||
class Inner:
|
||||
def __init__(self):
|
||||
self.foo = 42
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.inner = Inner()
|
||||
self.fooo = 100 # Similar to 'foo'
|
||||
|
||||
# Should suggest 'fooo' (direct) not 'inner.foo' (nested)
|
||||
actual = self.get_suggestion(Outer(), 'foo')
|
||||
self.assertIn("Did you mean: 'fooo'", actual)
|
||||
self.assertNotIn("inner.foo", actual)
|
||||
|
||||
def test_getattr_nested_with_property(self):
|
||||
# Test that descriptors (including properties) are suggested in nested attributes
|
||||
class Inner:
|
||||
@property
|
||||
def computed(self):
|
||||
return 42
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.inner = Inner()
|
||||
|
||||
actual = self.get_suggestion(Outer(), 'computed')
|
||||
# Descriptors should not be suggested to avoid executing arbitrary code
|
||||
self.assertIn("inner.computed", actual)
|
||||
|
||||
def test_getattr_nested_no_suggestion_for_deep_nesting(self):
|
||||
# Test that deeply nested attributes (2+ levels) are not suggested
|
||||
class Deep:
|
||||
def __init__(self):
|
||||
self.value = 42
|
||||
|
||||
class Middle:
|
||||
def __init__(self):
|
||||
self.deep = Deep()
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.middle = Middle()
|
||||
|
||||
# Should not suggest 'middle.deep.value' (too deep)
|
||||
actual = self.get_suggestion(Outer(), 'value')
|
||||
self.assertNotIn("Did you mean", actual)
|
||||
|
||||
def test_getattr_nested_ignores_private_attributes(self):
|
||||
# Test that nested suggestions ignore private attributes
|
||||
class Inner:
|
||||
def __init__(self):
|
||||
self.public_value = 42
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self._private_inner = Inner()
|
||||
|
||||
# Should not suggest '_private_inner.public_value'
|
||||
actual = self.get_suggestion(Outer(), 'public_value')
|
||||
self.assertNotIn("Did you mean", actual)
|
||||
|
||||
def test_getattr_nested_limits_attribute_checks(self):
|
||||
# Test that nested suggestions are limited to checking first 20 non-private attributes
|
||||
class Inner:
|
||||
def __init__(self):
|
||||
self.target_value = 42
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
# Add many attributes before 'inner'
|
||||
for i in range(25):
|
||||
setattr(self, f'attr_{i:02d}', i)
|
||||
# Add the inner object after 20+ attributes
|
||||
self.inner = Inner()
|
||||
|
||||
obj = Outer()
|
||||
# Verify that 'inner' is indeed present but after position 20
|
||||
attrs = [x for x in sorted(dir(obj)) if not x.startswith('_')]
|
||||
inner_position = attrs.index('inner')
|
||||
self.assertGreater(inner_position, 19, "inner should be after position 20 in sorted attributes")
|
||||
|
||||
# Should not suggest 'inner.target_value' because inner is beyond the first 20 attributes checked
|
||||
actual = self.get_suggestion(obj, 'target_value')
|
||||
self.assertNotIn("inner.target_value", actual)
|
||||
|
||||
def test_getattr_nested_returns_first_match_only(self):
|
||||
# Test that only the first nested match is returned (not multiple)
|
||||
class Inner1:
|
||||
def __init__(self):
|
||||
self.value = 1
|
||||
|
||||
class Inner2:
|
||||
def __init__(self):
|
||||
self.value = 2
|
||||
|
||||
class Inner3:
|
||||
def __init__(self):
|
||||
self.value = 3
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
# Multiple inner objects with same attribute
|
||||
self.a_inner = Inner1()
|
||||
self.b_inner = Inner2()
|
||||
self.c_inner = Inner3()
|
||||
|
||||
# Should suggest only the first match (alphabetically)
|
||||
actual = self.get_suggestion(Outer(), 'value')
|
||||
self.assertIn("'a_inner.value'", actual)
|
||||
# Verify it's a single suggestion, not multiple
|
||||
self.assertEqual(actual.count("Did you mean"), 1)
|
||||
|
||||
def test_getattr_nested_handles_attribute_access_exceptions(self):
|
||||
# Test that exceptions raised when accessing attributes don't crash the suggestion system
|
||||
class ExplodingProperty:
|
||||
@property
|
||||
def exploding_attr(self):
|
||||
raise RuntimeError("BOOM! This property always explodes")
|
||||
|
||||
def __repr__(self):
|
||||
raise RuntimeError("repr also explodes")
|
||||
|
||||
class SafeInner:
|
||||
def __init__(self):
|
||||
self.target = 42
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.exploder = ExplodingProperty() # Accessing attributes will raise
|
||||
self.safe_inner = SafeInner()
|
||||
|
||||
# Should still suggest 'safe_inner.target' without crashing
|
||||
# even though accessing exploder.target would raise an exception
|
||||
actual = self.get_suggestion(Outer(), 'target')
|
||||
self.assertIn("'safe_inner.target'", actual)
|
||||
|
||||
def test_getattr_nested_handles_hasattr_exceptions(self):
|
||||
# Test that exceptions in hasattr don't crash the system
|
||||
class WeirdObject:
|
||||
def __getattr__(self, name):
|
||||
if name == 'target':
|
||||
raise RuntimeError("Can't check for target attribute")
|
||||
raise AttributeError(f"No attribute {name}")
|
||||
|
||||
class NormalInner:
|
||||
def __init__(self):
|
||||
self.target = 100
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.weird = WeirdObject() # hasattr will raise for 'target'
|
||||
self.normal = NormalInner()
|
||||
|
||||
# Should still find 'normal.target' even though weird.target check fails
|
||||
actual = self.get_suggestion(Outer(), 'target')
|
||||
self.assertIn("'normal.target'", actual)
|
||||
|
||||
def make_module(self, code):
|
||||
tmpdir = Path(tempfile.mkdtemp())
|
||||
self.addCleanup(shutil.rmtree, tmpdir)
|
||||
|
|
|
@ -1601,6 +1601,34 @@ def _substitution_cost(ch_a, ch_b):
|
|||
return _MOVE_COST
|
||||
|
||||
|
||||
def _check_for_nested_attribute(obj, wrong_name, attrs):
|
||||
"""Check if any attribute of obj has the wrong_name as a nested attribute.
|
||||
|
||||
Returns the first nested attribute suggestion found, or None.
|
||||
Limited to checking 20 attributes.
|
||||
Only considers non-descriptor attributes to avoid executing arbitrary code.
|
||||
"""
|
||||
# Check for nested attributes (only one level deep)
|
||||
attrs_to_check = [x for x in attrs if not x.startswith('_')][:20] # Limit number of attributes to check
|
||||
for attr_name in attrs_to_check:
|
||||
with suppress(Exception):
|
||||
# Check if attr_name is a descriptor - if so, skip it
|
||||
attr_from_class = getattr(type(obj), attr_name, None)
|
||||
if attr_from_class is not None and hasattr(attr_from_class, '__get__'):
|
||||
continue # Skip descriptors to avoid executing arbitrary code
|
||||
|
||||
# Safe to get the attribute since it's not a descriptor
|
||||
attr_obj = getattr(obj, attr_name)
|
||||
|
||||
# Check if the nested attribute exists and is not a descriptor
|
||||
nested_attr_from_class = getattr(type(attr_obj), wrong_name, None)
|
||||
|
||||
if hasattr(attr_obj, wrong_name):
|
||||
return f"{attr_name}.{wrong_name}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _compute_suggestion_error(exc_value, tb, wrong_name):
|
||||
if wrong_name is None or not isinstance(wrong_name, str):
|
||||
return None
|
||||
|
@ -1666,7 +1694,9 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
|
|||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return _suggestions._generate_suggestions(d, wrong_name)
|
||||
suggestion = _suggestions._generate_suggestions(d, wrong_name)
|
||||
if suggestion:
|
||||
return suggestion
|
||||
|
||||
# Compute closest match
|
||||
|
||||
|
@ -1691,6 +1721,14 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
|
|||
if not suggestion or current_distance < best_distance:
|
||||
suggestion = possible_name
|
||||
best_distance = current_distance
|
||||
|
||||
# If no direct attribute match found, check for nested attributes
|
||||
if not suggestion and isinstance(exc_value, AttributeError):
|
||||
with suppress(Exception):
|
||||
nested_suggestion = _check_for_nested_attribute(exc_value.obj, wrong_name, d)
|
||||
if nested_suggestion:
|
||||
return nested_suggestion
|
||||
|
||||
return suggestion
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Replace the shim code added to every piece of jitted code with a single
|
||||
trampoline function.
|
|
@ -0,0 +1 @@
|
|||
Show error suggestions on nested attribute access. Patch by Pablo Galindo
|
File diff suppressed because it is too large
Load diff
1267
Modules/_decimal/clinic/_decimal.c.h
generated
1267
Modules/_decimal/clinic/_decimal.c.h
generated
File diff suppressed because it is too large
Load diff
|
@ -10,53 +10,6 @@
|
|||
|
||||
#include "pymacro.h"
|
||||
|
||||
PyDoc_STRVAR(doc_is_canonical,
|
||||
"is_canonical($self, /)\n--\n\n\
|
||||
Return True if the argument is canonical and False otherwise. Currently,\n\
|
||||
a Decimal instance is always canonical, so this operation always returns\n\
|
||||
True.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_finite,
|
||||
"is_finite($self, /)\n--\n\n\
|
||||
Return True if the argument is a finite number, and False if the argument\n\
|
||||
is infinite or a NaN.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_infinite,
|
||||
"is_infinite($self, /)\n--\n\n\
|
||||
Return True if the argument is either positive or negative infinity and\n\
|
||||
False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_nan,
|
||||
"is_nan($self, /)\n--\n\n\
|
||||
Return True if the argument is a (quiet or signaling) NaN and False\n\
|
||||
otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_qnan,
|
||||
"is_qnan($self, /)\n--\n\n\
|
||||
Return True if the argument is a quiet NaN, and False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_signed,
|
||||
"is_signed($self, /)\n--\n\n\
|
||||
Return True if the argument has a negative sign and False otherwise.\n\
|
||||
Note that both zeros and NaNs can carry signs.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_snan,
|
||||
"is_snan($self, /)\n--\n\n\
|
||||
Return True if the argument is a signaling NaN and False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_is_zero,
|
||||
"is_zero($self, /)\n--\n\n\
|
||||
Return True if the argument is a (positive or negative) zero and False\n\
|
||||
otherwise.\n\
|
||||
\n");
|
||||
|
||||
/******************************************************************************/
|
||||
/* Context Object and Methods */
|
||||
/******************************************************************************/
|
||||
|
@ -114,41 +67,11 @@ Create a new Decimal instance from float f. Unlike the Decimal.from_float()\n\
|
|||
class method, this function observes the context limits.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_abs,
|
||||
"abs($self, x, /)\n--\n\n\
|
||||
Return the absolute value of x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_add,
|
||||
"add($self, x, y, /)\n--\n\n\
|
||||
Return the sum of x and y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_canonical,
|
||||
"canonical($self, x, /)\n--\n\n\
|
||||
Return a new instance of x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_compare,
|
||||
"compare($self, x, y, /)\n--\n\n\
|
||||
Compare x and y numerically.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_compare_signal,
|
||||
"compare_signal($self, x, y, /)\n--\n\n\
|
||||
Compare x and y numerically. All NaNs signal.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_compare_total,
|
||||
"compare_total($self, x, y, /)\n--\n\n\
|
||||
Compare x and y using their abstract representation.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_compare_total_mag,
|
||||
"compare_total_mag($self, x, y, /)\n--\n\n\
|
||||
Compare x and y using their abstract representation, ignoring sign.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_copy_abs,
|
||||
"copy_abs($self, x, /)\n--\n\n\
|
||||
Return a copy of x with the sign set to 0.\n\
|
||||
|
@ -164,196 +87,16 @@ PyDoc_STRVAR(doc_ctx_copy_sign,
|
|||
Copy the sign from y to x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_divide,
|
||||
"divide($self, x, y, /)\n--\n\n\
|
||||
Return x divided by y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_divide_int,
|
||||
"divide_int($self, x, y, /)\n--\n\n\
|
||||
Return x divided by y, truncated to an integer.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_divmod,
|
||||
"divmod($self, x, y, /)\n--\n\n\
|
||||
Return quotient and remainder of the division x / y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_exp,
|
||||
"exp($self, x, /)\n--\n\n\
|
||||
Return e ** x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_fma,
|
||||
"fma($self, x, y, z, /)\n--\n\n\
|
||||
Return x multiplied by y, plus z.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_compare_total,
|
||||
"compare_total($self, /, other, context=None)\n--\n\n\
|
||||
Compare two operands using their abstract representation rather than\n\
|
||||
their numerical value. Similar to the compare() method, but the result\n\
|
||||
gives a total ordering on Decimal instances. Two Decimal instances with\n\
|
||||
the same numeric value but different representations compare unequal\n\
|
||||
in this ordering:\n\
|
||||
\n\
|
||||
>>> Decimal('12.0').compare_total(Decimal('12'))\n\
|
||||
Decimal('-1')\n\
|
||||
\n\
|
||||
Quiet and signaling NaNs are also included in the total ordering. The result\n\
|
||||
of this function is Decimal('0') if both operands have the same representation,\n\
|
||||
Decimal('-1') if the first operand is lower in the total order than the second,\n\
|
||||
and Decimal('1') if the first operand is higher in the total order than the\n\
|
||||
second operand. See the specification for details of the total order.\n\
|
||||
\n\
|
||||
This operation is unaffected by context and is quiet: no flags are changed\n\
|
||||
and no rounding is performed. As an exception, the C version may raise\n\
|
||||
InvalidOperation if the second operand cannot be converted exactly.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_compare_total_mag,
|
||||
"compare_total_mag($self, /, other, context=None)\n--\n\n\
|
||||
Compare two operands using their abstract representation rather than their\n\
|
||||
value as in compare_total(), but ignoring the sign of each operand.\n\
|
||||
\n\
|
||||
x.compare_total_mag(y) is equivalent to x.copy_abs().compare_total(y.copy_abs()).\n\
|
||||
\n\
|
||||
This operation is unaffected by context and is quiet: no flags are changed\n\
|
||||
and no rounding is performed. As an exception, the C version may raise\n\
|
||||
InvalidOperation if the second operand cannot be converted exactly.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_canonical,
|
||||
"is_canonical($self, x, /)\n--\n\n\
|
||||
Return True if x is canonical, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_finite,
|
||||
"is_finite($self, x, /)\n--\n\n\
|
||||
Return True if x is finite, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_infinite,
|
||||
"is_infinite($self, x, /)\n--\n\n\
|
||||
Return True if x is infinite, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_nan,
|
||||
"is_nan($self, x, /)\n--\n\n\
|
||||
Return True if x is a qNaN or sNaN, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_normal,
|
||||
"is_normal($self, x, /)\n--\n\n\
|
||||
Return True if x is a normal number, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_qnan,
|
||||
"is_qnan($self, x, /)\n--\n\n\
|
||||
Return True if x is a quiet NaN, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_signed,
|
||||
"is_signed($self, x, /)\n--\n\n\
|
||||
Return True if x is negative, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_snan,
|
||||
"is_snan($self, x, /)\n--\n\n\
|
||||
Return True if x is a signaling NaN, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_subnormal,
|
||||
"is_subnormal($self, x, /)\n--\n\n\
|
||||
Return True if x is subnormal, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_is_zero,
|
||||
"is_zero($self, x, /)\n--\n\n\
|
||||
Return True if x is a zero, False otherwise.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_ln,
|
||||
"ln($self, x, /)\n--\n\n\
|
||||
Return the natural (base e) logarithm of x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_log10,
|
||||
"log10($self, x, /)\n--\n\n\
|
||||
Return the base 10 logarithm of x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_logb,
|
||||
"logb($self, x, /)\n--\n\n\
|
||||
Return the exponent of the magnitude of the operand's MSD.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_logical_and,
|
||||
"logical_and($self, x, y, /)\n--\n\n\
|
||||
Digit-wise and of x and y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_logical_invert,
|
||||
"logical_invert($self, x, /)\n--\n\n\
|
||||
Invert all digits of x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_logical_or,
|
||||
"logical_or($self, x, y, /)\n--\n\n\
|
||||
Digit-wise or of x and y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_logical_xor,
|
||||
"logical_xor($self, x, y, /)\n--\n\n\
|
||||
Digit-wise xor of x and y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_max,
|
||||
"max($self, x, y, /)\n--\n\n\
|
||||
Compare the values numerically and return the maximum.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_max_mag,
|
||||
"max_mag($self, x, y, /)\n--\n\n\
|
||||
Compare the values numerically with their sign ignored.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_min,
|
||||
"min($self, x, y, /)\n--\n\n\
|
||||
Compare the values numerically and return the minimum.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_min_mag,
|
||||
"min_mag($self, x, y, /)\n--\n\n\
|
||||
Compare the values numerically with their sign ignored.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_minus,
|
||||
"minus($self, x, /)\n--\n\n\
|
||||
Minus corresponds to the unary prefix minus operator in Python, but applies\n\
|
||||
the context to the result.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_multiply,
|
||||
"multiply($self, x, y, /)\n--\n\n\
|
||||
Return the product of x and y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_next_minus,
|
||||
"next_minus($self, x, /)\n--\n\n\
|
||||
Return the largest representable number smaller than x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_next_plus,
|
||||
"next_plus($self, x, /)\n--\n\n\
|
||||
Return the smallest representable number larger than x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_next_toward,
|
||||
"next_toward($self, x, y, /)\n--\n\n\
|
||||
Return the number closest to x, in the direction towards y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_normalize,
|
||||
"normalize($self, x, /)\n--\n\n\
|
||||
Reduce x to its simplest form. Alias for reduce(x).\n\
|
||||
|
@ -364,79 +107,16 @@ PyDoc_STRVAR(doc_ctx_number_class,
|
|||
Return an indication of the class of x.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_plus,
|
||||
"plus($self, x, /)\n--\n\n\
|
||||
Plus corresponds to the unary prefix plus operator in Python, but applies\n\
|
||||
the context to the result.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_quantize,
|
||||
"quantize($self, x, y, /)\n--\n\n\
|
||||
Return a value equal to x (rounded), having the exponent of y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_remainder,
|
||||
"remainder($self, x, y, /)\n--\n\n\
|
||||
Return the remainder from integer division. The sign of the result,\n\
|
||||
if non-zero, is the same as that of the original dividend.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_remainder_near,
|
||||
"remainder_near($self, x, y, /)\n--\n\n\
|
||||
Return x - y * n, where n is the integer nearest the exact value of x / y\n\
|
||||
(if the result is 0 then its sign will be the sign of x).\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_rotate,
|
||||
"rotate($self, x, y, /)\n--\n\n\
|
||||
Return a copy of x, rotated by y places.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_same_quantum,
|
||||
"same_quantum($self, x, y, /)\n--\n\n\
|
||||
Return True if the two operands have the same exponent.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_scaleb,
|
||||
"scaleb($self, x, y, /)\n--\n\n\
|
||||
Return the first operand after adding the second value to its exp.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_shift,
|
||||
"shift($self, x, y, /)\n--\n\n\
|
||||
Return a copy of x, shifted by y places.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_sqrt,
|
||||
"sqrt($self, x, /)\n--\n\n\
|
||||
Square root of a non-negative number to context precision.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_subtract,
|
||||
"subtract($self, x, y, /)\n--\n\n\
|
||||
Return the difference between x and y.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_to_eng_string,
|
||||
"to_eng_string($self, x, /)\n--\n\n\
|
||||
Convert a number to a string, using engineering notation.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_to_integral,
|
||||
"to_integral($self, x, /)\n--\n\n\
|
||||
Identical to to_integral_value(x).\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_to_integral_exact,
|
||||
"to_integral_exact($self, x, /)\n--\n\n\
|
||||
Round to an integer. Signal if the result is rounded or inexact.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_to_integral_value,
|
||||
"to_integral_value($self, x, /)\n--\n\n\
|
||||
Round to an integer.\n\
|
||||
\n");
|
||||
|
||||
PyDoc_STRVAR(doc_ctx_to_sci_string,
|
||||
"to_sci_string($self, x, /)\n--\n\n\
|
||||
Convert a number to a string using scientific notation.\n\
|
||||
|
|
|
@ -1603,7 +1603,7 @@ queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
PyObject *obj;
|
||||
int unboundarg = -1;
|
||||
int fallbackarg = -1;
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|ii$p:put", kwlist,
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|ii:put", kwlist,
|
||||
qidarg_converter, &qidarg, &obj,
|
||||
&unboundarg, &fallbackarg))
|
||||
{
|
||||
|
|
|
@ -2971,7 +2971,7 @@ dummy_func(
|
|||
assert(tstate->current_executor == NULL);
|
||||
assert(executor != tstate->interp->cold_executor);
|
||||
tstate->jit_exit = NULL;
|
||||
GOTO_TIER_TWO(executor);
|
||||
TIER1_TO_TIER2(executor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -3037,7 +3037,7 @@ dummy_func(
|
|||
}
|
||||
assert(executor != tstate->interp->cold_executor);
|
||||
tstate->jit_exit = NULL;
|
||||
GOTO_TIER_TWO(executor);
|
||||
TIER1_TO_TIER2(executor);
|
||||
#else
|
||||
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
|
||||
#endif /* _Py_TIER2 */
|
||||
|
@ -5257,7 +5257,7 @@ dummy_func(
|
|||
}
|
||||
#endif
|
||||
tstate->jit_exit = exit;
|
||||
GOTO_TIER_TWO(exit->executor);
|
||||
TIER2_TO_TIER2(exit->executor);
|
||||
}
|
||||
|
||||
tier2 op(_CHECK_VALIDITY, (--)) {
|
||||
|
@ -5353,7 +5353,7 @@ dummy_func(
|
|||
|
||||
tier2 op(_START_EXECUTOR, (executor/4 --)) {
|
||||
#ifndef _Py_JIT
|
||||
current_executor = (_PyExecutorObject*)executor;
|
||||
assert(current_executor == (_PyExecutorObject*)executor);
|
||||
#endif
|
||||
assert(tstate->jit_exit == NULL || tstate->jit_exit->executor == current_executor);
|
||||
tstate->current_executor = (PyObject *)executor;
|
||||
|
@ -5434,7 +5434,7 @@ dummy_func(
|
|||
}
|
||||
assert(tstate->jit_exit == exit);
|
||||
exit->executor = executor;
|
||||
GOTO_TIER_TWO(exit->executor);
|
||||
TIER2_TO_TIER2(exit->executor);
|
||||
}
|
||||
|
||||
label(pop_2_error) {
|
||||
|
|
|
@ -275,7 +275,8 @@ maybe_lltrace_resume_frame(_PyInterpreterFrame *frame, PyObject *globals)
|
|||
}
|
||||
int r = PyDict_Contains(globals, &_Py_ID(__lltrace__));
|
||||
if (r < 0) {
|
||||
return -1;
|
||||
PyErr_Clear();
|
||||
return 0;
|
||||
}
|
||||
int lltrace = r * 5; // Levels 1-4 only trace uops
|
||||
if (!lltrace) {
|
||||
|
@ -1109,11 +1110,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
#endif
|
||||
}
|
||||
|
||||
#if defined(_Py_TIER2) && !defined(_Py_JIT)
|
||||
/* Tier 2 interpreter state */
|
||||
_PyExecutorObject *current_executor = NULL;
|
||||
const _PyUOpInstruction *next_uop = NULL;
|
||||
#endif
|
||||
#if Py_TAIL_CALL_INTERP
|
||||
# if Py_STATS
|
||||
return _TAIL_CALL_start_frame(frame, NULL, tstate, NULL, 0, lastopcode);
|
||||
|
@ -1126,14 +1122,41 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
#endif
|
||||
|
||||
|
||||
early_exit:
|
||||
assert(_PyErr_Occurred(tstate));
|
||||
_Py_LeaveRecursiveCallPy(tstate);
|
||||
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
|
||||
// GH-99729: We need to unlink the frame *before* clearing it:
|
||||
_PyInterpreterFrame *dying = frame;
|
||||
frame = tstate->current_frame = dying->previous;
|
||||
_PyEval_FrameClearAndPop(tstate, dying);
|
||||
frame->return_offset = 0;
|
||||
assert(frame->owner == FRAME_OWNED_BY_INTERPRETER);
|
||||
/* Restore previous frame and exit */
|
||||
tstate->current_frame = frame->previous;
|
||||
return NULL;
|
||||
}
|
||||
#ifdef _Py_TIER2
|
||||
|
||||
// Tier 2 is also here!
|
||||
enter_tier_two:
|
||||
|
||||
#ifdef _Py_JIT
|
||||
assert(0);
|
||||
_PyJitEntryFuncPtr _Py_jit_entry = _Py_LazyJitTrampoline;
|
||||
#else
|
||||
_PyJitEntryFuncPtr _Py_jit_entry = _PyTier2Interpreter;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_Py_TIER2) && !defined(_Py_JIT)
|
||||
|
||||
_Py_CODEUNIT *
|
||||
_PyTier2Interpreter(
|
||||
_PyExecutorObject *current_executor, _PyInterpreterFrame *frame,
|
||||
_PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
) {
|
||||
const _PyUOpInstruction *next_uop;
|
||||
int oparg;
|
||||
tier2_start:
|
||||
|
||||
next_uop = current_executor->trace;
|
||||
assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT);
|
||||
|
||||
#undef LOAD_IP
|
||||
#define LOAD_IP(UNUSED) (void)0
|
||||
|
@ -1151,7 +1174,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
#undef ENABLE_SPECIALIZATION_FT
|
||||
#define ENABLE_SPECIALIZATION_FT 0
|
||||
|
||||
; // dummy statement after a label, before a declaration
|
||||
uint16_t uopcode;
|
||||
#ifdef Py_STATS
|
||||
int lastuop = 0;
|
||||
|
@ -1225,24 +1247,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
next_uop = current_executor->trace + target;
|
||||
goto tier2_dispatch;
|
||||
|
||||
#endif // _Py_JIT
|
||||
|
||||
}
|
||||
#endif // _Py_TIER2
|
||||
|
||||
early_exit:
|
||||
assert(_PyErr_Occurred(tstate));
|
||||
_Py_LeaveRecursiveCallPy(tstate);
|
||||
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
|
||||
// GH-99729: We need to unlink the frame *before* clearing it:
|
||||
_PyInterpreterFrame *dying = frame;
|
||||
frame = tstate->current_frame = dying->previous;
|
||||
_PyEval_FrameClearAndPop(tstate, dying);
|
||||
frame->return_offset = 0;
|
||||
assert(frame->owner == FRAME_OWNED_BY_INTERPRETER);
|
||||
/* Restore previous frame and exit */
|
||||
tstate->current_frame = frame->previous;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef DO_NOT_OPTIMIZE_INTERP_LOOP
|
||||
# pragma optimize("", on)
|
||||
|
|
|
@ -133,9 +133,6 @@ do { \
|
|||
_PyFrame_SetStackPointer(frame, stack_pointer); \
|
||||
int lltrace = maybe_lltrace_resume_frame(frame, GLOBALS()); \
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame); \
|
||||
if (lltrace < 0) { \
|
||||
JUMP_TO_LABEL(exit_unwind); \
|
||||
} \
|
||||
frame->lltrace = lltrace; \
|
||||
} while (0)
|
||||
#else
|
||||
|
@ -354,16 +351,10 @@ _PyFrame_SetStackPointer(frame, stack_pointer)
|
|||
|
||||
/* Tier-switching macros. */
|
||||
|
||||
#ifdef _Py_JIT
|
||||
#define GOTO_TIER_TWO(EXECUTOR) \
|
||||
#define TIER1_TO_TIER2(EXECUTOR) \
|
||||
do { \
|
||||
OPT_STAT_INC(traces_executed); \
|
||||
_PyExecutorObject *_executor = (EXECUTOR); \
|
||||
jit_func jitted = _executor->jit_code; \
|
||||
/* Keep the shim frame alive via the executor: */ \
|
||||
Py_INCREF(_executor); \
|
||||
next_instr = jitted(frame, stack_pointer, tstate); \
|
||||
Py_DECREF(_executor); \
|
||||
next_instr = _Py_jit_entry((EXECUTOR), frame, stack_pointer, tstate); \
|
||||
frame = tstate->current_frame; \
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame); \
|
||||
if (next_instr == NULL) { \
|
||||
|
@ -372,31 +363,21 @@ do { \
|
|||
} \
|
||||
DISPATCH(); \
|
||||
} while (0)
|
||||
#else
|
||||
#define GOTO_TIER_TWO(EXECUTOR) \
|
||||
do { \
|
||||
OPT_STAT_INC(traces_executed); \
|
||||
_PyExecutorObject *_executor = (EXECUTOR); \
|
||||
next_uop = _executor->trace; \
|
||||
assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT); \
|
||||
goto enter_tier_two; \
|
||||
|
||||
#define TIER2_TO_TIER2(EXECUTOR) \
|
||||
do { \
|
||||
OPT_STAT_INC(traces_executed); \
|
||||
current_executor = (EXECUTOR); \
|
||||
goto tier2_start; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define GOTO_TIER_ONE(TARGET) \
|
||||
do \
|
||||
{ \
|
||||
tstate->current_executor = NULL; \
|
||||
next_instr = (TARGET); \
|
||||
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); \
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer); \
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame); \
|
||||
if (next_instr == NULL) \
|
||||
{ \
|
||||
next_instr = frame->instr_ptr; \
|
||||
goto error; \
|
||||
} \
|
||||
DISPATCH(); \
|
||||
return TARGET; \
|
||||
} while (0)
|
||||
|
||||
#define CURRENT_OPARG() (next_uop[-1].oparg)
|
||||
|
|
6
Python/executor_cases.c.h
generated
6
Python/executor_cases.c.h
generated
|
@ -7122,7 +7122,7 @@
|
|||
}
|
||||
#endif
|
||||
tstate->jit_exit = exit;
|
||||
GOTO_TIER_TWO(exit->executor);
|
||||
TIER2_TO_TIER2(exit->executor);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -7400,7 +7400,7 @@
|
|||
case _START_EXECUTOR: {
|
||||
PyObject *executor = (PyObject *)CURRENT_OPERAND0();
|
||||
#ifndef _Py_JIT
|
||||
current_executor = (_PyExecutorObject*)executor;
|
||||
assert(current_executor == (_PyExecutorObject*)executor);
|
||||
#endif
|
||||
assert(tstate->jit_exit == NULL || tstate->jit_exit->executor == current_executor);
|
||||
tstate->current_executor = (PyObject *)executor;
|
||||
|
@ -7503,7 +7503,7 @@
|
|||
}
|
||||
assert(tstate->jit_exit == exit);
|
||||
exit->executor = executor;
|
||||
GOTO_TIER_TWO(exit->executor);
|
||||
TIER2_TO_TIER2(exit->executor);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
4
Python/generated_cases.c.h
generated
4
Python/generated_cases.c.h
generated
|
@ -5493,7 +5493,7 @@
|
|||
}
|
||||
assert(executor != tstate->interp->cold_executor);
|
||||
tstate->jit_exit = NULL;
|
||||
GOTO_TIER_TWO(executor);
|
||||
TIER1_TO_TIER2(executor);
|
||||
#else
|
||||
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
|
||||
#endif /* _Py_TIER2 */
|
||||
|
@ -7667,7 +7667,7 @@
|
|||
assert(tstate->current_executor == NULL);
|
||||
assert(executor != tstate->interp->cold_executor);
|
||||
tstate->jit_exit = NULL;
|
||||
GOTO_TIER_TWO(executor);
|
||||
TIER1_TO_TIER2(executor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
78
Python/jit.c
78
Python/jit.c
|
@ -494,10 +494,6 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz
|
|||
size_t code_size = 0;
|
||||
size_t data_size = 0;
|
||||
jit_state state = {0};
|
||||
group = &shim;
|
||||
code_size += group->code_size;
|
||||
data_size += group->data_size;
|
||||
combine_symbol_mask(group->trampoline_mask, state.trampolines.mask);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
const _PyUOpInstruction *instruction = &trace[i];
|
||||
group = &stencil_groups[instruction->opcode];
|
||||
|
@ -539,13 +535,6 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz
|
|||
unsigned char *code = memory;
|
||||
state.trampolines.mem = memory + code_size;
|
||||
unsigned char *data = memory + code_size + state.trampolines.size + code_padding;
|
||||
// Compile the shim, which handles converting between the native
|
||||
// calling convention and the calling convention used by jitted code
|
||||
// (which may be different for efficiency reasons).
|
||||
group = &shim;
|
||||
group->emit(code, data, executor, NULL, &state);
|
||||
code += group->code_size;
|
||||
data += group->data_size;
|
||||
assert(trace[0].opcode == _START_EXECUTOR || trace[0].opcode == _COLD_EXIT);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
const _PyUOpInstruction *instruction = &trace[i];
|
||||
|
@ -566,11 +555,75 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz
|
|||
return -1;
|
||||
}
|
||||
executor->jit_code = memory;
|
||||
executor->jit_side_entry = memory + shim.code_size;
|
||||
executor->jit_size = total_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* One-off compilation of the jit entry trampoline
|
||||
* We compile this once only as it effectively a normal
|
||||
* function, but we need to use the JIT because it needs
|
||||
* to understand the jit-specific calling convention.
|
||||
*/
|
||||
static _PyJitEntryFuncPtr
|
||||
compile_trampoline(void)
|
||||
{
|
||||
_PyExecutorObject dummy;
|
||||
const StencilGroup *group;
|
||||
size_t code_size = 0;
|
||||
size_t data_size = 0;
|
||||
jit_state state = {0};
|
||||
group = &trampoline;
|
||||
code_size += group->code_size;
|
||||
data_size += group->data_size;
|
||||
combine_symbol_mask(group->trampoline_mask, state.trampolines.mask);
|
||||
// Round up to the nearest page:
|
||||
size_t page_size = get_page_size();
|
||||
assert((page_size & (page_size - 1)) == 0);
|
||||
size_t code_padding = DATA_ALIGN - ((code_size + state.trampolines.size) & (DATA_ALIGN - 1));
|
||||
size_t padding = page_size - ((code_size + state.trampolines.size + code_padding + data_size) & (page_size - 1));
|
||||
size_t total_size = code_size + state.trampolines.size + code_padding + data_size + padding;
|
||||
unsigned char *memory = jit_alloc(total_size);
|
||||
if (memory == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
unsigned char *code = memory;
|
||||
state.trampolines.mem = memory + code_size;
|
||||
unsigned char *data = memory + code_size + state.trampolines.size + code_padding;
|
||||
// Compile the shim, which handles converting between the native
|
||||
// calling convention and the calling convention used by jitted code
|
||||
// (which may be different for efficiency reasons).
|
||||
group = &trampoline;
|
||||
group->emit(code, data, &dummy, NULL, &state);
|
||||
code += group->code_size;
|
||||
data += group->data_size;
|
||||
assert(code == memory + code_size);
|
||||
assert(data == memory + code_size + state.trampolines.size + code_padding + data_size);
|
||||
if (mark_executable(memory, total_size)) {
|
||||
jit_free(memory, total_size);
|
||||
return NULL;
|
||||
}
|
||||
return (_PyJitEntryFuncPtr)memory;
|
||||
}
|
||||
|
||||
static PyMutex lazy_jit_mutex = { 0 };
|
||||
|
||||
_Py_CODEUNIT *
|
||||
_Py_LazyJitTrampoline(
|
||||
_PyExecutorObject *executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
) {
|
||||
PyMutex_Lock(&lazy_jit_mutex);
|
||||
if (_Py_jit_entry == _Py_LazyJitTrampoline) {
|
||||
_PyJitEntryFuncPtr trampoline = compile_trampoline();
|
||||
if (trampoline == NULL) {
|
||||
PyMutex_Unlock(&lazy_jit_mutex);
|
||||
Py_FatalError("Cannot allocate core JIT code");
|
||||
}
|
||||
_Py_jit_entry = trampoline;
|
||||
}
|
||||
PyMutex_Unlock(&lazy_jit_mutex);
|
||||
return _Py_jit_entry(executor, frame, stack_pointer, tstate);
|
||||
}
|
||||
|
||||
void
|
||||
_PyJIT_Free(_PyExecutorObject *executor)
|
||||
{
|
||||
|
@ -578,7 +631,6 @@ _PyJIT_Free(_PyExecutorObject *executor)
|
|||
size_t size = executor->jit_size;
|
||||
if (memory) {
|
||||
executor->jit_code = NULL;
|
||||
executor->jit_side_entry = NULL;
|
||||
executor->jit_size = 0;
|
||||
if (jit_free(memory, size)) {
|
||||
PyErr_FormatUnraisable("Exception ignored while "
|
||||
|
|
|
@ -1238,7 +1238,6 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil
|
|||
#endif
|
||||
#ifdef _Py_JIT
|
||||
executor->jit_code = NULL;
|
||||
executor->jit_side_entry = NULL;
|
||||
executor->jit_size = 0;
|
||||
// This is initialized to true so we can prevent the executor
|
||||
// from being immediately detected as cold and invalidated.
|
||||
|
@ -1490,7 +1489,6 @@ _PyExecutor_GetColdExecutor(void)
|
|||
((_PyUOpInstruction *)cold->trace)->opcode = _COLD_EXIT;
|
||||
#ifdef _Py_JIT
|
||||
cold->jit_code = NULL;
|
||||
cold->jit_side_entry = NULL;
|
||||
cold->jit_size = 0;
|
||||
// This is initialized to true so we can prevent the executor
|
||||
// from being immediately detected as cold and invalidated.
|
||||
|
|
|
@ -494,6 +494,11 @@ free_interpreter(PyInterpreterState *interp)
|
|||
static inline int check_interpreter_whence(long);
|
||||
#endif
|
||||
|
||||
extern _Py_CODEUNIT *
|
||||
_Py_LazyJitTrampoline(
|
||||
struct _PyExecutorObject *exec, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
);
|
||||
|
||||
/* Get the interpreter state to a minimal consistent state.
|
||||
Further init happens in pylifecycle.c before it can be used.
|
||||
All fields not initialized here are expected to be zeroed out,
|
||||
|
|
|
@ -191,8 +191,8 @@ async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
|
|||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
work = pathlib.Path(tempdir).resolve()
|
||||
async with asyncio.TaskGroup() as group:
|
||||
coro = self._compile("shim", TOOLS_JIT / "shim.c", work)
|
||||
tasks.append(group.create_task(coro, name="shim"))
|
||||
coro = self._compile("trampoline", TOOLS_JIT / "trampoline.c", work)
|
||||
tasks.append(group.create_task(coro, name="trampoline"))
|
||||
template = TOOLS_JIT_TEMPLATE_C.read_text()
|
||||
for case, opname in cases_and_opnames:
|
||||
# Write out a copy of the template with *only* this case
|
||||
|
|
|
@ -22,11 +22,11 @@ def _dump_footer(
|
|||
yield " symbol_mask trampoline_mask;"
|
||||
yield "} StencilGroup;"
|
||||
yield ""
|
||||
yield f"static const StencilGroup shim = {groups['shim'].as_c('shim')};"
|
||||
yield f"static const StencilGroup trampoline = {groups['trampoline'].as_c('trampoline')};"
|
||||
yield ""
|
||||
yield "static const StencilGroup stencil_groups[MAX_UOP_ID + 1] = {"
|
||||
for opname, group in sorted(groups.items()):
|
||||
if opname == "shim":
|
||||
if opname == "trampoline":
|
||||
continue
|
||||
yield f" [{opname}] = {group.as_c(opname)},"
|
||||
yield "};"
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
#include "Python.h"
|
||||
|
||||
#include "pycore_ceval.h"
|
||||
#include "pycore_frame.h"
|
||||
#include "pycore_jit.h"
|
||||
|
||||
#include "jit.h"
|
||||
|
||||
_Py_CODEUNIT *
|
||||
_JIT_ENTRY(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate)
|
||||
{
|
||||
// Note that this is *not* a tail call:
|
||||
DECLARE_TARGET(_JIT_CONTINUE);
|
||||
return _JIT_CONTINUE(frame, stack_pointer, tstate);
|
||||
}
|
|
@ -46,12 +46,12 @@
|
|||
#undef CURRENT_TARGET
|
||||
#define CURRENT_TARGET() (_target)
|
||||
|
||||
#undef GOTO_TIER_TWO
|
||||
#define GOTO_TIER_TWO(EXECUTOR) \
|
||||
#undef TIER2_TO_TIER2
|
||||
#define TIER2_TO_TIER2(EXECUTOR) \
|
||||
do { \
|
||||
OPT_STAT_INC(traces_executed); \
|
||||
_PyExecutorObject *_executor = (EXECUTOR); \
|
||||
jit_func_preserve_none jitted = _executor->jit_side_entry; \
|
||||
jit_func_preserve_none jitted = _executor->jit_code; \
|
||||
__attribute__((musttail)) return jitted(frame, stack_pointer, tstate); \
|
||||
} while (0)
|
||||
|
||||
|
|
16
Tools/jit/trampoline.c
Normal file
16
Tools/jit/trampoline.c
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "Python.h"
|
||||
|
||||
#include "pycore_ceval.h"
|
||||
#include "pycore_frame.h"
|
||||
#include "pycore_jit.h"
|
||||
|
||||
#include "jit.h"
|
||||
|
||||
_Py_CODEUNIT *
|
||||
_JIT_ENTRY(
|
||||
_PyExecutorObject *exec, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
) {
|
||||
typedef DECLARE_TARGET((*jit_func));
|
||||
jit_func jitted = (jit_func)exec->jit_code;
|
||||
return jitted(frame, stack_pointer, tstate);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue