From 4a050233edd01a6adcac36525f5311f766922c31 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 17:01:31 -0400 Subject: [PATCH 01/10] Initial implementation. --- Include/internal/pycore_intrinsics.h | 3 ++- Include/internal/pycore_opcode_utils.h | 3 ++- Include/internal/pycore_setobject.h | 3 +++ Objects/setobject.c | 30 +++++++++++++++++++++++++- Python/codegen.c | 13 ++++++++--- Python/intrinsics.c | 10 +++++++++ Python/pylifecycle.c | 1 + 7 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_intrinsics.h b/Include/internal/pycore_intrinsics.h index 39c2a30f6e979d..59a7b16073f886 100644 --- a/Include/internal/pycore_intrinsics.h +++ b/Include/internal/pycore_intrinsics.h @@ -18,8 +18,9 @@ #define INTRINSIC_TYPEVARTUPLE 9 #define INTRINSIC_SUBSCRIPT_GENERIC 10 #define INTRINSIC_TYPEALIAS 11 +#define INTRINSIC_BUILD_FROZENSET 12 -#define MAX_INTRINSIC_1 11 +#define MAX_INTRINSIC_1 12 /* Binary Functions: */ diff --git a/Include/internal/pycore_opcode_utils.h b/Include/internal/pycore_opcode_utils.h index 3e2c4ae411c925..b20718344b3998 100644 --- a/Include/internal/pycore_opcode_utils.h +++ b/Include/internal/pycore_opcode_utils.h @@ -80,7 +80,8 @@ extern "C" { #define CONSTANT_TRUE 9 #define CONSTANT_FALSE 10 #define CONSTANT_MINUS_ONE 11 -#define NUM_COMMON_CONSTANTS 12 +#define CONSTANT_BUILTIN_FROZENSET 12 +#define NUM_COMMON_CONSTANTS 13 /* Values used in the oparg for RESUME */ #define RESUME_AT_FUNC_START 0 diff --git a/Include/internal/pycore_setobject.h b/Include/internal/pycore_setobject.h index 24d0135ed1aeca..fae1cf3e8e30dc 100644 --- a/Include/internal/pycore_setobject.h +++ b/Include/internal/pycore_setobject.h @@ -35,6 +35,9 @@ extern void _PySet_ClearInternal(PySetObject *so); PyAPI_FUNC(int) _PySet_AddTakeRef(PySetObject *so, PyObject *key); +PyObject * +_PyFrozenSet_NewAndSteal(PyObject *set); + #ifdef __cplusplus } #endif diff --git a/Objects/setobject.c b/Objects/setobject.c index 1e630563604552..f1e8a85d95ee3d 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1416,7 +1416,19 @@ make_new_frozenset(PyTypeObject *type, PyObject *iterable) /* frozenset(f) is idempotent */ return Py_NewRef(iterable); } - PyObject *obj = make_new_set(type, iterable); + + // For cases like frozenset({1, 2, 3}), we can just steal the memory from + // the mutable set to avoid the copy. + PyObject *obj; + if (iterable != NULL + && PySet_CheckExact(iterable) + && PyUnstable_Object_IsUniqueReferencedTemporary(iterable)) { + return _PyFrozenSet_NewAndSteal(iterable); + } + else { + obj = make_new_set(type, iterable); + } + if (obj != NULL) { _PyFrozenSet_MaybeUntrack(obj); } @@ -1545,6 +1557,22 @@ set_swap_bodies(PySetObject *a, PySetObject *b) FT_ATOMIC_STORE_PTR_RELEASE(b->table, b_table); } +PyObject * +_PyFrozenSet_NewAndSteal(PyObject *set) +{ + assert(set != NULL); + assert(PySet_CheckExact(set)); + assert(_PyObject_IsUniquelyReferenced(set)); + PyObject *frozenset = make_new_set(&PyFrozenSet_Type, NULL); + if (frozenset == NULL) { + return NULL; + } + + set_swap_bodies((PySetObject *)set, (PySetObject *)frozenset); + _PyFrozenSet_MaybeUntrack(frozenset); + return frozenset; +} + /*[clinic input] @critical_section set.copy diff --git a/Python/codegen.c b/Python/codegen.c index 529c1733598e38..59ea9fde7158b2 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3994,6 +3994,9 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "set")) { const_oparg = CONSTANT_BUILTIN_SET; } + else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset")) { + const_oparg = CONSTANT_BUILTIN_FROZENSET; + } if (const_oparg != -1) { ADDOP_I(c, loc, COPY, 1); // the function ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, const_oparg); @@ -4003,7 +4006,7 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) if (const_oparg == CONSTANT_BUILTIN_TUPLE || const_oparg == CONSTANT_BUILTIN_LIST) { ADDOP_I(c, loc, BUILD_LIST, 0); - } else if (const_oparg == CONSTANT_BUILTIN_SET) { + } else if (const_oparg == CONSTANT_BUILTIN_SET || const_oparg == CONSTANT_BUILTIN_FROZENSET) { ADDOP_I(c, loc, BUILD_SET, 0); } VISIT(c, expr, generator_exp); @@ -4017,7 +4020,7 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) if (const_oparg == CONSTANT_BUILTIN_TUPLE || const_oparg == CONSTANT_BUILTIN_LIST) { ADDOP_I(c, loc, LIST_APPEND, 3); ADDOP_JUMP(c, loc, JUMP, loop); - } else if (const_oparg == CONSTANT_BUILTIN_SET) { + } else if (const_oparg == CONSTANT_BUILTIN_SET || const_oparg == CONSTANT_BUILTIN_FROZENSET) { ADDOP_I(c, loc, SET_ADD, 3); ADDOP_JUMP(c, loc, JUMP, loop); } @@ -4029,7 +4032,8 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) ADDOP(c, NO_LOCATION, POP_ITER); if (const_oparg != CONSTANT_BUILTIN_TUPLE && const_oparg != CONSTANT_BUILTIN_LIST && - const_oparg != CONSTANT_BUILTIN_SET) { + const_oparg != CONSTANT_BUILTIN_SET && + const_oparg != CONSTANT_BUILTIN_FROZENSET) { ADDOP_LOAD_CONST(c, loc, initial_res == Py_True ? Py_False : Py_True); } ADDOP_JUMP(c, loc, JUMP, end); @@ -4044,6 +4048,9 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) } else if (const_oparg == CONSTANT_BUILTIN_SET) { // result is already a set } + else if (const_oparg == CONSTANT_BUILTIN_FROZENSET) { + ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_BUILD_FROZENSET); + } else { ADDOP_LOAD_CONST(c, loc, initial_res); } diff --git a/Python/intrinsics.c b/Python/intrinsics.c index 9f994950f2721d..2e5bece976e72c 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -9,6 +9,7 @@ #include "pycore_intrinsics.h" // INTRINSIC_PRINT #include "pycore_list.h" // _PyList_AsTupleAndClear() #include "pycore_object.h" // _PyObject_IsUniquelyReferenced() +#include "pycore_setobject.h" // _PyFrozenSet_NewAndSteal() #include "pycore_pyerrors.h" // _PyErr_SetString() #include "pycore_runtime.h" // _Py_ID() #include "pycore_typevarobject.h" // _Py_make_typevar() @@ -207,6 +208,14 @@ make_typevar(PyThreadState* Py_UNUSED(ignored), PyObject *v) return _Py_make_typevar(v, NULL, NULL); } +static PyObject * +make_frozenset(PyThreadState* Py_UNUSED(ignored), PyObject *set) +{ + assert(PySet_CheckExact(set)); + assert(_PyObject_IsUniquelyReferenced(set)); + return _PyFrozenSet_NewAndSteal(set); +} + #define INTRINSIC_FUNC_ENTRY(N, F) \ [N] = {F, #N}, @@ -225,6 +234,7 @@ _PyIntrinsics_UnaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEVARTUPLE, _Py_make_typevartuple) INTRINSIC_FUNC_ENTRY(INTRINSIC_SUBSCRIPT_GENERIC, _Py_subscript_generic) INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEALIAS, _Py_make_typealias) + INTRINSIC_FUNC_ENTRY(INTRINSIC_BUILD_FROZENSET, make_frozenset) }; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 46579a45f4cc39..3c2862dcc90c0a 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -892,6 +892,7 @@ pycore_init_builtins(PyThreadState *tstate) interp->common_consts[CONSTANT_FALSE] = Py_False; interp->common_consts[CONSTANT_MINUS_ONE] = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS - 1]; + interp->common_consts[CONSTANT_BUILTIN_FROZENSET] = (PyObject *)&PyFrozenDict_Type; for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) { assert(interp->common_consts[i] != NULL); } From 3fe7fac4f83d4b94e9a3e4c10b0793c6cfe24cfe Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 17:12:06 -0400 Subject: [PATCH 02/10] Add tests and fix things. --- Lib/opcode.py | 2 +- Lib/test/test_builtin.py | 23 ++++++++++++++--------- Python/pylifecycle.c | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Lib/opcode.py b/Lib/opcode.py index 4e60fb5af34f22..bb7824da70e8e5 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -44,7 +44,7 @@ builtins.set, # Append-only — must match CONSTANT_* in # Include/internal/pycore_opcode_utils.h. - None, "", True, False, -1] + None, "", True, False, -1, builtins.frozenset] _nb_ops = _opcode.get_nb_ops() hascompare = [opmap["COMPARE_OP"]] diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 81967fb8a83740..823098303d4036 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -268,7 +268,10 @@ def f_list(): def f_set(): return set(2*x for x in [1,2,3]) - funcs = [f_all, f_any, f_tuple, f_list, f_set] + def f_frozenset(): + return frozenset(2*x for x in [1,2,3]) + + funcs = [f_all, f_any, f_tuple, f_list, f_set, f_frozenset] for f in funcs: # check that generator code object is not duplicated @@ -278,35 +281,37 @@ def f_set(): # check the overriding the builtins works - global all, any, tuple, list, set - saved = all, any, tuple, list, set + global all, any, tuple, list, set, frozenset + saved = all, any, tuple, list, set, frozenset try: all = lambda x : "all" any = lambda x : "any" tuple = lambda x : "tuple" list = lambda x : "list" set = lambda x : "set" + frozenset = lambda x : "frozenset" overridden_outputs = [f() for f in funcs] finally: - all, any, tuple, list, set = saved + all, any, tuple, list, set, frozenset = saved - self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set']) + self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set', 'frozenset']) # Now repeat, overriding the builtins module as well - saved = all, any, tuple, list, set + saved = all, any, tuple, list, set, frozenset try: builtins.all = all = lambda x : "all" builtins.any = any = lambda x : "any" builtins.tuple = tuple = lambda x : "tuple" builtins.list = list = lambda x : "list" builtins.set = set = lambda x : "set" + builtins.frozenset = frozenset = lambda x : "frozenset" overridden_outputs = [f() for f in funcs] finally: - all, any, tuple, list, set = saved - builtins.all, builtins.any, builtins.tuple, builtins.list, builtins.set = saved + all, any, tuple, list, set, frozenset = saved + builtins.all, builtins.any, builtins.tuple, builtins.list, builtins.set, builtins.frozenset = saved - self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set']) + self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set', 'frozenset']) def test_builtin_call_async_genexpr_no_crash(self): async def f_all(): diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 3c2862dcc90c0a..cc29a832fc7549 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -892,7 +892,7 @@ pycore_init_builtins(PyThreadState *tstate) interp->common_consts[CONSTANT_FALSE] = Py_False; interp->common_consts[CONSTANT_MINUS_ONE] = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS - 1]; - interp->common_consts[CONSTANT_BUILTIN_FROZENSET] = (PyObject *)&PyFrozenDict_Type; + interp->common_consts[CONSTANT_BUILTIN_FROZENSET] = (PyObject *)&PyFrozenSet_Type; for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) { assert(interp->common_consts[i] != NULL); } From ab4bd0aa70f0e152e4dd4738a43764d49c4b4cac Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 17:16:55 -0400 Subject: [PATCH 03/10] Add blurb. --- .../2026-05-18-17-16-51.gh-issue-150027.sJgLvd.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-18-17-16-51.gh-issue-150027.sJgLvd.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-18-17-16-51.gh-issue-150027.sJgLvd.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-18-17-16-51.gh-issue-150027.sJgLvd.rst new file mode 100644 index 00000000000000..66446105da3fba --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-18-17-16-51.gh-issue-150027.sJgLvd.rst @@ -0,0 +1,2 @@ +Improve performance of :class:`frozenset` objects by avoiding copies during +construction. From 3e21fd6b7aa50ccc1316a563d297d25c8e8c60bb Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 18:45:26 -0400 Subject: [PATCH 04/10] Changes based on sprint feedback. --- Objects/setobject.c | 14 +------------- Python/codegen.c | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index f1e8a85d95ee3d..a635e8d6c0fcb1 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1416,19 +1416,7 @@ make_new_frozenset(PyTypeObject *type, PyObject *iterable) /* frozenset(f) is idempotent */ return Py_NewRef(iterable); } - - // For cases like frozenset({1, 2, 3}), we can just steal the memory from - // the mutable set to avoid the copy. - PyObject *obj; - if (iterable != NULL - && PySet_CheckExact(iterable) - && PyUnstable_Object_IsUniqueReferencedTemporary(iterable)) { - return _PyFrozenSet_NewAndSteal(iterable); - } - else { - obj = make_new_set(type, iterable); - } - + PyObject *obj = make_new_set(type, iterable); if (obj != NULL) { _PyFrozenSet_MaybeUntrack(obj); } diff --git a/Python/codegen.c b/Python/codegen.c index 59ea9fde7158b2..8cba461b074e6c 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3953,12 +3953,35 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) if (! (func->kind == Name_kind && asdl_seq_LEN(args) == 1 && - asdl_seq_LEN(kwds) == 0 && - asdl_seq_GET(args, 0)->kind == GeneratorExp_kind)) + asdl_seq_LEN(kwds) == 0)) { return 0; } + location loc = LOC(func); + + if (asdl_seq_GET(args, 0)->kind != GeneratorExp_kind) { + if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset")) { + NEW_JUMP_TARGET_LABEL(c, skip_optimization); + + ADDOP_I(c, loc, COPY, 1); + ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_BUILTIN_FROZENSET); + ADDOP_COMPARE(c, loc, Is); + ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization); + ADDOP(c, loc, POP_TOP); + + VISIT(c, expr, e->v.Call.args->elements[0]); + ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_BUILD_FROZENSET); + + ADDOP_JUMP(c, loc, JUMP, end); + + USE_LABEL(c, skip_optimization); + return 1; + } + + return 0; + } + expr_ty generator_exp = asdl_seq_GET(args, 0); PySTEntryObject *generator_entry = _PySymtable_Lookup(SYMTABLE(c), (void *)generator_exp); if (generator_entry->ste_coroutine) { @@ -3967,8 +3990,6 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) } Py_DECREF(generator_entry); - location loc = LOC(func); - int optimized = 0; NEW_JUMP_TARGET_LABEL(c, skip_optimization); From a0ecb7ccab16715823882fad205ff76a3e948e51 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 19:00:15 -0400 Subject: [PATCH 05/10] Only optimize for set literals (and comprehensions). --- Python/codegen.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index 8cba461b074e6c..e70ff44beecb54 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3960,8 +3960,11 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) location loc = LOC(func); - if (asdl_seq_GET(args, 0)->kind != GeneratorExp_kind) { - if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset")) { + expr_ty arg_expr = asdl_seq_GET(args, 0); + + if (arg_expr->kind != GeneratorExp_kind) { + if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset") + && (arg_expr->kind == Set_kind || arg_expr->kind == SetComp_kind)) { NEW_JUMP_TARGET_LABEL(c, skip_optimization); ADDOP_I(c, loc, COPY, 1); @@ -3970,7 +3973,7 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization); ADDOP(c, loc, POP_TOP); - VISIT(c, expr, e->v.Call.args->elements[0]); + VISIT(c, expr, arg_expr); ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_BUILD_FROZENSET); ADDOP_JUMP(c, loc, JUMP, end); @@ -3982,8 +3985,7 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) return 0; } - expr_ty generator_exp = asdl_seq_GET(args, 0); - PySTEntryObject *generator_entry = _PySymtable_Lookup(SYMTABLE(c), (void *)generator_exp); + PySTEntryObject *generator_entry = _PySymtable_Lookup(SYMTABLE(c), (void *)arg_expr); if (generator_entry->ste_coroutine) { Py_DECREF(generator_entry); return 0; @@ -4030,7 +4032,7 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) } else if (const_oparg == CONSTANT_BUILTIN_SET || const_oparg == CONSTANT_BUILTIN_FROZENSET) { ADDOP_I(c, loc, BUILD_SET, 0); } - VISIT(c, expr, generator_exp); + VISIT(c, expr, arg_expr); NEW_JUMP_TARGET_LABEL(c, loop); NEW_JUMP_TARGET_LABEL(c, cleanup); From a44772bf47a171e434d2b1217f3bcac58a526433 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 19:07:56 -0400 Subject: [PATCH 06/10] Modify the type instead of making a new object. --- Objects/setobject.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index a635e8d6c0fcb1..8b091f50626498 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1551,14 +1551,8 @@ _PyFrozenSet_NewAndSteal(PyObject *set) assert(set != NULL); assert(PySet_CheckExact(set)); assert(_PyObject_IsUniquelyReferenced(set)); - PyObject *frozenset = make_new_set(&PyFrozenSet_Type, NULL); - if (frozenset == NULL) { - return NULL; - } - - set_swap_bodies((PySetObject *)set, (PySetObject *)frozenset); - _PyFrozenSet_MaybeUntrack(frozenset); - return frozenset; + set->ob_type = &PyFrozenSet_Type; + return Py_NewRef(set); } /*[clinic input] From e7f2d5627325ed1510742ff1eb1a479f03221351 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 19:51:09 -0400 Subject: [PATCH 07/10] Rename to _PySet_Freeze --- Include/internal/pycore_setobject.h | 2 +- Objects/setobject.c | 2 +- Python/intrinsics.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_setobject.h b/Include/internal/pycore_setobject.h index fae1cf3e8e30dc..92d1a15177f79e 100644 --- a/Include/internal/pycore_setobject.h +++ b/Include/internal/pycore_setobject.h @@ -36,7 +36,7 @@ extern void _PySet_ClearInternal(PySetObject *so); PyAPI_FUNC(int) _PySet_AddTakeRef(PySetObject *so, PyObject *key); PyObject * -_PyFrozenSet_NewAndSteal(PyObject *set); +_PySet_Freeze(PyObject *set); #ifdef __cplusplus } diff --git a/Objects/setobject.c b/Objects/setobject.c index 8b091f50626498..a1f654f0715bf3 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1546,7 +1546,7 @@ set_swap_bodies(PySetObject *a, PySetObject *b) } PyObject * -_PyFrozenSet_NewAndSteal(PyObject *set) +_PySet_Freeze(PyObject *set) { assert(set != NULL); assert(PySet_CheckExact(set)); diff --git a/Python/intrinsics.c b/Python/intrinsics.c index 2e5bece976e72c..f081f33cc83b88 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -9,7 +9,7 @@ #include "pycore_intrinsics.h" // INTRINSIC_PRINT #include "pycore_list.h" // _PyList_AsTupleAndClear() #include "pycore_object.h" // _PyObject_IsUniquelyReferenced() -#include "pycore_setobject.h" // _PyFrozenSet_NewAndSteal() +#include "pycore_setobject.h" // _PySet_Freeze() #include "pycore_pyerrors.h" // _PyErr_SetString() #include "pycore_runtime.h" // _Py_ID() #include "pycore_typevarobject.h" // _Py_make_typevar() @@ -213,7 +213,7 @@ make_frozenset(PyThreadState* Py_UNUSED(ignored), PyObject *set) { assert(PySet_CheckExact(set)); assert(_PyObject_IsUniquelyReferenced(set)); - return _PyFrozenSet_NewAndSteal(set); + return _PySet_Freeze(set); } From ed49167bc4962c7ae17eac98950d31be4488cd22 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 19:52:27 -0400 Subject: [PATCH 08/10] Refactor if statement. --- Python/codegen.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index e70ff44beecb54..205c49cff1827c 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3962,26 +3962,26 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) expr_ty arg_expr = asdl_seq_GET(args, 0); - if (arg_expr->kind != GeneratorExp_kind) { - if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset") - && (arg_expr->kind == Set_kind || arg_expr->kind == SetComp_kind)) { - NEW_JUMP_TARGET_LABEL(c, skip_optimization); + if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset") + && (arg_expr->kind == Set_kind || arg_expr->kind == SetComp_kind)) { + NEW_JUMP_TARGET_LABEL(c, skip_optimization); - ADDOP_I(c, loc, COPY, 1); - ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_BUILTIN_FROZENSET); - ADDOP_COMPARE(c, loc, Is); - ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization); - ADDOP(c, loc, POP_TOP); + ADDOP_I(c, loc, COPY, 1); + ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_BUILTIN_FROZENSET); + ADDOP_COMPARE(c, loc, Is); + ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization); + ADDOP(c, loc, POP_TOP); - VISIT(c, expr, arg_expr); - ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_BUILD_FROZENSET); + VISIT(c, expr, arg_expr); + ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_BUILD_FROZENSET); - ADDOP_JUMP(c, loc, JUMP, end); + ADDOP_JUMP(c, loc, JUMP, end); - USE_LABEL(c, skip_optimization); - return 1; - } + USE_LABEL(c, skip_optimization); + return 1; + } + if (arg_expr->kind != GeneratorExp_kind) { return 0; } From e7fc901d657bfb370e68d0ad39a6b7dfdbc20309 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 18 May 2026 20:36:59 -0400 Subject: [PATCH 09/10] Add a bytecode test. --- Lib/test/test_compiler_codegen.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index d02937c84d9534..ddc942dc1190cc 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -161,3 +161,32 @@ def test_syntax_error__return_not_in_function(self): self.assertIsNone(cm.exception.text) self.assertEqual(cm.exception.offset, 1) self.assertEqual(cm.exception.end_offset, 10) + + def test_frozenset_optimization(self): + snippet = "frozenset({1, 2, 3})" + expected = [ + ('RESUME', 0), + ('ANNOTATIONS_PLACEHOLDER', None), + ('LOAD_NAME', 0), + ('COPY', 1), + ('LOAD_COMMON_CONSTANT', 12), + ('IS_OP', 0), + ('POP_JUMP_IF_FALSE', 9), + ('POP_TOP', None), + ('LOAD_CONST', 1), + ('LOAD_CONST', 2), + ('LOAD_CONST', 3), + ('BUILD_SET', 3), + ('CALL_INTRINSIC_1', 12), + ('JUMP', 0), + ('PUSH_NULL', None), + ('LOAD_CONST', 1), + ('LOAD_CONST', 2), + ('LOAD_CONST', 3), + ('BUILD_SET', 3), + ('CALL', 1), + ('POP_TOP', None), + ('LOAD_CONST', 0), + ('RETURN_VALUE', None) + ] + self.codegen_test(snippet, expected) From eeea08753c369ff2e46240ac961d2289b4679b0c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 19 May 2026 12:26:10 -0400 Subject: [PATCH 10/10] Add a label to the test. --- Lib/test/test_compiler_codegen.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index ddc942dc1190cc..36058854a41d63 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -163,6 +163,7 @@ def test_syntax_error__return_not_in_function(self): self.assertEqual(cm.exception.end_offset, 10) def test_frozenset_optimization(self): + l1 = self.Label() snippet = "frozenset({1, 2, 3})" expected = [ ('RESUME', 0), @@ -171,7 +172,7 @@ def test_frozenset_optimization(self): ('COPY', 1), ('LOAD_COMMON_CONSTANT', 12), ('IS_OP', 0), - ('POP_JUMP_IF_FALSE', 9), + ('POP_JUMP_IF_FALSE', l1), ('POP_TOP', None), ('LOAD_CONST', 1), ('LOAD_CONST', 2), @@ -179,6 +180,7 @@ def test_frozenset_optimization(self): ('BUILD_SET', 3), ('CALL_INTRINSIC_1', 12), ('JUMP', 0), + l1, ('PUSH_NULL', None), ('LOAD_CONST', 1), ('LOAD_CONST', 2),