GH-131498: Cases generator: Parse down to C statement level. (GH-131948)

* Parse down to statement level in the cases generator

* Add handling for #if macros, treating them much like normal ifs.
This commit is contained in:
Mark Shannon 2025-04-02 16:31:59 +01:00 committed by GitHub
parent 6e91d1f9aa
commit ad053d8d6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 795 additions and 959 deletions

View file

@ -457,7 +457,6 @@ class TestGeneratedCases(unittest.TestCase):
if (cond) {
JUMP_TO_LABEL(label);
}
// Comment is ok
DISPATCH();
}
"""
@ -586,7 +585,6 @@ class TestGeneratedCases(unittest.TestCase):
LABEL(somewhere)
{
}
"""
self.run_cases_test(input, output)
@ -1351,7 +1349,6 @@ class TestGeneratedCases(unittest.TestCase):
}
// THIRD
{
// Mark j and k as used
if (cond) {
JUMP_TO_LABEL(pop_2_error);
}
@ -1757,17 +1754,14 @@ class TestGeneratedCases(unittest.TestCase):
output = """
LABEL(other_label)
{
}
LABEL(other_label2)
{
}
LABEL(my_label)
{
// Comment
_PyFrame_SetStackPointer(frame, stack_pointer);
do_thing();
stack_pointer = _PyFrame_GetStackPointer(frame);
@ -1795,7 +1789,6 @@ class TestGeneratedCases(unittest.TestCase):
output = """
LABEL(one)
{
/* STACK SPILLED */
stack_pointer = _PyFrame_GetStackPointer(frame);
JUMP_TO_LABEL(two);
}
@ -1851,7 +1844,6 @@ class TestGeneratedCases(unittest.TestCase):
output = """
LABEL(my_label_1)
{
// Comment
_PyFrame_SetStackPointer(frame, stack_pointer);
do_thing1();
stack_pointer = _PyFrame_GetStackPointer(frame);
@ -1860,7 +1852,6 @@ class TestGeneratedCases(unittest.TestCase):
LABEL(my_label_2)
{
// Comment
_PyFrame_SetStackPointer(frame, stack_pointer);
do_thing2();
stack_pointer = _PyFrame_GetStackPointer(frame);

View file

@ -163,7 +163,7 @@ dummy_func(
op(_CHECK_PERIODIC_IF_NOT_YIELD_FROM, (--)) {
if ((oparg & RESUME_OPARG_LOCATION_MASK) < RESUME_AFTER_YIELD_FROM) {
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
QSBR_QUIESCENT_STATE(tstate); \
QSBR_QUIESCENT_STATE(tstate);
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) {
int err = _Py_HandlePending(tstate);
ERROR_IF(err != 0, error);
@ -2245,7 +2245,8 @@ dummy_func(
PyObject *attr_o = FT_ATOMIC_LOAD_PTR_ACQUIRE(*value_ptr);
DEOPT_IF(attr_o == NULL);
#ifdef Py_GIL_DISABLED
if (!_Py_TryIncrefCompareStackRef(value_ptr, attr_o, &attr)) {
int increfed = _Py_TryIncrefCompareStackRef(value_ptr, attr_o, &attr);
if (!increfed) {
DEOPT_IF(true);
}
#else
@ -2322,7 +2323,8 @@ dummy_func(
}
STAT_INC(LOAD_ATTR, hit);
#ifdef Py_GIL_DISABLED
if (!_Py_TryIncrefCompareStackRef(&ep->me_value, attr_o, &attr)) {
int increfed = _Py_TryIncrefCompareStackRef(&ep->me_value, attr_o, &attr);
if (!increfed) {
DEOPT_IF(true);
}
#else

View file

@ -30,7 +30,7 @@
oparg = CURRENT_OPARG();
if ((oparg & RESUME_OPARG_LOCATION_MASK) < RESUME_AFTER_YIELD_FROM) {
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
QSBR_QUIESCENT_STATE(tstate); \
QSBR_QUIESCENT_STATE(tstate);
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) {
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = _Py_HandlePending(tstate);
@ -601,11 +601,6 @@
case _END_FOR: {
_PyStackRef value;
value = stack_pointer[-1];
/* Don't update instr_ptr, so that POP_ITER sees
* the FOR_ITER as the previous instruction.
* This has the benign side effect that if value is
* finalized it will see the location as the FOR_ITER's.
*/
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -749,7 +744,6 @@
_PyStackRef value;
_PyStackRef res;
value = stack_pointer[-1];
// This one is a bit weird, because we expect *some* failures:
if (!PyStackRef_IsNone(value)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -1111,17 +1105,6 @@
JUMP_TO_JUMP_TARGET();
}
STAT_INC(BINARY_OP, hit);
/* Handle `left = left + right` or `left += right` for str.
*
* When possible, extend `left` in place rather than
* allocating a new PyUnicodeObject. This attempts to avoid
* quadratic behavior when one neglects to use str.join().
*
* If `left` has only two references remaining (one from
* the stack, one in the locals), DECREFing `left` leaves
* only the locals reference, so PyUnicode_Append knows
* that the string is safe to mutate.
*/
assert(Py_REFCNT(left_o) >= 2 || !PyStackRef_IsHeapSafe(left));
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local);
@ -1139,8 +1122,7 @@
JUMP_TO_ERROR();
}
#if TIER_ONE
// The STORE_FAST is already done. This is done here in tier one,
// and during trace projection in tier two:
assert(next_instr->op.code == STORE_FAST);
SKIP_OVER(1);
#endif
@ -1213,8 +1195,6 @@
PyStackRef_AsPyObjectSteal(stop));
stack_pointer = _PyFrame_GetStackPointer(frame);
PyObject *res_o;
// Can't use ERROR_IF() here, because we haven't
// DECREF'ed container yet, and we still own slice.
if (slice == NULL) {
res_o = NULL;
}
@ -1303,7 +1283,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// Deopt unless 0 <= sub < PyList_Size(list)
if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -1364,7 +1343,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// Specialize for reading an ASCII character from any string:
Py_UCS4 c = PyUnicode_READ_CHAR(str, index);
if (Py_ARRAY_LENGTH(_Py_SINGLETON(strings).ascii) <= c) {
UOP_STAT_INC(uopcode, miss);
@ -1398,7 +1376,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// Deopt unless 0 <= sub < PyTuple_Size(list)
if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -1461,7 +1438,6 @@
if (rc <= 0) {
JUMP_TO_ERROR();
}
// not found or error
res = PyStackRef_FromPyObjectSteal(res_o);
stack_pointer[0] = res;
stack_pointer += 1;
@ -1567,7 +1543,6 @@
sub = stack_pointer[-1];
container = stack_pointer[-2];
v = stack_pointer[-3];
/* container[sub] = v */
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = PyObject_SetItem(PyStackRef_AsPyObjectBorrow(container), PyStackRef_AsPyObjectBorrow(sub), PyStackRef_AsPyObjectBorrow(v));
_PyStackRef tmp = sub;
@ -1605,7 +1580,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// Ensure nonnegative, zero-or-one-digit ints.
if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -1615,7 +1589,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// Ensure index < len(list)
if (index >= PyList_GET_SIZE(list)) {
UNLOCK_OBJECT(list);
if (true) {
@ -1628,7 +1601,7 @@
FT_ATOMIC_STORE_PTR_RELEASE(_PyList_ITEMS(list)[index],
PyStackRef_AsPyObjectSteal(value));
assert(old_value != NULL);
UNLOCK_OBJECT(list); // unlock before decrefs!
UNLOCK_OBJECT(list);
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
stack_pointer += -3;
assert(WITHIN_STACK_BOUNDS());
@ -1673,7 +1646,6 @@
_PyStackRef container;
sub = stack_pointer[-1];
container = stack_pointer[-2];
/* del container[sub] */
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = PyObject_DelItem(PyStackRef_AsPyObjectBorrow(container),
PyStackRef_AsPyObjectBorrow(sub));
@ -1762,7 +1734,6 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
assert(EMPTY());
_Py_LeaveRecursiveCallPy(tstate);
// GH-99729: We need to unlink the frame *before* clearing it:
_PyInterpreterFrame *dying = frame;
frame = tstate->current_frame = dying->previous;
_PyEval_FrameClearAndPop(tstate, dying);
@ -1906,9 +1877,6 @@
_PyStackRef value;
oparg = CURRENT_OPARG();
retval = stack_pointer[-1];
// NOTE: It's important that YIELD_VALUE never raises an exception!
// The compiler treats any exception raised here as a failed close()
// or throw() call.
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
frame->instr_ptr++;
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
@ -1925,7 +1893,6 @@
_PyInterpreterFrame *gen_frame = frame;
frame = tstate->current_frame = frame->previous;
gen_frame->previous = NULL;
/* We don't know which of these is relevant here, so keep them equal */
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
#if TIER_ONE
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||
@ -1962,7 +1929,6 @@
case _LOAD_COMMON_CONSTANT: {
_PyStackRef value;
oparg = CURRENT_OPARG();
// Keep in sync with _common_constants in opcode.py
assert(oparg < NUM_COMMON_CONSTANTS);
value = PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
stack_pointer[0] = value;
@ -2049,7 +2015,6 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
err = PyObject_DelItem(ns, name);
stack_pointer = _PyFrame_GetStackPointer(frame);
// Can't use ERROR_IF here.
if (err != 0) {
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyEval_FormatExcCheckArg(tstate, PyExc_NameError,
@ -2268,7 +2233,6 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = PyDict_Pop(GLOBALS(), name, NULL);
stack_pointer = _PyFrame_GetStackPointer(frame);
// Can't use ERROR_IF here.
if (err < 0) {
JUMP_TO_ERROR();
}
@ -2459,8 +2423,6 @@
case _MAKE_CELL: {
oparg = CURRENT_OPARG();
// "initial" is probably NULL but not if it's an arg (or set
// via the f_locals proxy before MAKE_CELL has run).
PyObject *initial = PyStackRef_AsPyObjectBorrow(GETLOCAL(oparg));
PyObject *cell = PyCell_New(initial);
if (cell == NULL) {
@ -2477,8 +2439,6 @@
case _DELETE_DEREF: {
oparg = CURRENT_OPARG();
PyObject *cell = PyStackRef_AsPyObjectBorrow(GETLOCAL(oparg));
// Can't use ERROR_IF here.
// Fortunately we don't need its superpower.
PyObject *oldobj = PyCell_SwapTakeRef((PyCellObject *)cell, NULL);
if (oldobj == NULL) {
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -2568,7 +2528,6 @@
case _COPY_FREE_VARS: {
oparg = CURRENT_OPARG();
/* Copy closure variables to free variables */
PyCodeObject *co = _PyFrame_GetCode(frame);
assert(PyStackRef_FunctionCheck(frame->f_funcobj));
PyFunctionObject *func = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(frame->f_funcobj);
@ -2825,7 +2784,6 @@
stack_pointer = _PyFrame_GetStackPointer(frame);
JUMP_TO_ERROR();
}
/* check if __annotations__ in locals()... */
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = PyMapping_GetOptionalItem(LOCALS(), &_Py_ID(__annotations__), &ann_dict);
stack_pointer = _PyFrame_GetStackPointer(frame);
@ -2936,8 +2894,6 @@
dict_st = stack_pointer[-3 - (oparg - 1)];
PyObject *dict = PyStackRef_AsPyObjectBorrow(dict_st);
assert(PyDict_CheckExact(dict));
/* dict[key] = value */
// Do not DECREF INPUTS because the function steals the references
_PyFrame_SetStackPointer(frame, stack_pointer);
int err = _PyDict_SetItem_Take2(
(PyDictObject *)dict,
@ -3039,7 +2995,7 @@
JUMP_TO_ERROR();
}
if (method_found) {
self_or_null = self_st; // transfer ownership
self_or_null = self_st;
} else {
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -3082,26 +3038,15 @@
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
PyObject *attr_o;
if (oparg & 1) {
/* Designed to work in tandem with CALL, pushes two values. */
attr_o = NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (is_meth) {
/* We can bypass temporary bound method object.
meth is unbound method and obj is self.
meth | self | arg1 | ... | argN
*/
assert(attr_o != NULL); // No errors on this branch
self_or_null[0] = owner; // Transfer ownership
assert(attr_o != NULL);
self_or_null[0] = owner;
}
else {
/* meth is not an unbound method (but a regular attr, or
something was returned by a descriptor protocol). Set
the second element of the stack to NULL, to signal
CALL that it's not a method call.
meth | NULL | arg1 | ... | argN
*/
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -3116,7 +3061,6 @@
}
}
else {
/* Classic, pushes one value. */
_PyFrame_SetStackPointer(frame, stack_pointer);
attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
stack_pointer = _PyFrame_GetStackPointer(frame);
@ -3198,7 +3142,8 @@
JUMP_TO_JUMP_TARGET();
}
#ifdef Py_GIL_DISABLED
if (!_Py_TryIncrefCompareStackRef(value_ptr, attr_o, &attr)) {
int increfed = _Py_TryIncrefCompareStackRef(value_ptr, attr_o, &attr);
if (!increfed) {
if (true) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -3311,7 +3256,8 @@
}
STAT_INC(LOAD_ATTR, hit);
#ifdef Py_GIL_DISABLED
if (!_Py_TryIncrefCompareStackRef(&ep->me_value, attr_o, &attr)) {
int increfed = _Py_TryIncrefCompareStackRef(&ep->me_value, attr_o, &attr);
if (!increfed) {
if (true) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -3529,8 +3475,6 @@
stack_pointer = _PyFrame_GetStackPointer(frame);
FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value));
UNLOCK_OBJECT(dict);
// old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
// when dict only holds the strong reference to value in ep->me_value.
STAT_INC(STORE_ATTR, hit);
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
@ -3623,12 +3567,10 @@
STAT_INC(COMPARE_OP, hit);
double dleft = PyFloat_AS_DOUBLE(left_o);
double dright = PyFloat_AS_DOUBLE(right_o);
// 1 if NaN, 2 if <, 4 if >, 8 if ==; this matches low four bits of the oparg
int sign_ish = COMPARISON_BIT(dleft, dright);
PyStackRef_CLOSE_SPECIALIZED(left, _PyFloat_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(right, _PyFloat_ExactDealloc);
res = (sign_ish & oparg) ? PyStackRef_True : PyStackRef_False;
// It's always a bool, so we don't care about oparg & 16.
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -3657,12 +3599,10 @@
_PyLong_DigitCount((PyLongObject *)right_o) <= 1);
Py_ssize_t ileft = _PyLong_CompactValue((PyLongObject *)left_o);
Py_ssize_t iright = _PyLong_CompactValue((PyLongObject *)right_o);
// 2 if <, 4 if >, 8 if ==; this matches the low 4 bits of the oparg
int sign_ish = COMPARISON_BIT(ileft, iright);
PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc);
res = (sign_ish & oparg) ? PyStackRef_True : PyStackRef_False;
// It's always a bool, so we don't care about oparg & 16.
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -3687,7 +3627,6 @@
assert((oparg & 0xf) == COMPARISON_NOT_EQUALS || (oparg & 0xf) == COMPARISON_EQUALS);
assert(COMPARISON_NOT_EQUALS + 1 == COMPARISON_EQUALS);
res = ((COMPARISON_NOT_EQUALS + eq) & oparg) ? PyStackRef_True : PyStackRef_False;
// It's always a bool, so we don't care about oparg & 16.
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -3767,7 +3706,6 @@
JUMP_TO_JUMP_TARGET();
}
STAT_INC(CONTAINS_OP, hit);
// Note: both set and frozenset use the same seq_contains method!
_PyFrame_SetStackPointer(frame, stack_pointer);
int res = _PySet_Contains((PySetObject *)right_o, left_o);
_PyStackRef tmp = right;
@ -4002,7 +3940,6 @@
_PyStackRef obj;
_PyStackRef len;
obj = stack_pointer[-1];
// PUSH(len(TOS))
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_ssize_t len_i = PyObject_Length(PyStackRef_AsPyObjectBorrow(obj));
stack_pointer = _PyFrame_GetStackPointer(frame);
@ -4029,8 +3966,6 @@
names = stack_pointer[-1];
type = stack_pointer[-2];
subject = stack_pointer[-3];
// Pop TOS and TOS1. Set TOS to a tuple of attributes on success, or
// None on failure.
assert(PyTuple_CheckExact(PyStackRef_AsPyObjectBorrow(names)));
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *attrs_o = _PyEval_MatchClass(tstate,
@ -4053,15 +3988,14 @@
stack_pointer += -3;
assert(WITHIN_STACK_BOUNDS());
if (attrs_o) {
assert(PyTuple_CheckExact(attrs_o)); // Success!
assert(PyTuple_CheckExact(attrs_o));
attrs = PyStackRef_FromPyObjectSteal(attrs_o);
}
else {
if (_PyErr_Occurred(tstate)) {
JUMP_TO_ERROR();
}
// Error!
attrs = PyStackRef_None; // Failure!
attrs = PyStackRef_None;
}
stack_pointer[0] = attrs;
stack_pointer += 1;
@ -4099,7 +4033,6 @@
_PyStackRef values_or_none;
keys = stack_pointer[-1];
subject = stack_pointer[-2];
// On successful match, PUSH(values). Otherwise, PUSH(None).
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *values_or_none_o = _PyEval_MatchKeys(tstate,
PyStackRef_AsPyObjectBorrow(subject), PyStackRef_AsPyObjectBorrow(keys));
@ -4118,7 +4051,6 @@
_PyStackRef iterable;
_PyStackRef iter;
iterable = stack_pointer[-1];
/* before: [obj]; after [getiter(obj)] */
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *iter_o = PyObject_GetIter(PyStackRef_AsPyObjectBorrow(iterable));
stack_pointer = _PyFrame_GetStackPointer(frame);
@ -4141,13 +4073,9 @@
_PyStackRef iterable;
_PyStackRef iter;
iterable = stack_pointer[-1];
/* before: [obj]; after [getiter(obj)] */
PyObject *iterable_o = PyStackRef_AsPyObjectBorrow(iterable);
if (PyCoro_CheckExact(iterable_o)) {
/* `iterable` is a coroutine */
if (!(_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE))) {
/* and it is used in a 'yield from' expression of a
regular generator. */
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyErr_SetString(tstate, PyExc_TypeError,
"cannot 'yield from' a coroutine object "
@ -4157,26 +4085,23 @@
}
iter = iterable;
}
else if (PyGen_CheckExact(iterable_o)) {
iter = iterable;
}
else {
if (PyGen_CheckExact(iterable_o)) {
iter = iterable;
}
else {
/* `iterable` is not a generator. */
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *iter_o = PyObject_GetIter(iterable_o);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (iter_o == NULL) {
JUMP_TO_ERROR();
}
iter = PyStackRef_FromPyObjectSteal(iter_o);
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyStackRef tmp = iterable;
iterable = iter;
stack_pointer[-1] = iterable;
PyStackRef_CLOSE(tmp);
stack_pointer = _PyFrame_GetStackPointer(frame);
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *iter_o = PyObject_GetIter(iterable_o);
stack_pointer = _PyFrame_GetStackPointer(frame);
if (iter_o == NULL) {
JUMP_TO_ERROR();
}
iter = PyStackRef_FromPyObjectSteal(iter_o);
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyStackRef tmp = iterable;
iterable = iter;
stack_pointer[-1] = iterable;
PyStackRef_CLOSE(tmp);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
stack_pointer[-1] = iter;
break;
@ -4188,7 +4113,6 @@
_PyStackRef iter;
_PyStackRef next;
iter = stack_pointer[-1];
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
@ -4206,15 +4130,12 @@
_PyErr_Clear(tstate);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
/* iterator ended normally */
/* The translator sets the deopt target just past the matching END_FOR */
if (true) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
}
next = PyStackRef_FromPyObjectSteal(next_o);
// Common case: no jump, leave it to the code generator
stack_pointer[0] = next;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@ -4290,8 +4211,6 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
int result = _PyList_GetItemRefNoLock(seq, it->it_index, &next);
stack_pointer = _PyFrame_GetStackPointer(frame);
// A negative result means we lost a race with another thread
// and we need to take the slow path.
if (result < 0) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -4440,10 +4359,7 @@
JUMP_TO_JUMP_TARGET();
}
#ifdef Py_GIL_DISABLED
// Since generators can't be used by multiple threads anyway we
// don't need to deopt here, but this lets us work on making
// generators thread-safe without necessarily having to
// specialize them thread-safely as well.
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -4460,7 +4376,6 @@
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
gen_frame->previous = frame;
// oparg is the return offset from the next instruction.
frame->return_offset = (uint16_t)( 2 + oparg);
stack_pointer[0].bits = (uintptr_t)gen_frame;
stack_pointer += 1;
@ -4513,15 +4428,6 @@
lasti = stack_pointer[-3];
exit_self = stack_pointer[-4];
exit_func = stack_pointer[-5];
/* At the top of the stack are 4 values:
- val: TOP = exc_info()
- unused: SECOND = previous exception
- lasti: THIRD = lasti of exception in exc_info()
- exit_self: FOURTH = the context or NULL
- exit_func: FIFTH = the context.__exit__ function or context.__exit__ bound method
We call FOURTH(type(TOP), TOP, GetTraceback(TOP)).
Then we push the __exit__ return value.
*/
PyObject *exc, *tb;
PyObject *val_o = PyStackRef_AsPyObjectBorrow(val);
PyObject *exit_func_o = PyStackRef_AsPyObjectBorrow(exit_func);
@ -4532,7 +4438,7 @@
tb = Py_None;
}
assert(PyStackRef_LongCheck(lasti));
(void)lasti; // Shut up compiler warning if asserts are off
(void)lasti;
PyObject *stack[5] = {NULL, PyStackRef_AsPyObjectBorrow(exit_self), exc, val_o, tb};
int has_self = !PyStackRef_IsNull(exit_self);
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -4607,7 +4513,6 @@
owner = stack_pointer[-1];
PyObject *descr = (PyObject *)CURRENT_OPERAND0();
assert(oparg & 1);
/* Cached method object */
STAT_INC(LOAD_ATTR, hit);
assert(descr != NULL);
assert(_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR));
@ -4690,7 +4595,6 @@
uint16_t dictoffset = (uint16_t)CURRENT_OPERAND0();
char *ptr = ((char *)PyStackRef_AsPyObjectBorrow(owner)) + MANAGED_DICT_OFFSET + dictoffset;
PyObject *dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*(PyObject **)ptr);
/* This object has a __dict__, just not yet created */
if (dict != NULL) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -4755,7 +4659,6 @@
self_or_null = &stack_pointer[-1 - oparg];
callable = &stack_pointer[-2 - oparg];
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable[0]);
// oparg counts all of the args, but *not* self:
int total_args = oparg;
if (!PyStackRef_IsNull(self_or_null[0])) {
args--;
@ -4770,7 +4673,6 @@
args, total_args, NULL, frame
);
stack_pointer = _PyFrame_GetStackPointer(frame);
// The frame has stolen all the arguments from the stack.
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
if (temp == NULL) {
@ -4895,7 +4797,6 @@
arguments--;
total_args++;
}
/* Callable is not a normal Python function */
STACKREFS_TO_PYOBJECTS(arguments, total_args, args_o);
if (CONVERSION_FAILED(args_o)) {
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -5174,8 +5075,6 @@
case _PUSH_FRAME: {
_PyInterpreterFrame *new_frame;
new_frame = (_PyInterpreterFrame *)stack_pointer[-1].bits;
// Write it out explicitly because it's subtly different.
// Eventually this should be the only occurrence of this code.
assert(tstate->interp->eval_frame == NULL);
_PyInterpreterFrame *temp = new_frame;
stack_pointer += -1;
@ -5365,7 +5264,6 @@
stack_pointer = _PyFrame_GetStackPointer(frame);
assert(_PyFrame_GetBytecode(shim)[0].op.code == EXIT_INIT_CHECK);
assert(_PyFrame_GetBytecode(shim)[1].op.code == RETURN_VALUE);
/* Push self onto stack of shim */
shim->localsplus[0] = PyStackRef_DUP(self[0]);
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyInterpreterFrame *temp = _PyEvalFramePushAndInit(
@ -5381,9 +5279,6 @@
}
init_frame = temp;
frame->return_offset = 1 + INLINE_CACHE_ENTRIES_CALL;
/* Account for pushing the extra frame.
* We don't check recursion depth here,
* as it will be checked after start_frame */
tstate->py_recursion_remaining--;
stack_pointer[0].bits = (uintptr_t)init_frame;
stack_pointer += 1;
@ -5493,7 +5388,6 @@
args = &stack_pointer[-oparg];
self_or_null = &stack_pointer[-1 - oparg];
callable = &stack_pointer[-2 - oparg];
/* Builtin METH_O functions */
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable[0]);
int total_args = oparg;
if (!PyStackRef_IsNull(self_or_null[0])) {
@ -5512,7 +5406,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// CPython promises to check all non-vectorcall function calls.
if (_Py_ReachedRecursionLimit(tstate)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -5552,7 +5445,6 @@
args = &stack_pointer[-oparg];
self_or_null = &stack_pointer[-1 - oparg];
callable = &stack_pointer[-2 - oparg];
/* Builtin METH_FASTCALL functions, without keywords */
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable[0]);
int total_args = oparg;
_PyStackRef *arguments = args;
@ -5570,7 +5462,6 @@
}
STAT_INC(CALL, hit);
PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable_o);
/* res = func(self, args, nargs) */
STACKREFS_TO_PYOBJECTS(arguments, total_args, args_o);
if (CONVERSION_FAILED(args_o)) {
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -5634,7 +5525,6 @@
args = &stack_pointer[-oparg];
self_or_null = &stack_pointer[-1 - oparg];
callable = &stack_pointer[-2 - oparg];
/* Builtin METH_FASTCALL | METH_KEYWORDS functions */
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable[0]);
int total_args = oparg;
_PyStackRef *arguments = args;
@ -5651,7 +5541,6 @@
JUMP_TO_JUMP_TARGET();
}
STAT_INC(CALL, hit);
/* res = func(self, arguments, nargs, kwnames) */
_PyFrame_SetStackPointer(frame, stack_pointer);
PyCFunctionFastWithKeywords cfunc =
(PyCFunctionFastWithKeywords)(void(*)(void))
@ -5717,7 +5606,6 @@
args = &stack_pointer[-oparg];
self_or_null = &stack_pointer[-1 - oparg];
callable = &stack_pointer[-2 - oparg];
/* len(o) */
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable[0]);
int total_args = oparg;
if (!PyStackRef_IsNull(self_or_null[0])) {
@ -5771,7 +5659,6 @@
args = &stack_pointer[-oparg];
self_or_null = &stack_pointer[-1 - oparg];
callable = stack_pointer[-2 - oparg];
/* isinstance(o, o2) */
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable);
int total_args = oparg;
_PyStackRef *arguments = args;
@ -5860,8 +5747,7 @@
JUMP_TO_ERROR();
}
#if TIER_ONE
// Skip the following POP_TOP. This is done here in tier one, and
// during trace projection in tier two:
assert(next_instr->op.code == POP_TOP);
SKIP_OVER(1);
#endif
@ -5898,7 +5784,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// CPython promises to check all non-vectorcall function calls.
if (_Py_ReachedRecursionLimit(tstate)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -6068,7 +5953,6 @@
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
// CPython promises to check all non-vectorcall function calls.
if (_Py_ReachedRecursionLimit(tstate)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -6115,7 +5999,6 @@
total_args++;
}
PyMethodDescrObject *method = (PyMethodDescrObject *)callable_o;
/* Builtin METH_FASTCALL methods, without keywords */
if (!Py_IS_TYPE(method, &PyMethodDescr_Type)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@ -6229,7 +6112,6 @@
self_or_null = &stack_pointer[-2 - oparg];
callable = &stack_pointer[-3 - oparg];
PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable[0]);
// oparg counts all of the args, but *not* self:
int total_args = oparg;
_PyStackRef *arguments = args;
if (!PyStackRef_IsNull(self_or_null[0])) {
@ -6252,8 +6134,6 @@
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(kwnames);
stack_pointer = _PyFrame_GetStackPointer(frame);
// The frame has stolen all the arguments from the stack,
// so there is no need to clean them up.
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
if (temp == NULL) {
@ -6368,7 +6248,6 @@
arguments--;
total_args++;
}
/* Callable is not a normal Python function */
STACKREFS_TO_PYOBJECTS(arguments, total_args, args_o);
if (CONVERSION_FAILED(args_o)) {
_PyFrame_SetStackPointer(frame, stack_pointer);
@ -6617,8 +6496,6 @@
_PyStackRef res;
value = stack_pointer[-1];
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
/* If value is a unicode object, then we know the result
* of format(value) is value itself. */
if (!PyUnicode_CheckExact(value_o)) {
_PyFrame_SetStackPointer(frame, stack_pointer);
PyObject *res_o = PyObject_Format(value_o, NULL);
@ -7012,7 +6889,6 @@
case _MAKE_WARM: {
current_executor->vm_data.warm = true;
// It's okay if this ends up going negative.
if (--tstate->interp->trace_run_counter == 0) {
_Py_set_eval_breaker_bit(tstate, _PY_EVAL_JIT_INVALIDATE_COLD_BIT);
}

File diff suppressed because it is too large Load diff

View file

@ -367,34 +367,39 @@ dummy_func(void) {
}
op(_TO_BOOL, (value -- res)) {
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
res = sym_new_truthiness(ctx, value, true);
}
}
op(_TO_BOOL_BOOL, (value -- res)) {
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_type(value, &PyBool_Type);
res = sym_new_truthiness(ctx, value, true);
}
}
op(_TO_BOOL_INT, (value -- res)) {
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_type(value, &PyLong_Type);
res = sym_new_truthiness(ctx, value, true);
}
}
op(_TO_BOOL_LIST, (value -- res)) {
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_type(value, &PyList_Type);
res = sym_new_type(ctx, &PyBool_Type);
}
}
op(_TO_BOOL_NONE, (value -- res)) {
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_const(value, Py_None);
res = sym_new_const(ctx, Py_False);
}
@ -415,7 +420,8 @@ dummy_func(void) {
}
op(_TO_BOOL_STR, (value -- res)) {
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
res = sym_new_truthiness(ctx, value, true);
}
}

View file

@ -28,7 +28,6 @@
case _LOAD_FAST_CHECK: {
JitOptSymbol *value;
value = GETLOCAL(oparg);
// We guarantee this will error - just bail and don't optimize it.
if (sym_is_null(value)) {
ctx->done = true;
}
@ -162,7 +161,8 @@
JitOptSymbol *value;
JitOptSymbol *res;
value = stack_pointer[-1];
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
res = sym_new_truthiness(ctx, value, true);
}
stack_pointer[-1] = res;
@ -173,7 +173,8 @@
JitOptSymbol *value;
JitOptSymbol *res;
value = stack_pointer[-1];
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_type(value, &PyBool_Type);
res = sym_new_truthiness(ctx, value, true);
}
@ -185,7 +186,8 @@
JitOptSymbol *value;
JitOptSymbol *res;
value = stack_pointer[-1];
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_type(value, &PyLong_Type);
res = sym_new_truthiness(ctx, value, true);
}
@ -197,7 +199,8 @@
JitOptSymbol *value;
JitOptSymbol *res;
value = stack_pointer[-1];
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_type(value, &PyList_Type);
res = sym_new_type(ctx, &PyBool_Type);
}
@ -209,7 +212,8 @@
JitOptSymbol *value;
JitOptSymbol *res;
value = stack_pointer[-1];
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
sym_set_const(value, Py_None);
res = sym_new_const(ctx, Py_False);
}
@ -241,7 +245,8 @@
JitOptSymbol *value;
JitOptSymbol *res;
value = stack_pointer[-1];
if (!optimize_to_bool(this_instr, ctx, value, &res)) {
int already_bool = optimize_to_bool(this_instr, ctx, value, &res);
if (!already_bool) {
res = sym_new_truthiness(ctx, value, true);
}
stack_pointer[-1] = res;
@ -301,8 +306,6 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Py_DECREF(temp);
// TODO gh-115506:
// replace opcode with constant propagated one and add tests!
}
else {
res = sym_new_type(ctx, &PyLong_Type);
@ -332,8 +335,6 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Py_DECREF(temp);
// TODO gh-115506:
// replace opcode with constant propagated one and add tests!
}
else {
res = sym_new_type(ctx, &PyLong_Type);
@ -363,8 +364,6 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Py_DECREF(temp);
// TODO gh-115506:
// replace opcode with constant propagated one and add tests!
}
else {
res = sym_new_type(ctx, &PyLong_Type);
@ -415,8 +414,6 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Py_DECREF(temp);
// TODO gh-115506:
// replace opcode with constant propagated one and update tests!
}
else {
res = sym_new_type(ctx, &PyFloat_Type);
@ -447,8 +444,6 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Py_DECREF(temp);
// TODO gh-115506:
// replace opcode with constant propagated one and update tests!
}
else {
res = sym_new_type(ctx, &PyFloat_Type);
@ -479,8 +474,6 @@
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
Py_DECREF(temp);
// TODO gh-115506:
// replace opcode with constant propagated one and update tests!
}
else {
res = sym_new_type(ctx, &PyFloat_Type);
@ -538,7 +531,6 @@
else {
res = sym_new_type(ctx, &PyUnicode_Type);
}
// _STORE_FAST:
GETLOCAL(this_instr->operand0) = res;
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
@ -690,7 +682,6 @@
ctx->frame->stack_pointer = stack_pointer;
frame_pop(ctx);
stack_pointer = ctx->frame->stack_pointer;
/* Stack space handling */
assert(corresponding_check_stack == NULL);
assert(co != NULL);
int framesize = co->co_framesize;
@ -699,7 +690,6 @@
curr_space -= framesize;
co = get_code(this_instr);
if (co == NULL) {
// might be impossible, but bailing is still safe
ctx->done = true;
}
res = temp;
@ -735,7 +725,6 @@
/* _SEND is not a viable micro-op for tier 2 */
case _SEND_GEN_FRAME: {
// We are about to hit the end of the trace:
ctx->done = true;
break;
}
@ -784,7 +773,6 @@
case _UNPACK_SEQUENCE: {
JitOptSymbol **values;
values = &stack_pointer[-1];
/* This has to be done manually */
for (int i = 0; i < oparg; i++) {
values[i] = sym_new_unknown(ctx);
}
@ -834,7 +822,6 @@
case _UNPACK_EX: {
JitOptSymbol **values;
values = &stack_pointer[-1];
/* This has to be done manually */
int totalargs = (oparg & 0xFF) + (oparg >> 8) + 1;
for (int i = 0; i < totalargs; i++) {
values[i] = sym_new_unknown(ctx);
@ -1097,15 +1084,8 @@
if (sym_matches_type_version(owner, type_version)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
} else {
// add watcher so that whenever the type changes we invalidate this
PyTypeObject *type = _PyType_LookupByVersion(type_version);
// if the type is null, it was not found in the cache (there was a conflict)
// with the key, in which case we can't trust the version
if (type) {
// if the type version was set properly, then add a watcher
// if it wasn't this means that the type version was previously set to something else
// and we set the owner to bottom, so we don't need to add a watcher because we must have
// already added one earlier.
if (sym_set_type_version(owner, type_version)) {
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type);
_Py_BloomFilter_Add(dependencies, type);
@ -1156,7 +1136,6 @@
}
}
if (attr == NULL) {
/* No conversion made. We don't know what `attr` is. */
attr = sym_new_not_null(ctx);
}
stack_pointer[-1] = attr;
@ -1507,7 +1486,6 @@
}
case _FOR_ITER_GEN_FRAME: {
/* We are about to hit the end of the trace */
ctx->done = true;
break;
}
@ -1712,8 +1690,6 @@
}
case _CHECK_PEP_523: {
/* Setting the eval frame function invalidates
* all executors, so no need to check dynamically */
if (_PyInterpreterState_GET()->eval_frame == NULL) {
REPLACE_OP(this_instr, _NOP, 0 ,0);
}
@ -1761,7 +1737,6 @@
assert(self_or_null != NULL);
assert(args != NULL);
if (sym_is_not_null(self_or_null)) {
// Bound method fiddling, same as _INIT_CALL_PY_EXACT_ARGS in VM
args--;
argcount++;
}
@ -1787,16 +1762,13 @@
stack_pointer = new_frame->stack_pointer;
co = get_code(this_instr);
if (co == NULL) {
// should be about to _EXIT_TRACE anyway
ctx->done = true;
break;
}
/* Stack space handling */
int framesize = co->co_framesize;
assert(framesize > 0);
curr_space += framesize;
if (curr_space < 0 || curr_space > INT32_MAX) {
// won't fit in signed 32-bit int
ctx->done = true;
break;
}
@ -1804,11 +1776,8 @@
if (first_valid_check_stack == NULL) {
first_valid_check_stack = corresponding_check_stack;
}
else {
if (corresponding_check_stack) {
// delete all but the first valid _CHECK_STACK_SPACE
corresponding_check_stack->opcode = _NOP;
}
else if (corresponding_check_stack) {
corresponding_check_stack->opcode = _NOP;
}
corresponding_check_stack = NULL;
break;
@ -2049,7 +2018,6 @@
frame_pop(ctx);
stack_pointer = ctx->frame->stack_pointer;
res = sym_new_unknown(ctx);
/* Stack space handling */
assert(corresponding_check_stack == NULL);
assert(co != NULL);
int framesize = co->co_framesize;
@ -2061,7 +2029,6 @@
assert(WITHIN_STACK_BOUNDS());
co = get_code(this_instr);
if (co == NULL) {
// might be impossible, but bailing is still safe
ctx->done = true;
}
stack_pointer[-1] = res;
@ -2123,64 +2090,34 @@
bool lhs_float = sym_matches_type(left, &PyFloat_Type);
bool rhs_float = sym_matches_type(right, &PyFloat_Type);
if (!((lhs_int || lhs_float) && (rhs_int || rhs_float))) {
// There's something other than an int or float involved:
res = sym_new_unknown(ctx);
}
else {
if (oparg == NB_POWER || oparg == NB_INPLACE_POWER) {
// This one's fun... the *type* of the result depends on the
// *values* being exponentiated. However, exponents with one
// constant part are reasonably common, so it's probably worth
// trying to infer some simple cases:
// - A: 1 ** 1 -> 1 (int ** int -> int)
// - B: 1 ** -1 -> 1.0 (int ** int -> float)
// - C: 1.0 ** 1 -> 1.0 (float ** int -> float)
// - D: 1 ** 1.0 -> 1.0 (int ** float -> float)
// - E: -1 ** 0.5 ~> 1j (int ** float -> complex)
// - F: 1.0 ** 1.0 -> 1.0 (float ** float -> float)
// - G: -1.0 ** 0.5 ~> 1j (float ** float -> complex)
if (rhs_float) {
// Case D, E, F, or G... can't know without the sign of the LHS
// or whether the RHS is whole, which isn't worth the effort:
res = sym_new_unknown(ctx);
}
else {
if (lhs_float) {
// Case C:
res = sym_new_type(ctx, &PyFloat_Type);
}
else {
if (!sym_is_const(ctx, right)) {
// Case A or B... can't know without the sign of the RHS:
res = sym_new_unknown(ctx);
}
else {
if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, right))) {
// Case B:
res = sym_new_type(ctx, &PyFloat_Type);
}
else {
// Case A:
res = sym_new_type(ctx, &PyLong_Type);
}
}
}
}
else if (oparg == NB_POWER || oparg == NB_INPLACE_POWER) {
if (rhs_float) {
res = sym_new_unknown(ctx);
}
else if (lhs_float) {
res = sym_new_type(ctx, &PyFloat_Type);
}
else if (!sym_is_const(ctx, right)) {
res = sym_new_unknown(ctx);
}
else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, right))) {
res = sym_new_type(ctx, &PyFloat_Type);
}
else {
if (oparg == NB_TRUE_DIVIDE || oparg == NB_INPLACE_TRUE_DIVIDE) {
res = sym_new_type(ctx, &PyFloat_Type);
}
else {
if (lhs_int && rhs_int) {
res = sym_new_type(ctx, &PyLong_Type);
}
else {
res = sym_new_type(ctx, &PyFloat_Type);
}
}
res = sym_new_type(ctx, &PyLong_Type);
}
}
else if (oparg == NB_TRUE_DIVIDE || oparg == NB_INPLACE_TRUE_DIVIDE) {
res = sym_new_type(ctx, &PyFloat_Type);
}
else if (lhs_int && rhs_int) {
res = sym_new_type(ctx, &PyLong_Type);
}
else {
res = sym_new_type(ctx, &PyFloat_Type);
}
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -2253,11 +2190,9 @@
assert(value != NULL);
eliminate_pop_guard(this_instr, !Py_IsNone(value));
}
else {
if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
eliminate_pop_guard(this_instr, true);
}
else if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
eliminate_pop_guard(this_instr, true);
}
sym_set_const(flag, Py_None);
stack_pointer += -1;
@ -2273,11 +2208,9 @@
assert(value != NULL);
eliminate_pop_guard(this_instr, Py_IsNone(value));
}
else {
if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
eliminate_pop_guard(this_instr, false);
}
else if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
eliminate_pop_guard(this_instr, false);
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -2296,8 +2229,6 @@
case _CHECK_STACK_SPACE_OPERAND: {
uint32_t framesize = (uint32_t)this_instr->operand0;
(void)framesize;
/* We should never see _CHECK_STACK_SPACE_OPERANDs.
* They are only created at the end of this pass. */
Py_UNREACHABLE();
break;
}

View file

@ -3,18 +3,19 @@ import itertools
import lexer
import parser
import re
from typing import Optional
from typing import Optional, Callable
from parser import Stmt, SimpleStmt, BlockStmt, IfStmt, WhileStmt
@dataclass
class EscapingCall:
start: lexer.Token
stmt: SimpleStmt
call: lexer.Token
end: lexer.Token
kills: lexer.Token | None
@dataclass
class Properties:
escaping_calls: dict[lexer.Token, EscapingCall]
escaping_calls: dict[SimpleStmt, EscapingCall]
escapes: bool
error_with_pop: bool
error_without_pop: bool
@ -48,7 +49,7 @@ class Properties:
@staticmethod
def from_list(properties: list["Properties"]) -> "Properties":
escaping_calls: dict[lexer.Token, EscapingCall] = {}
escaping_calls: dict[SimpleStmt, EscapingCall] = {}
for p in properties:
escaping_calls.update(p.escaping_calls)
return Properties(
@ -176,9 +177,8 @@ class Uop:
annotations: list[str]
stack: StackEffect
caches: list[CacheEntry]
deferred_refs: dict[lexer.Token, str | None]
local_stores: list[lexer.Token]
body: list[lexer.Token]
body: BlockStmt
properties: Properties
_size: int = -1
implicitly_created: bool = False
@ -221,7 +221,7 @@ class Uop:
return self.why_not_viable() is None
def is_super(self) -> bool:
for tkn in self.body:
for tkn in self.body.tokens():
if tkn.kind == "IDENTIFIER" and tkn.text == "oparg1":
return True
return False
@ -229,7 +229,7 @@ class Uop:
class Label:
def __init__(self, name: str, spilled: bool, body: list[lexer.Token], properties: Properties):
def __init__(self, name: str, spilled: bool, body: BlockStmt, properties: Properties):
self.name = name
self.spilled = spilled
self.body = body
@ -421,100 +421,102 @@ def analyze_caches(inputs: list[parser.InputEffect]) -> list[CacheEntry]:
return [CacheEntry(i.name, int(i.size)) for i in caches]
def find_assignment_target(node: parser.InstDef, idx: int) -> list[lexer.Token]:
"""Find the tokens that make up the left-hand side of an assignment"""
offset = 0
for tkn in reversed(node.block.tokens[: idx]):
if tkn.kind in {"SEMI", "LBRACE", "RBRACE", "CMACRO"}:
return node.block.tokens[idx - offset : idx]
offset += 1
return []
def find_variable_stores(node: parser.InstDef) -> list[lexer.Token]:
res: list[lexer.Token] = []
outnames = { out.name for out in node.outputs }
innames = { out.name for out in node.inputs }
for idx, tkn in enumerate(node.block.tokens):
if tkn.kind == "AND":
name = node.block.tokens[idx+1]
if name.text in outnames:
res.append(name)
if tkn.kind != "EQUALS":
continue
lhs = find_assignment_target(node, idx)
assert lhs
while lhs and lhs[0].kind == "COMMENT":
lhs = lhs[1:]
if len(lhs) != 1 or lhs[0].kind != "IDENTIFIER":
continue
name = lhs[0]
if name.text in outnames or name.text in innames:
res.append(name)
def find_stores_in_tokens(tokens: list[lexer.Token], callback: Callable[[lexer.Token], None]) -> None:
while tokens and tokens[0].kind == "COMMENT":
tokens = tokens[1:]
if len(tokens) < 4:
return
if tokens[1].kind == "EQUALS":
if tokens[0].kind == "IDENTIFIER":
name = tokens[0].text
if name in outnames or name in innames:
callback(tokens[0])
#Passing the address of a local is also a definition
for idx, tkn in enumerate(tokens):
if tkn.kind == "AND":
name_tkn = tokens[idx+1]
if name_tkn.text in outnames:
callback(name_tkn)
def visit(stmt: Stmt) -> None:
if isinstance(stmt, IfStmt):
def error(tkn: lexer.Token) -> None:
raise analysis_error("Cannot define variable in 'if' condition", tkn)
find_stores_in_tokens(stmt.condition, error)
elif isinstance(stmt, SimpleStmt):
find_stores_in_tokens(stmt.contents, res.append)
node.block.accept(visit)
return res
def analyze_deferred_refs(node: parser.InstDef) -> dict[lexer.Token, str | None]:
"""Look for PyStackRef_FromPyObjectNew() calls"""
def in_frame_push(idx: int) -> bool:
for tkn in reversed(node.block.tokens[: idx - 1]):
if tkn.kind in {"SEMI", "LBRACE", "RBRACE"}:
return False
if tkn.kind == "IDENTIFIER" and tkn.text == "_PyFrame_PushUnchecked":
return True
return False
#def analyze_deferred_refs(node: parser.InstDef) -> dict[lexer.Token, str | None]:
#"""Look for PyStackRef_FromPyObjectNew() calls"""
refs: dict[lexer.Token, str | None] = {}
for idx, tkn in enumerate(node.block.tokens):
if tkn.kind != "IDENTIFIER" or tkn.text != "PyStackRef_FromPyObjectNew":
continue
#def in_frame_push(idx: int) -> bool:
#for tkn in reversed(node.block.tokens[: idx - 1]):
#if tkn.kind in {"SEMI", "LBRACE", "RBRACE"}:
#return False
#if tkn.kind == "IDENTIFIER" and tkn.text == "_PyFrame_PushUnchecked":
#return True
#return False
if idx == 0 or node.block.tokens[idx - 1].kind != "EQUALS":
if in_frame_push(idx):
# PyStackRef_FromPyObjectNew() is called in _PyFrame_PushUnchecked()
refs[tkn] = None
continue
raise analysis_error("Expected '=' before PyStackRef_FromPyObjectNew", tkn)
#refs: dict[lexer.Token, str | None] = {}
#for idx, tkn in enumerate(node.block.tokens):
#if tkn.kind != "IDENTIFIER" or tkn.text != "PyStackRef_FromPyObjectNew":
#continue
lhs = find_assignment_target(node, idx - 1)
if len(lhs) == 0:
raise analysis_error(
"PyStackRef_FromPyObjectNew() must be assigned to an output", tkn
)
#if idx == 0 or node.block.tokens[idx - 1].kind != "EQUALS":
#if in_frame_push(idx):
## PyStackRef_FromPyObjectNew() is called in _PyFrame_PushUnchecked()
#refs[tkn] = None
#continue
#raise analysis_error("Expected '=' before PyStackRef_FromPyObjectNew", tkn)
if lhs[0].kind == "TIMES" or any(
t.kind == "ARROW" or t.kind == "LBRACKET" for t in lhs[1:]
):
# Don't handle: *ptr = ..., ptr->field = ..., or ptr[field] = ...
# Assume that they are visible to the GC.
refs[tkn] = None
continue
#lhs = find_assignment_target(node, idx - 1)
#if len(lhs) == 0:
#raise analysis_error(
#"PyStackRef_FromPyObjectNew() must be assigned to an output", tkn
#)
if len(lhs) != 1 or lhs[0].kind != "IDENTIFIER":
raise analysis_error(
"PyStackRef_FromPyObjectNew() must be assigned to an output", tkn
)
#if lhs[0].kind == "TIMES" or any(
#t.kind == "ARROW" or t.kind == "LBRACKET" for t in lhs[1:]
#):
## Don't handle: *ptr = ..., ptr->field = ..., or ptr[field] = ...
## Assume that they are visible to the GC.
#refs[tkn] = None
#continue
name = lhs[0].text
match = (
any(var.name == name for var in node.inputs)
or any(var.name == name for var in node.outputs)
)
if not match:
raise analysis_error(
f"PyStackRef_FromPyObjectNew() must be assigned to an input or output, not '{name}'",
tkn,
)
#if len(lhs) != 1 or lhs[0].kind != "IDENTIFIER":
#raise analysis_error(
#"PyStackRef_FromPyObjectNew() must be assigned to an output", tkn
#)
refs[tkn] = name
#name = lhs[0].text
#match = (
#any(var.name == name for var in node.inputs)
#or any(var.name == name for var in node.outputs)
#)
#if not match:
#raise analysis_error(
#f"PyStackRef_FromPyObjectNew() must be assigned to an input or output, not '{name}'",
#tkn,
#)
return refs
#refs[tkn] = name
#return refs
def variable_used(node: parser.CodeDef, name: str) -> bool:
"""Determine whether a variable with a given name is used in a node."""
return any(
token.kind == "IDENTIFIER" and token.text == name for token in node.block.tokens
token.kind == "IDENTIFIER" and token.text == name for token in node.block.tokens()
)
@ -678,93 +680,86 @@ NON_ESCAPING_FUNCTIONS = (
"_Py_ReachedRecursionLimit",
)
def find_stmt_start(node: parser.CodeDef, idx: int) -> lexer.Token:
assert idx < len(node.block.tokens)
while True:
tkn = node.block.tokens[idx-1]
if tkn.kind in {"SEMI", "LBRACE", "RBRACE", "CMACRO"}:
break
idx -= 1
assert idx > 0
while node.block.tokens[idx].kind == "COMMENT":
idx += 1
return node.block.tokens[idx]
def find_stmt_end(node: parser.CodeDef, idx: int) -> lexer.Token:
assert idx < len(node.block.tokens)
while True:
idx += 1
tkn = node.block.tokens[idx]
if tkn.kind == "SEMI":
return node.block.tokens[idx+1]
def check_escaping_calls(instr: parser.CodeDef, escapes: dict[lexer.Token, EscapingCall]) -> None:
def check_escaping_calls(instr: parser.CodeDef, escapes: dict[SimpleStmt, EscapingCall]) -> None:
error: lexer.Token | None = None
calls = {e.call for e in escapes.values()}
in_if = 0
tkn_iter = iter(instr.block.tokens)
for tkn in tkn_iter:
if tkn.kind == "IF":
next(tkn_iter)
in_if = 1
if tkn.kind == "IDENTIFIER" and tkn.text in ("DEOPT_IF", "ERROR_IF", "EXIT_IF"):
next(tkn_iter)
in_if = 1
elif tkn.kind == "LPAREN" and in_if:
in_if += 1
elif tkn.kind == "RPAREN":
if in_if:
in_if -= 1
elif tkn in calls and in_if:
raise analysis_error(f"Escaping call '{tkn.text} in condition", tkn)
def find_escaping_api_calls(instr: parser.CodeDef) -> dict[lexer.Token, EscapingCall]:
result: dict[lexer.Token, EscapingCall] = {}
tokens = instr.block.tokens
for idx, tkn in enumerate(tokens):
try:
next_tkn = tokens[idx+1]
except IndexError:
break
if tkn.kind == "SWITCH":
raise analysis_error(f"switch statements are not supported due to their complex flow control. Sorry.", tkn)
if next_tkn.kind != lexer.LPAREN:
continue
if tkn.kind == lexer.IDENTIFIER:
if tkn.text.upper() == tkn.text:
# simple macro
def visit(stmt: Stmt) -> None:
nonlocal error
if isinstance(stmt, IfStmt) or isinstance(stmt, WhileStmt):
for tkn in stmt.condition:
if tkn in calls:
error = tkn
elif isinstance(stmt, SimpleStmt):
in_if = 0
tkn_iter = iter(stmt.contents)
for tkn in tkn_iter:
if tkn.kind == "IDENTIFIER" and tkn.text in ("DEOPT_IF", "ERROR_IF", "EXIT_IF"):
in_if = 1
next(tkn_iter)
elif tkn.kind == "LPAREN":
if in_if:
in_if += 1
elif tkn.kind == "RPAREN":
if in_if:
in_if -= 1
elif tkn in calls and in_if:
error = tkn
instr.block.accept(visit)
if error is not None:
raise analysis_error(f"Escaping call '{error.text} in condition", error)
def find_escaping_api_calls(instr: parser.CodeDef) -> dict[SimpleStmt, EscapingCall]:
result: dict[SimpleStmt, EscapingCall] = {}
def visit(stmt: Stmt) -> None:
if not isinstance(stmt, SimpleStmt):
return
tokens = stmt.contents
for idx, tkn in enumerate(tokens):
try:
next_tkn = tokens[idx+1]
except IndexError:
break
if next_tkn.kind != lexer.LPAREN:
continue
#if not tkn.text.startswith(("Py", "_Py", "monitor")):
# continue
if tkn.text.startswith(("sym_", "optimize_")):
# Optimize functions
if tkn.kind == lexer.IDENTIFIER:
if tkn.text.upper() == tkn.text:
# simple macro
continue
#if not tkn.text.startswith(("Py", "_Py", "monitor")):
# continue
if tkn.text.startswith(("sym_", "optimize_")):
# Optimize functions
continue
if tkn.text.endswith("Check"):
continue
if tkn.text.startswith("Py_Is"):
continue
if tkn.text.endswith("CheckExact"):
continue
if tkn.text in NON_ESCAPING_FUNCTIONS:
continue
elif tkn.kind == "RPAREN":
prev = tokens[idx-1]
if prev.text.endswith("_t") or prev.text == "*" or prev.text == "int":
#cast
continue
elif tkn.kind != "RBRACKET":
continue
if tkn.text.endswith("Check"):
continue
if tkn.text.startswith("Py_Is"):
continue
if tkn.text.endswith("CheckExact"):
continue
if tkn.text in NON_ESCAPING_FUNCTIONS:
continue
elif tkn.kind == "RPAREN":
prev = tokens[idx-1]
if prev.text.endswith("_t") or prev.text == "*" or prev.text == "int":
#cast
continue
elif tkn.kind != "RBRACKET":
continue
if tkn.text in ("PyStackRef_CLOSE", "PyStackRef_XCLOSE"):
if len(tokens) <= idx+2:
raise analysis_error("Unexpected end of file", next_tkn)
kills = tokens[idx+2]
if kills.kind != "IDENTIFIER":
raise analysis_error(f"Expected identifier, got '{kills.text}'", kills)
else:
kills = None
start = find_stmt_start(instr, idx)
end = find_stmt_end(instr, idx)
result[start] = EscapingCall(start, tkn, end, kills)
if tkn.text in ("PyStackRef_CLOSE", "PyStackRef_XCLOSE"):
if len(tokens) <= idx+2:
raise analysis_error("Unexpected end of file", next_tkn)
kills = tokens[idx+2]
if kills.kind != "IDENTIFIER":
raise analysis_error(f"Expected identifier, got '{kills.text}'", kills)
else:
kills = None
result[stmt] = EscapingCall(stmt, tkn, kills)
instr.block.accept(visit)
check_escaping_calls(instr, result)
return result
@ -876,9 +871,8 @@ def make_uop(
annotations=op.annotations,
stack=analyze_stack(op),
caches=analyze_caches(inputs),
deferred_refs=analyze_deferred_refs(op),
local_stores=find_variable_stores(op),
body=op.block.tokens,
body=op.block,
properties=compute_properties(op),
)
for anno in op.annotations:
@ -898,9 +892,8 @@ def make_uop(
annotations=op.annotations,
stack=analyze_stack(op),
caches=analyze_caches(inputs),
deferred_refs=analyze_deferred_refs(op),
local_stores=find_variable_stores(op),
body=op.block.tokens,
body=op.block,
properties=properties,
)
rep.replicates = result
@ -1015,7 +1008,7 @@ def add_label(
labels: dict[str, Label],
) -> None:
properties = compute_properties(label)
labels[label.name] = Label(label.name, label.spilled, label.block.tokens, properties)
labels[label.name] = Label(label.name, label.spilled, label.block, properties)
def assign_opcodes(
@ -1109,9 +1102,9 @@ def get_instruction_size_for_uop(instructions: dict[str, Instruction], uop: Uop)
If there is more than one instruction that contains the uop,
ensure that they all have the same size.
"""
for tkn in uop.body:
if tkn.text == "INSTRUCTION_SIZE":
break
for tkn in uop.body.tokens():
if tkn.text == "INSTRUCTION_SIZE":
break
else:
return None

View file

@ -99,7 +99,7 @@ class CWriter:
self.maybe_dedent(tkn.text)
self.set_position(tkn)
self.emit_text(tkn.text)
if tkn.kind == "CMACRO":
if tkn.kind.startswith("CMACRO"):
self.newline = True
self.maybe_indent(tkn.text)

View file

@ -12,6 +12,7 @@ from cwriter import CWriter
from typing import Callable, TextIO, Iterator, Iterable
from lexer import Token
from stack import Storage, StackError
from parser import Stmt, SimpleStmt, BlockStmt, IfStmt, ForStmt, WhileStmt, MacroIfStmt
# Set this to true for voluminous output showing state of stack and locals
PRINT_STACKS = False
@ -160,7 +161,7 @@ class Emitter:
self.emit(") {\n")
next(tkn_iter) # Semi colon
assert inst is not None
assert inst.family is not None
assert inst.family is not None, inst
family_name = inst.family.name
self.emit(f"UPDATE_MISS_STATS({family_name});\n")
self.emit(f"assert(_PyOpcode_Deopt[opcode] == ({family_name}));\n")
@ -458,119 +459,38 @@ class Emitter:
self.emit(storage.as_comment())
self.out.start_line()
def _emit_if(
def _emit_stmt(
self,
tkn_iter: TokenIterator,
stmt: Stmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
) -> tuple[bool, Token, Storage]:
"""Returns (reachable?, closing '}', stack)."""
tkn = next(tkn_iter)
assert tkn.kind == "LPAREN"
self.out.emit(tkn)
rparen = emit_to(self.out, tkn_iter, "RPAREN")
self.emit(rparen)
if_storage = storage.copy()
reachable, rbrace, if_storage = self._emit_block(tkn_iter, uop, if_storage, inst, True)
try:
maybe_else = tkn_iter.peek()
if maybe_else and maybe_else.kind == "ELSE":
self._print_storage(storage)
self.emit(rbrace)
self.emit(next(tkn_iter))
maybe_if = tkn_iter.peek()
if maybe_if and maybe_if.kind == "IF":
# Emit extra braces around the if to get scoping right
self.emit(" {\n")
self.emit(next(tkn_iter))
else_reachable, rbrace, else_storage = self._emit_if(tkn_iter, uop, storage, inst)
self.out.start_line()
self.emit("}\n")
else:
else_reachable, rbrace, else_storage = self._emit_block(tkn_iter, uop, storage, inst, True)
if not reachable:
# Discard the if storage
reachable = else_reachable
storage = else_storage
elif not else_reachable:
# Discard the else storage
storage = if_storage
reachable = True
else:
if PRINT_STACKS:
self.emit("/* Merge */\n")
self.out.emit(if_storage.as_comment())
self.out.emit("\n")
self.out.emit(else_storage.as_comment())
else_storage.merge(if_storage, self.out)
storage = else_storage
self._print_storage(storage)
else:
if reachable:
if PRINT_STACKS:
self.emit("/* Merge */\n")
if_storage.merge(storage, self.out)
storage = if_storage
self._print_storage(storage)
else:
# Discard the if storage
reachable = True
except StackError as ex:
self._print_storage(if_storage)
raise analysis_error(ex.args[0], rbrace) from None
return reachable, rbrace, storage
) -> tuple[bool, Token | None, Storage]:
method_name = "emit_" + stmt.__class__.__name__
method = getattr(self, method_name, None)
if method is None:
raise NotImplementedError
return method(stmt, uop, storage, inst) # type: ignore[no-any-return]
def _emit_block(
def emit_SimpleStmt(
self,
tkn_iter: TokenIterator,
stmt: SimpleStmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
emit_first_brace: bool
) -> tuple[bool, Token, Storage]:
""" Returns (reachable?, closing '}', stack)."""
braces = 1
) -> tuple[bool, Token | None, Storage]:
local_stores = set(uop.local_stores)
tkn = next(tkn_iter)
reload: Token | None = None
reachable = True
tkn = stmt.contents[-1]
try:
reachable = True
line : int = -1
if tkn.kind != "LBRACE":
raise analysis_error(f"PEP 7: expected '{{', found: {tkn.text}", tkn)
escaping_calls = uop.properties.escaping_calls
if emit_first_brace:
self.emit(tkn)
self._print_storage(storage)
if stmt in uop.properties.escaping_calls:
escape = uop.properties.escaping_calls[stmt]
if escape.kills is not None:
self.stackref_kill(escape.kills, storage, True)
self.emit_save(storage)
tkn_iter = TokenIterator(stmt.contents)
for tkn in tkn_iter:
if PRINT_STACKS and tkn.line != line:
self.out.start_line()
self.emit(storage.as_comment())
self.out.start_line()
line = tkn.line
if tkn in escaping_calls:
escape = escaping_calls[tkn]
if escape.kills is not None:
if tkn == reload:
self.emit_reload(storage)
self.stackref_kill(escape.kills, storage, True)
self.emit_save(storage)
elif tkn != reload:
self.emit_save(storage)
reload = escape.end
elif tkn == reload:
self.emit_reload(storage)
if tkn.kind == "LBRACE":
self.out.emit(tkn)
braces += 1
elif tkn.kind == "RBRACE":
self._print_storage(storage)
braces -= 1
if braces == 0:
return reachable, tkn, storage
self.out.emit(tkn)
elif tkn.kind == "GOTO":
if tkn.kind == "GOTO":
label_tkn = next(tkn_iter)
self.goto_label(tkn, label_tkn, storage)
reachable = False
@ -597,34 +517,161 @@ class Emitter:
self._print_storage(storage)
reachable = False
self.out.emit(tkn)
elif tkn.kind == "IF":
self.out.emit(tkn)
if_reachable, rbrace, storage = self._emit_if(tkn_iter, uop, storage, inst)
if reachable:
reachable = if_reachable
self.out.emit(rbrace)
else:
self.out.emit(tkn)
if stmt in uop.properties.escaping_calls:
self.emit_reload(storage)
return reachable, None, storage
except StackError as ex:
raise analysis_error(ex.args[0], tkn) #from None
def emit_MacroIfStmt(
self,
stmt: MacroIfStmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
) -> tuple[bool, Token | None, Storage]:
self.out.emit(stmt.condition)
branch = stmt.else_ is not None
reachable = True
for s in stmt.body:
r, tkn, storage = self._emit_stmt(s, uop, storage, inst)
if tkn is not None:
self.out.emit(tkn)
if not r:
reachable = False
if branch:
else_storage = storage.copy()
assert stmt.else_ is not None
self.out.emit(stmt.else_)
assert stmt.else_body is not None
for s in stmt.else_body:
r, tkn, else_storage = self._emit_stmt(s, uop, else_storage, inst)
if tkn is not None:
self.out.emit(tkn)
if not r:
reachable = False
storage.merge(else_storage, self.out)
self.out.emit(stmt.endif)
return reachable, None, storage
def emit_IfStmt(
self,
stmt: IfStmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
) -> tuple[bool, Token | None, Storage]:
self.out.emit(stmt.if_)
for tkn in stmt.condition:
self.out.emit(tkn)
if_storage = storage.copy()
rbrace: Token | None = stmt.if_
try:
reachable, rbrace, if_storage = self._emit_stmt(stmt.body, uop, if_storage, inst)
if stmt.else_ is not None:
assert rbrace is not None
self.out.emit(rbrace)
self.out.emit(stmt.else_)
if stmt.else_body is not None:
else_reachable, rbrace, else_storage = self._emit_stmt(stmt.else_body, uop, storage, inst)
if not reachable:
reachable, storage = else_reachable, else_storage
elif not else_reachable:
# Discard the else storage
storage = if_storage
else:
#Both reachable
else_storage.merge(if_storage, self.out)
storage = else_storage
else:
if reachable:
if_storage.merge(storage, self.out)
storage = if_storage
else:
# Discard the if storage
reachable = True
return reachable, rbrace, storage
except StackError as ex:
self._print_storage(if_storage)
assert rbrace is not None
raise analysis_error(ex.args[0], rbrace) from None
def emit_BlockStmt(
self,
stmt: BlockStmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
emit_braces: bool = True,
) -> tuple[bool, Token | None, Storage]:
""" Returns (reachable?, closing '}', stack)."""
tkn: Token | None = None
try:
if emit_braces:
self.out.emit(stmt.open)
reachable = True
for s in stmt.body:
reachable, tkn, storage = self._emit_stmt(s, uop, storage, inst)
if tkn is not None:
self.out.emit(tkn)
if not reachable:
break
return reachable, stmt.close, storage
except StackError as ex:
if tkn is None:
tkn = stmt.close
raise analysis_error(ex.args[0], tkn) from None
raise analysis_error("Expecting closing brace. Reached end of file", tkn)
def emit_ForStmt(
self,
stmt: ForStmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
) -> tuple[bool, Token | None, Storage]:
""" Returns (reachable?, closing '}', stack)."""
self.out.emit(stmt.for_)
for tkn in stmt.header:
self.out.emit(tkn)
return self._emit_stmt(stmt.body, uop, storage, inst)
def emit_WhileStmt(
self,
stmt: WhileStmt,
uop: CodeSection,
storage: Storage,
inst: Instruction | None,
) -> tuple[bool, Token | None, Storage]:
""" Returns (reachable?, closing '}', stack)."""
self.out.emit(stmt.while_)
for tkn in stmt.condition:
self.out.emit(tkn)
return self._emit_stmt(stmt.body, uop, storage, inst)
def emit_tokens(
self,
code: CodeSection,
storage: Storage,
inst: Instruction | None,
emit_braces: bool = True
) -> Storage:
tkn_iter = TokenIterator(code.body)
self.out.start_line()
reachable, rbrace, storage = self._emit_block(tkn_iter, code, storage, inst, False)
reachable, tkn, storage = self.emit_BlockStmt(code.body, code, storage, inst, emit_braces)
assert tkn is not None
try:
if reachable:
self._print_storage(storage)
storage.push_outputs()
self._print_storage(storage)
if emit_braces:
self.out.emit(tkn)
except StackError as ex:
raise analysis_error(ex.args[0], rbrace) from None
raise analysis_error(ex.args[0], tkn) from None
return storage
def emit(self, txt: str | Token) -> None:

View file

@ -80,7 +80,10 @@ opmap = {pattern.replace("\\", "") or "\\": op for op, pattern in operators.item
# Macros
macro = r"#.*\n"
CMACRO = "CMACRO"
CMACRO_IF = "CMACRO_IF"
CMACRO_ELSE = "CMACRO_ELSE"
CMACRO_ENDIF = "CMACRO_ENDIF"
CMACRO_OTHER = "CMACRO_OTHER"
id_re = r"[a-zA-Z_][0-9a-zA-Z_]*"
IDENTIFIER = "IDENTIFIER"
@ -292,6 +295,7 @@ def tokenize(src: str, line: int = 1, filename: str = "") -> Iterator[Token]:
linestart = -1
for m in matcher.finditer(src):
start, end = m.span()
macro_body = ""
text = m.group(0)
if text in keywords:
kind = keywords[text]
@ -316,7 +320,15 @@ def tokenize(src: str, line: int = 1, filename: str = "") -> Iterator[Token]:
elif text[0] == "'":
kind = CHARACTER
elif text[0] == "#":
kind = CMACRO
macro_body = text[1:].strip()
if macro_body.startswith("if"):
kind = CMACRO_IF
elif macro_body.startswith("else"):
kind = CMACRO_ELSE
elif macro_body.startswith("endif"):
kind = CMACRO_ENDIF
else:
kind = CMACRO_OTHER
elif text[0] == "/" and text[1] in "/*":
kind = COMMENT
else:
@ -338,7 +350,7 @@ def tokenize(src: str, line: int = 1, filename: str = "") -> Iterator[Token]:
line += newlines
else:
begin = line, start - linestart
if kind == CMACRO:
if macro_body:
linestart = end
line += 1
if kind != "\n":

View file

@ -145,7 +145,7 @@ def write_uop(
# No reference management of inputs needed.
for var in storage.inputs: # type: ignore[possibly-undefined]
var.in_local = False
storage = emitter.emit_tokens(override, storage, None)
storage = emitter.emit_tokens(override, storage, None, False)
out.start_line()
storage.flush(out)
else:
@ -153,7 +153,7 @@ def write_uop(
out.start_line()
stack.flush(out)
except StackError as ex:
raise analysis_error(ex.args[0], prototype.body[0]) # from None
raise analysis_error(ex.args[0], prototype.body.open) # from None
SKIPS = ("_EXTENDED_ARG",)

View file

@ -11,8 +11,17 @@ from parsing import ( # noqa: F401
InputEffect,
OpName,
AstNode,
Stmt,
SimpleStmt,
IfStmt,
ForStmt,
WhileStmt,
BlockStmt,
MacroIfStmt,
)
import pprint
CodeDef = InstDef | LabelDef
def prettify_filename(filename: str) -> str:
@ -61,6 +70,7 @@ def parse_files(filenames: list[str]) -> list[AstNode]:
assert node is not None
result.append(node) # type: ignore[arg-type]
if not psr.eof():
pprint.pprint(result)
psr.backup()
raise psr.make_syntax_error(
f"Extra stuff at the end of {filename}", psr.next(True)

View file

@ -1,10 +1,12 @@
"""Parser for bytecodes.inst."""
from dataclasses import dataclass, field
from typing import NamedTuple, Callable, TypeVar, Literal, cast
from typing import NamedTuple, Callable, TypeVar, Literal, cast, Iterator
from io import StringIO
import lexer as lx
from plexer import PLexer
from cwriter import CWriter
P = TypeVar("P", bound="Parser")
@ -66,12 +68,181 @@ class Node:
assert context is not None
return context.owner.tokens[context.begin]
# Statements
Visitor = Callable[["Stmt"], None]
class Stmt:
def __repr__(self) -> str:
io = StringIO()
out = CWriter(io, 0, False)
self.print(out)
return io.getvalue()
def print(self, out:CWriter) -> None:
raise NotImplementedError
def accept(self, visitor: Visitor) -> None:
raise NotImplementedError
def tokens(self) -> Iterator[lx.Token]:
raise NotImplementedError
@dataclass
class Block(Node):
# This just holds a context which has the list of tokens.
pass
class IfStmt(Stmt):
if_: lx.Token
condition: list[lx.Token]
body: Stmt
else_: lx.Token | None
else_body: Stmt | None
def print(self, out:CWriter) -> None:
out.emit(self.if_)
for tkn in self.condition:
out.emit(tkn)
self.body.print(out)
if self.else_ is not None:
out.emit(self.else_)
self.body.print(out)
if self.else_body is not None:
self.else_body.print(out)
def accept(self, visitor: Visitor) -> None:
visitor(self)
self.body.accept(visitor)
if self.else_body is not None:
self.else_body.accept(visitor)
def tokens(self) -> Iterator[lx.Token]:
yield self.if_
yield from self.condition
yield from self.body.tokens()
if self.else_ is not None:
yield self.else_
if self.else_body is not None:
yield from self.else_body.tokens()
@dataclass
class ForStmt(Stmt):
for_: lx.Token
header: list[lx.Token]
body: Stmt
def print(self, out:CWriter) -> None:
out.emit(self.for_)
for tkn in self.header:
out.emit(tkn)
self.body.print(out)
def accept(self, visitor: Visitor) -> None:
visitor(self)
self.body.accept(visitor)
def tokens(self) -> Iterator[lx.Token]:
yield self.for_
yield from self.header
yield from self.body.tokens()
@dataclass
class WhileStmt(Stmt):
while_: lx.Token
condition: list[lx.Token]
body: Stmt
def print(self, out:CWriter) -> None:
out.emit(self.while_)
for tkn in self.condition:
out.emit(tkn)
self.body.print(out)
def accept(self, visitor: Visitor) -> None:
visitor(self)
self.body.accept(visitor)
def tokens(self) -> Iterator[lx.Token]:
yield self.while_
yield from self.condition
yield from self.body.tokens()
@dataclass
class MacroIfStmt(Stmt):
condition: lx.Token
body: list[Stmt]
else_: lx.Token | None
else_body: list[Stmt] | None
endif: lx.Token
def print(self, out:CWriter) -> None:
out.emit(self.condition)
for stmt in self.body:
stmt.print(out)
if self.else_body is not None:
out.emit("#else\n")
for stmt in self.else_body:
stmt.print(out)
def accept(self, visitor: Visitor) -> None:
visitor(self)
for stmt in self.body:
stmt.accept(visitor)
if self.else_body is not None:
for stmt in self.else_body:
stmt.accept(visitor)
def tokens(self) -> Iterator[lx.Token]:
yield self.condition
for stmt in self.body:
yield from stmt.tokens()
if self.else_body is not None:
for stmt in self.else_body:
yield from stmt.tokens()
@dataclass
class BlockStmt(Stmt):
open: lx.Token
body: list[Stmt]
close: lx.Token
def print(self, out:CWriter) -> None:
out.emit(self.open)
for stmt in self.body:
stmt.print(out)
out.start_line()
out.emit(self.close)
def accept(self, visitor: Visitor) -> None:
visitor(self)
for stmt in self.body:
stmt.accept(visitor)
def tokens(self) -> Iterator[lx.Token]:
yield self.open
for stmt in self.body:
yield from stmt.tokens()
yield self.close
@dataclass
class SimpleStmt(Stmt):
contents: list[lx.Token]
def print(self, out:CWriter) -> None:
for tkn in self.contents:
out.emit(tkn)
def tokens(self) -> Iterator[lx.Token]:
yield from self.contents
def accept(self, visitor: Visitor) -> None:
visitor(self)
__hash__ = object.__hash__
@dataclass
class StackEffect(Node):
@ -124,7 +295,7 @@ class InstDef(Node):
name: str
inputs: list[InputEffect]
outputs: list[OutputEffect]
block: Block
block: BlockStmt
@dataclass
@ -153,7 +324,7 @@ class Pseudo(Node):
class LabelDef(Node):
name: str
spilled: bool
block: Block
block: BlockStmt
AstNode = InstDef | Macro | Pseudo | Family | LabelDef
@ -183,23 +354,22 @@ class Parser(PLexer):
if self.expect(lx.LPAREN):
if tkn := self.expect(lx.IDENTIFIER):
if self.expect(lx.RPAREN):
if block := self.block():
return LabelDef(tkn.text, spilled, block)
block = self.block()
return LabelDef(tkn.text, spilled, block)
return None
@contextual
def inst_def(self) -> InstDef | None:
if hdr := self.inst_header():
if block := self.block():
return InstDef(
hdr.annotations,
hdr.kind,
hdr.name,
hdr.inputs,
hdr.outputs,
block,
)
raise self.make_syntax_error("Expected block")
block = self.block()
return InstDef(
hdr.annotations,
hdr.kind,
hdr.name,
hdr.inputs,
hdr.outputs,
block,
)
return None
@contextual
@ -473,28 +643,85 @@ class Parser(PLexer):
self.setpos(here)
return None
@contextual
def block(self) -> Block | None:
if self.c_blob():
return Block()
return None
def block(self) -> BlockStmt:
open = self.require(lx.LBRACE)
stmts: list[Stmt] = []
while not (close := self.expect(lx.RBRACE)):
stmts.append(self.stmt())
return BlockStmt(open, stmts, close)
def c_blob(self) -> list[lx.Token]:
tokens: list[lx.Token] = []
level = 0
while tkn := self.next(raw=True):
tokens.append(tkn)
if tkn.kind in (lx.LBRACE, lx.LPAREN, lx.LBRACKET):
level += 1
elif tkn.kind in (lx.RBRACE, lx.RPAREN, lx.RBRACKET):
level -= 1
if level <= 0:
break
return tokens
def stmt(self) -> Stmt:
if tkn := self.expect(lx.IF):
return self.if_stmt(tkn)
elif self.expect(lx.LBRACE):
self.backup()
return self.block()
elif tkn := self.expect(lx.FOR):
return self.for_stmt(tkn)
elif tkn := self.expect(lx.WHILE):
return self.while_stmt(tkn)
elif tkn := self.expect(lx.CMACRO_IF):
return self.macro_if(tkn)
elif tkn := self.expect(lx.CMACRO_ELSE):
msg = "Unexpected #else"
raise self.make_syntax_error(msg)
elif tkn := self.expect(lx.CMACRO_ENDIF):
msg = "Unexpected #endif"
raise self.make_syntax_error(msg)
elif tkn := self.expect(lx.CMACRO_OTHER):
return SimpleStmt([tkn])
elif tkn := self.expect(lx.SWITCH):
msg = "switch statements are not supported due to their complex flow control. Sorry."
raise self.make_syntax_error(msg)
tokens = self.consume_to(lx.SEMI)
return SimpleStmt(tokens)
def if_stmt(self, if_: lx.Token) -> IfStmt:
lparen = self.require(lx.LPAREN)
condition = [lparen] + self.consume_to(lx.RPAREN)
body = self.block()
else_body: Stmt | None = None
else_: lx.Token | None = None
if else_ := self.expect(lx.ELSE):
if inner := self.expect(lx.IF):
else_body = self.if_stmt(inner)
else:
else_body = self.block()
return IfStmt(if_, condition, body, else_, else_body)
def macro_if(self, cond: lx.Token) -> MacroIfStmt:
else_ = None
body: list[Stmt] = []
else_body: list[Stmt] | None = None
part = body
while True:
if tkn := self.expect(lx.CMACRO_ENDIF):
return MacroIfStmt(cond, body, else_, else_body, tkn)
elif tkn := self.expect(lx.CMACRO_ELSE):
if part is else_body:
raise self.make_syntax_error("Multiple #else")
else_ = tkn
else_body = []
part = else_body
else:
part.append(self.stmt())
def for_stmt(self, for_: lx.Token) -> ForStmt:
lparen = self.require(lx.LPAREN)
header = [lparen] + self.consume_to(lx.RPAREN)
body = self.block()
return ForStmt(for_, header, body)
def while_stmt(self, while_: lx.Token) -> WhileStmt:
lparen = self.require(lx.LPAREN)
cond = [lparen] + self.consume_to(lx.RPAREN)
body = self.block()
return WhileStmt(while_, cond, body)
if __name__ == "__main__":
import sys
import pprint
if sys.argv[1:]:
filename = sys.argv[1]
@ -512,5 +739,5 @@ if __name__ == "__main__":
filename = "<default>"
src = "if (x) { x.foo; // comment\n}"
parser = Parser(src, filename)
x = parser.definition()
print(x)
while node := parser.definition():
pprint.pprint(node)

View file

@ -69,6 +69,20 @@ class PLexer:
f"Expected {kind!r} but got {tkn and tkn.text!r}", tkn
)
def consume_to(self, end: str) -> list[Token]:
res: list[Token] = []
parens = 0
while tkn := self.next(raw=True):
res.append(tkn)
if tkn.kind == end and parens == 0:
return res
if tkn.kind == "LPAREN":
parens += 1
if tkn.kind == "RPAREN":
parens -= 1
raise self.make_syntax_error(
f"Expected {end!r} but reached EOF", tkn)
def extract_line(self, lineno: int) -> str:
# Return source line `lineno` (1-based)
lines = self.src.splitlines()

View file

@ -79,38 +79,35 @@ def write_uop(
emitter.emit(f"// flush\n")
stack.flush(emitter.out)
return offset, stack
try:
locals: dict[str, Local] = {}
locals: dict[str, Local] = {}
emitter.out.start_line()
if braces:
emitter.out.emit(f"// {uop.name}\n")
emitter.emit("{\n")
storage = Storage.for_uop(stack, uop, emitter.out)
emitter._print_storage(storage)
for cache in uop.caches:
if cache.name != "unused":
if cache.size == 4:
type = "PyObject *"
reader = "read_obj"
else:
type = f"uint{cache.size*16}_t "
reader = f"read_u{cache.size*16}"
emitter.emit(
f"{type}{cache.name} = {reader}(&this_instr[{offset}].cache);\n"
)
if inst.family is None:
emitter.emit(f"(void){cache.name};\n")
offset += cache.size
storage = emitter.emit_tokens(uop, storage, inst, False)
if braces:
emitter.out.start_line()
if braces:
emitter.out.emit(f"// {uop.name}\n")
emitter.emit("{\n")
storage = Storage.for_uop(stack, uop, emitter.out)
emitter._print_storage(storage)
for cache in uop.caches:
if cache.name != "unused":
if cache.size == 4:
type = "PyObject *"
reader = "read_obj"
else:
type = f"uint{cache.size*16}_t "
reader = f"read_u{cache.size*16}"
emitter.emit(
f"{type}{cache.name} = {reader}(&this_instr[{offset}].cache);\n"
)
if inst.family is None:
emitter.emit(f"(void){cache.name};\n")
offset += cache.size
storage = emitter.emit_tokens(uop, storage, inst)
if braces:
emitter.out.start_line()
emitter.emit("}\n")
# emitter.emit(stack.as_comment() + "\n")
return offset, storage.stack
except StackError as ex:
raise analysis_error(ex.args[0], uop.body[0])
emitter.emit("}\n")
# emitter.emit(stack.as_comment() + "\n")
return offset, storage.stack
def uses_this(inst: Instruction) -> bool:
@ -127,7 +124,7 @@ def uses_this(inst: Instruction) -> bool:
for uop in inst.parts:
if not isinstance(uop, Uop):
continue
for tkn in uop.body:
for tkn in uop.body.tokens():
if (tkn.kind == "IDENTIFIER"
and (tkn.text in {"DEOPT_IF", "EXIT_IF"})):
return True
@ -201,15 +198,11 @@ def generate_tier1_labels(
# Emit tail-callable labels as function defintions
for name, label in analysis.labels.items():
emitter.emit(f"LABEL({name})\n")
emitter.emit("{\n")
storage = Storage(Stack(), [], [], False)
if label.spilled:
storage.spilled = 1
emitter.emit("/* STACK SPILLED */\n")
emitter.emit_tokens(label, storage, None)
emitter.emit("\n")
emitter.emit("}\n")
emitter.emit("\n")
emitter.emit("\n\n")
def generate_tier1_cases(

View file

@ -154,10 +154,10 @@ def write_uop(uop: Uop, emitter: Emitter, stack: Stack) -> Stack:
cast = f"uint{cache.size*16}_t"
emitter.emit(f"{type}{cache.name} = ({cast})CURRENT_OPERAND{idx}();\n")
idx += 1
storage = emitter.emit_tokens(uop, storage, None)
storage = emitter.emit_tokens(uop, storage, None, False)
storage.flush(emitter.out)
except StackError as ex:
raise analysis_error(ex.args[0], uop.body[0]) from None
raise analysis_error(ex.args[0], uop.body.open) from None
return storage.stack
SKIPS = ("_EXTENDED_ARG",)