Skip to content

Commit

Permalink
gh-116088: Insert bottom checks after all sym_set_...() calls (#116089)
Browse files Browse the repository at this point in the history
This changes the `sym_set_...()` functions to return a `bool` which is `false`
when the symbol is `bottom` after the operation.

All calls to such functions now check this result and go to `hit_bottom`,
a special error label that prints a different message and then reports
that it wasn't able to optimize the trace. No executor will be produced
in this case.
  • Loading branch information
gvanrossum authored Feb 29, 2024
1 parent 3b6f4ca commit 0656509
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 47 deletions.
8 changes: 4 additions & 4 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ extern _Py_UopsSymbol *_Py_uop_sym_new_type(
extern _Py_UopsSymbol *_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val);
extern _Py_UopsSymbol *_Py_uop_sym_new_null(_Py_UOpsContext *ctx);
extern bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ);
extern void _Py_uop_sym_set_null(_Py_UopsSymbol *sym);
extern void _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym);
extern void _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ);
extern void _Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val);
extern bool _Py_uop_sym_set_null(_Py_UopsSymbol *sym);
extern bool _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym);
extern bool _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ);
extern bool _Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val);
extern bool _Py_uop_sym_is_bottom(_Py_UopsSymbol *sym);


Expand Down
24 changes: 17 additions & 7 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,23 +894,33 @@ def testfunc(n):

def test_type_inconsistency(self):
ns = {}
exec(textwrap.dedent("""
src = textwrap.dedent("""
def testfunc(n):
for i in range(n):
x = _test_global + _test_global
"""), globals(), ns)
""")
exec(src, ns, ns)
testfunc = ns['testfunc']
# Must be a real global else it won't be optimized to _LOAD_CONST_INLINE
global _test_global
_test_global = 0
ns['_test_global'] = 0
_, ex = self._run_with_optimizer(testfunc, 16)
self.assertIsNone(ex)
_test_global = 1.2
ns['_test_global'] = 1
_, ex = self._run_with_optimizer(testfunc, 16)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_GUARD_BOTH_INT", uops)
self.assertNotIn("_GUARD_BOTH_INT", uops)
self.assertIn("_BINARY_OP_ADD_INT", uops)
# Try again, but between the runs, set the global to a float.
# This should result in no executor the second time.
ns = {}
exec(src, ns, ns)
testfunc = ns['testfunc']
ns['_test_global'] = 0
_, ex = self._run_with_optimizer(testfunc, 16)
self.assertIsNone(ex)
ns['_test_global'] = 3.14
_, ex = self._run_with_optimizer(testfunc, 16)
self.assertIsNone(ex)


if __name__ == "__main__":
Expand Down
9 changes: 9 additions & 0 deletions Python/optimizer_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,15 @@ optimize_uops(
DPRINTF(1, "Encountered error in abstract interpreter\n");
_Py_uop_abstractcontext_fini(ctx);
return 0;

hit_bottom:
// Attempted to push a "bottom" (contradition) symbol onto the stack.
// This means that the abstract interpreter has hit unreachable code.
// We *could* generate an _EXIT_TRACE or _FATAL_ERROR here, but it's
// simpler to just admit failure and not create the executor.
DPRINTF(1, "Hit bottom in abstract interpreter\n");
_Py_uop_abstractcontext_fini(ctx);
return 0;
}


Expand Down
36 changes: 27 additions & 9 deletions Python/optimizer_bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,38 @@ dummy_func(void) {
sym_matches_type(right, &PyLong_Type)) {
REPLACE_OP(this_instr, _NOP, 0, 0);
}
sym_set_type(left, &PyLong_Type);
sym_set_type(right, &PyLong_Type);
if (!sym_set_type(left, &PyLong_Type)) {
goto hit_bottom;
}
if (!sym_set_type(right, &PyLong_Type)) {
goto hit_bottom;
}
}

op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) {
if (sym_matches_type(left, &PyFloat_Type) &&
sym_matches_type(right, &PyFloat_Type)) {
REPLACE_OP(this_instr, _NOP, 0 ,0);
}
sym_set_type(left, &PyFloat_Type);
sym_set_type(right, &PyFloat_Type);
if (!sym_set_type(left, &PyFloat_Type)) {
goto hit_bottom;
}
if (!sym_set_type(right, &PyFloat_Type)) {
goto hit_bottom;
}
}

op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) {
if (sym_matches_type(left, &PyUnicode_Type) &&
sym_matches_type(right, &PyUnicode_Type)) {
REPLACE_OP(this_instr, _NOP, 0 ,0);
}
sym_set_type(left, &PyUnicode_Type);
sym_set_type(right, &PyUnicode_Type);
if (!sym_set_type(left, &PyUnicode_Type)) {
goto hit_bottom;
}
if (!sym_set_type(right, &PyUnicode_Type)) {
goto hit_bottom;
}
}

op(_BINARY_OP_ADD_INT, (left, right -- res)) {
Expand Down Expand Up @@ -365,14 +377,20 @@ dummy_func(void) {


op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
sym_set_type(callable, &PyFunction_Type);
if (!sym_set_type(callable, &PyFunction_Type)) {
goto hit_bottom;
}
(void)self_or_null;
(void)func_version;
}

op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) {
sym_set_null(null);
sym_set_type(callable, &PyMethod_Type);
if (!sym_set_null(null)) {
goto hit_bottom;
}
if (!sym_set_type(callable, &PyMethod_Type)) {
goto hit_bottom;
}
}

op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) {
Expand Down
36 changes: 27 additions & 9 deletions Python/optimizer_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 22 additions & 14 deletions Python/optimizer_symbols.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,60 +113,68 @@ _Py_uop_sym_get_const(_Py_UopsSymbol *sym)
return sym->const_val;
}

void
bool
_Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ)
{
assert(typ != NULL && PyType_Check(typ));
if (sym->flags & IS_NULL) {
sym_set_bottom(sym);
return;
return false;
}
if (sym->typ != NULL) {
if (sym->typ != typ) {
sym_set_bottom(sym);
return false;
}
return;
}
sym_set_flag(sym, NOT_NULL);
sym->typ = typ;
else {
sym_set_flag(sym, NOT_NULL);
sym->typ = typ;
}
return true;
}

void
bool
_Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val)
{
assert(const_val != NULL);
if (sym->flags & IS_NULL) {
sym_set_bottom(sym);
return;
return false;
}
PyTypeObject *typ = Py_TYPE(const_val);
if (sym->typ != NULL && sym->typ != typ) {
sym_set_bottom(sym);
return;
return false;
}
if (sym->const_val != NULL) {
if (sym->const_val != const_val) {
// TODO: What if they're equal?
sym_set_bottom(sym);
return false;
}
return;
}
sym_set_flag(sym, NOT_NULL);
sym->typ = typ;
sym->const_val = Py_NewRef(const_val);
else {
sym_set_flag(sym, NOT_NULL);
sym->typ = typ;
sym->const_val = Py_NewRef(const_val);
}
return true;
}


void
bool
_Py_uop_sym_set_null(_Py_UopsSymbol *sym)
{
sym_set_flag(sym, IS_NULL);
return !_Py_uop_sym_is_bottom(sym);
}

void
bool
_Py_uop_sym_set_non_null(_Py_UopsSymbol *sym)
{
sym_set_flag(sym, NOT_NULL);
return !_Py_uop_sym_is_bottom(sym);
}


Expand Down
4 changes: 0 additions & 4 deletions Tools/cases_generator/optimizer_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@
"""

import argparse
import os.path
import sys

from analyzer import (
Analysis,
Instruction,
Uop,
Part,
analyze_files,
Skip,
StackItem,
analysis_error,
)
Expand Down

0 comments on commit 0656509

Please sign in to comment.