Skip to content

Commit ce1be2a

Browse files
authored
[mypyc] Improve support for frozenset (#18571)
Add optimized methods for `len` and `contains` on `frozenset` objects.
1 parent acfd53a commit ce1be2a

File tree

11 files changed

+307
-4
lines changed

11 files changed

+307
-4
lines changed

mypyc/codegen/emit.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
is_dict_rprimitive,
3535
is_fixed_width_rtype,
3636
is_float_rprimitive,
37+
is_frozenset_rprimitive,
3738
is_int16_rprimitive,
3839
is_int32_rprimitive,
3940
is_int64_rprimitive,
@@ -609,6 +610,7 @@ def emit_cast(
609610
is_list_rprimitive(typ)
610611
or is_dict_rprimitive(typ)
611612
or is_set_rprimitive(typ)
613+
or is_frozenset_rprimitive(typ)
612614
or is_str_rprimitive(typ)
613615
or is_range_rprimitive(typ)
614616
or is_float_rprimitive(typ)
@@ -625,6 +627,8 @@ def emit_cast(
625627
prefix = "PyDict"
626628
elif is_set_rprimitive(typ):
627629
prefix = "PySet"
630+
elif is_frozenset_rprimitive(typ):
631+
prefix = "PyFrozenSet"
628632
elif is_str_rprimitive(typ):
629633
prefix = "PyUnicode"
630634
elif is_range_rprimitive(typ):

mypyc/doc/frozenset_operations.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. _frozenset-ops:
2+
3+
Native frozenset operations
4+
======================
5+
6+
These ``frozenset`` operations have fast, optimized implementations. Other
7+
frozenset operations use generic implementations that are often slower.
8+
9+
Construction
10+
------------
11+
12+
Construct empty frozenset:
13+
14+
* ``frozenset()``
15+
16+
Construct frozenset from iterable:
17+
18+
* ``frozenset(x: Iterable)``
19+
20+
21+
Operators
22+
---------
23+
24+
* ``item in s``
25+
26+
Functions
27+
---------
28+
29+
* ``len(s: set)``

mypyc/doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ generate fast code.
4141
dict_operations
4242
set_operations
4343
tuple_operations
44+
frozenset_operations
4445

4546
.. toctree::
4647
:maxdepth: 2

mypyc/ir/rtypes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,11 @@ def __hash__(self) -> int:
461461
# Python set object (or an instance of a subclass of set).
462462
set_rprimitive: Final = RPrimitive("builtins.set", is_unboxed=False, is_refcounted=True)
463463

464+
# Python frozenset object (or an instance of a subclass of frozenset).
465+
frozenset_rprimitive: Final = RPrimitive(
466+
"builtins.frozenset", is_unboxed=False, is_refcounted=True
467+
)
468+
464469
# Python str object. At the C layer, str is referred to as unicode
465470
# (PyUnicode).
466471
str_rprimitive: Final = RPrimitive("builtins.str", is_unboxed=False, is_refcounted=True)
@@ -565,6 +570,10 @@ def is_set_rprimitive(rtype: RType) -> bool:
565570
return isinstance(rtype, RPrimitive) and rtype.name == "builtins.set"
566571

567572

573+
def is_frozenset_rprimitive(rtype: RType) -> bool:
574+
return isinstance(rtype, RPrimitive) and rtype.name == "builtins.frozenset"
575+
576+
568577
def is_str_rprimitive(rtype: RType) -> bool:
569578
return isinstance(rtype, RPrimitive) and rtype.name == "builtins.str"
570579

mypyc/irbuild/ll_builder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
is_dict_rprimitive,
100100
is_fixed_width_rtype,
101101
is_float_rprimitive,
102+
is_frozenset_rprimitive,
102103
is_int16_rprimitive,
103104
is_int32_rprimitive,
104105
is_int64_rprimitive,
@@ -2219,7 +2220,7 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val
22192220
size_value = None
22202221
if is_list_rprimitive(typ) or is_tuple_rprimitive(typ) or is_bytes_rprimitive(typ):
22212222
size_value = self.primitive_op(var_object_size, [val], line)
2222-
elif is_set_rprimitive(typ):
2223+
elif is_set_rprimitive(typ) or is_frozenset_rprimitive(typ):
22232224
elem_address = self.add(GetElementPtr(val, PySetObject, "used"))
22242225
size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address))
22252226
self.add(KeepAlive([val]))

mypyc/irbuild/mapper.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
bytes_rprimitive,
3434
dict_rprimitive,
3535
float_rprimitive,
36+
frozenset_rprimitive,
3637
int16_rprimitive,
3738
int32_rprimitive,
3839
int64_rprimitive,
@@ -89,6 +90,8 @@ def type_to_rtype(self, typ: Type | None) -> RType:
8990
return dict_rprimitive
9091
elif typ.type.fullname == "builtins.set":
9192
return set_rprimitive
93+
elif typ.type.fullname == "builtins.frozenset":
94+
return frozenset_rprimitive
9295
elif typ.type.fullname == "builtins.tuple":
9396
return tuple_rprimitive # Varying-length tuple
9497
elif typ.type.fullname == "builtins.range":

mypyc/primitives/set_ops.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Primitive set (and frozenset) ops."""
1+
"""Primitive set and frozenset ops."""
22

33
from __future__ import annotations
44

@@ -7,6 +7,7 @@
77
bit_rprimitive,
88
bool_rprimitive,
99
c_int_rprimitive,
10+
frozenset_rprimitive,
1011
object_rprimitive,
1112
pointer_rprimitive,
1213
set_rprimitive,
@@ -44,11 +45,21 @@
4445
error_kind=ERR_MAGIC,
4546
)
4647

48+
# Construct an empty frozenset
49+
function_op(
50+
name="builtins.frozenset",
51+
arg_types=[],
52+
return_type=frozenset_rprimitive,
53+
c_function_name="PyFrozenSet_New",
54+
error_kind=ERR_MAGIC,
55+
extra_int_constants=[(0, pointer_rprimitive)],
56+
)
57+
4758
# frozenset(obj)
4859
function_op(
4960
name="builtins.frozenset",
5061
arg_types=[object_rprimitive],
51-
return_type=object_rprimitive,
62+
return_type=frozenset_rprimitive,
5263
c_function_name="PyFrozenSet_New",
5364
error_kind=ERR_MAGIC,
5465
)
@@ -64,6 +75,17 @@
6475
ordering=[1, 0],
6576
)
6677

78+
# item in frozenset
79+
binary_op(
80+
name="in",
81+
arg_types=[object_rprimitive, frozenset_rprimitive],
82+
return_type=c_int_rprimitive,
83+
c_function_name="PySet_Contains",
84+
error_kind=ERR_NEG_INT,
85+
truncated_type=bool_rprimitive,
86+
ordering=[1, 0],
87+
)
88+
6789
# set.remove(obj)
6890
method_op(
6991
name="remove",
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
[case testNewFrozenSet]
2+
from typing import FrozenSet
3+
def f() -> FrozenSet[int]:
4+
return frozenset({1, 2, 3})
5+
[out]
6+
def f():
7+
r0 :: set
8+
r1 :: object
9+
r2 :: i32
10+
r3 :: bit
11+
r4 :: object
12+
r5 :: i32
13+
r6 :: bit
14+
r7 :: object
15+
r8 :: i32
16+
r9 :: bit
17+
r10 :: frozenset
18+
L0:
19+
r0 = PySet_New(0)
20+
r1 = object 1
21+
r2 = PySet_Add(r0, r1)
22+
r3 = r2 >= 0 :: signed
23+
r4 = object 2
24+
r5 = PySet_Add(r0, r4)
25+
r6 = r5 >= 0 :: signed
26+
r7 = object 3
27+
r8 = PySet_Add(r0, r7)
28+
r9 = r8 >= 0 :: signed
29+
r10 = PyFrozenSet_New(r0)
30+
return r10
31+
32+
[case testNewEmptyFrozenSet]
33+
from typing import FrozenSet
34+
def f1() -> FrozenSet[int]:
35+
return frozenset()
36+
37+
def f2() -> FrozenSet[int]:
38+
return frozenset(())
39+
[out]
40+
def f1():
41+
r0 :: frozenset
42+
L0:
43+
r0 = PyFrozenSet_New(0)
44+
return r0
45+
def f2():
46+
r0 :: tuple[]
47+
r1 :: object
48+
r2 :: frozenset
49+
L0:
50+
r0 = ()
51+
r1 = box(tuple[], r0)
52+
r2 = PyFrozenSet_New(r1)
53+
return r2
54+
55+
[case testNewFrozenSetFromIterable]
56+
from typing import FrozenSet, List, TypeVar
57+
58+
T = TypeVar("T")
59+
60+
def f(l: List[T]) -> FrozenSet[T]:
61+
return frozenset(l)
62+
[out]
63+
def f(l):
64+
l :: list
65+
r0 :: frozenset
66+
L0:
67+
r0 = PyFrozenSet_New(l)
68+
return r0
69+
70+
[case testFrozenSetSize]
71+
from typing import FrozenSet
72+
def f() -> int:
73+
return len(frozenset((1, 2, 3)))
74+
[out]
75+
def f():
76+
r0 :: tuple[int, int, int]
77+
r1 :: object
78+
r2 :: frozenset
79+
r3 :: ptr
80+
r4 :: native_int
81+
r5 :: short_int
82+
L0:
83+
r0 = (2, 4, 6)
84+
r1 = box(tuple[int, int, int], r0)
85+
r2 = PyFrozenSet_New(r1)
86+
r3 = get_element_ptr r2 used :: PySetObject
87+
r4 = load_mem r3 :: native_int*
88+
keep_alive r2
89+
r5 = r4 << 1
90+
return r5
91+
92+
[case testFrozenSetContains]
93+
from typing import FrozenSet
94+
def f() -> bool:
95+
x = frozenset((3, 4))
96+
return (5 in x)
97+
[out]
98+
def f():
99+
r0 :: tuple[int, int]
100+
r1 :: object
101+
r2, x :: frozenset
102+
r3 :: object
103+
r4 :: i32
104+
r5 :: bit
105+
r6 :: bool
106+
L0:
107+
r0 = (6, 8)
108+
r1 = box(tuple[int, int], r0)
109+
r2 = PyFrozenSet_New(r1)
110+
x = r2
111+
r3 = object 5
112+
r4 = PySet_Contains(x, r3)
113+
r5 = r4 >= 0 :: signed
114+
r6 = truncate r4: i32 to builtins.bool
115+
return r6

mypyc/test-data/irbuild-set.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ def not_precomputed_nested_set(i):
686686
r1 :: object
687687
r2 :: i32
688688
r3 :: bit
689-
r4 :: object
689+
r4 :: frozenset
690690
r5 :: set
691691
r6 :: i32
692692
r7 :: bit

0 commit comments

Comments
 (0)