Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Include/internal/pycore_opcode_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ extern "C" {
#define CONSTANT_BUILTIN_ANY 4
#define CONSTANT_BUILTIN_LIST 5
#define CONSTANT_BUILTIN_SET 6
#define NUM_COMMON_CONSTANTS 7
#define CONSTANT_BUILTIN_FROZENDICT 7
#define NUM_COMMON_CONSTANTS 8

/* Values used in the oparg for RESUME */
#define RESUME_AT_FUNC_START 0
Expand Down
2 changes: 1 addition & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
_special_method_names = _opcode.get_special_method_names()
_common_constants = [builtins.AssertionError, builtins.NotImplementedError,
builtins.tuple, builtins.all, builtins.any, builtins.list,
builtins.set]
builtins.set, builtins.frozendict]
_nb_ops = _opcode.get_nb_ops()

hascompare = [opmap["COMPARE_OP"]]
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,29 @@ def g(a):
self.assertTrue(g(4))
self.check_lnotab(g)

def test_constant_folding_frozendict_call(self):
# frozendict(key=const, ...) should be folded to LOAD_CONST
# with a runtime guard (fallback CALL_KW path still exists)
def f():
return frozendict(x=1, y=2)

code = f.__code__
self.assertInBytecode(code, 'LOAD_CONST', frozendict(x=1, y=2))
self.assertInBytecode(code, 'LOAD_COMMON_CONSTANT', 7)
result = f()
self.assertEqual(result, frozendict(x=1, y=2))
self.assertIsInstance(result, frozendict)

def test_constant_folding_frozendict_call_shadowed(self):
# When frozendict is shadowed, the fallback path should be used
def f():
frozendict = dict
return frozendict(x=1, y=2)

result = f()
self.assertEqual(result, {'x': 1, 'y': 2})
self.assertIsInstance(result, dict)

def test_constant_folding_small_int(self):
tests = [
('(0, )[0]', 0),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fold frozendict(key=const, ...) calls to LOAD_CONST in codegen. Patch by
Donghee Na.
61 changes: 61 additions & 0 deletions Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -4128,6 +4128,66 @@ codegen_validate_keywords(compiler *c, asdl_keyword_seq *keywords)
return SUCCESS;
}

/* Try to fold frozendict(key=const, ...) into LOAD_CONST with a runtime guard.
Called after the function has been loaded onto the stack.
Return 1 if optimization was emitted, 0 if not, -1 on error. */
static int
maybe_optimize_frozendict_call(compiler *c, expr_ty e, jump_target_label end)
{
expr_ty func = e->v.Call.func;
asdl_expr_seq *args = e->v.Call.args;
asdl_keyword_seq *kwds = e->v.Call.keywords;

if (func->kind != Name_kind ||
!_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozendict") ||
asdl_seq_LEN(args) != 0)
{
return 0;
}

/* All keywords must have names (no **kwargs) and constant values */
Py_ssize_t nkwds = asdl_seq_LEN(kwds);
for (Py_ssize_t i = 0; i < nkwds; i++) {
keyword_ty kw = asdl_seq_GET(kwds, i);
if (kw->arg == NULL || kw->value->kind != Constant_kind) {
return 0;
}
}

/* Build the frozendict at compile time */
PyObject *dict = PyDict_New();
if (dict == NULL) {
return -1;
}
for (Py_ssize_t i = 0; i < nkwds; i++) {
keyword_ty kw = asdl_seq_GET(kwds, i);
if (PyDict_SetItem(dict, kw->arg, kw->value->v.Constant.value) < 0) {
Py_DECREF(dict);
return -1;
}
}
PyObject *fd = PyFrozenDict_New(dict);
Py_DECREF(dict);
if (fd == NULL) {
return -1;
}

location loc = LOC(func);
NEW_JUMP_TARGET_LABEL(c, skip_optimization);

ADDOP_I(c, loc, COPY, 1);
ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_BUILTIN_FROZENDICT);
ADDOP_COMPARE(c, loc, Is);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization);
ADDOP(c, loc, POP_TOP);
ADDOP_LOAD_CONST(c, LOC(e), fd);
Py_DECREF(fd);
ADDOP_JUMP(c, loc, JUMP, end);

USE_LABEL(c, skip_optimization);
return 1;
}

static int
codegen_call(compiler *c, expr_ty e)
{
Expand All @@ -4143,6 +4203,7 @@ codegen_call(compiler *c, expr_ty e)
RETURN_IF_ERROR(check_caller(c, e->v.Call.func));
VISIT(c, expr, e->v.Call.func);
RETURN_IF_ERROR(maybe_optimize_function_call(c, e, skip_normal_call));
RETURN_IF_ERROR(maybe_optimize_frozendict_call(c, e, skip_normal_call));
location loc = LOC(e->v.Call.func);
ADDOP(c, loc, PUSH_NULL);
loc = LOC(e);
Expand Down
1 change: 1 addition & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,7 @@ pycore_init_builtins(PyThreadState *tstate)
interp->common_consts[CONSTANT_BUILTIN_ANY] = any;
interp->common_consts[CONSTANT_BUILTIN_LIST] = (PyObject*)&PyList_Type;
interp->common_consts[CONSTANT_BUILTIN_SET] = (PyObject*)&PySet_Type;
interp->common_consts[CONSTANT_BUILTIN_FROZENDICT] = (PyObject*)&PyFrozenDict_Type;

for (int i=0; i < NUM_COMMON_CONSTANTS; i++) {
assert(interp->common_consts[i] != NULL);
Expand Down
Loading