Skip to content

Commit ac8a5a7

Browse files
authored
[mypyc] Allow specifying primitives as pure (#17263)
Pure primitives have no side effects, take only immutable arguments, and never fail. These properties will enable additional optimizations. For example, it doesn't matter in which order these primitives are evaluated, and we can perform common subexpression elimination on them. Only mark a few primitives as pure for now, but we can generalize this later.
1 parent c27f4f5 commit ac8a5a7

File tree

4 files changed

+32
-0
lines changed

4 files changed

+32
-0
lines changed

mypyc/ir/ops.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,7 @@ def __init__(
600600
ordering: list[int] | None,
601601
extra_int_constants: list[tuple[int, RType]],
602602
priority: int,
603+
is_pure: bool,
603604
) -> None:
604605
# Each primitive much have a distinct name, but otherwise they are arbitrary.
605606
self.name: Final = name
@@ -617,6 +618,11 @@ def __init__(
617618
self.ordering: Final = ordering
618619
self.extra_int_constants: Final = extra_int_constants
619620
self.priority: Final = priority
621+
# Pure primitives have no side effects, take immutable arguments, and
622+
# never fail. They support additional optimizations.
623+
self.is_pure: Final = is_pure
624+
if is_pure:
625+
assert error_kind == ERR_NEVER
620626

621627
def __repr__(self) -> str:
622628
return f"<PrimitiveDescription {self.name}>"
@@ -1036,6 +1042,8 @@ def __init__(
10361042
error_kind: int,
10371043
line: int,
10381044
var_arg_idx: int = -1,
1045+
*,
1046+
is_pure: bool = False,
10391047
) -> None:
10401048
self.error_kind = error_kind
10411049
super().__init__(line)
@@ -1046,6 +1054,12 @@ def __init__(
10461054
self.is_borrowed = is_borrowed
10471055
# The position of the first variable argument in args (if >= 0)
10481056
self.var_arg_idx = var_arg_idx
1057+
# Is the function pure? Pure functions have no side effects
1058+
# and all the arguments are immutable. Pure functions support
1059+
# additional optimizations. Pure functions never fail.
1060+
self.is_pure = is_pure
1061+
if is_pure:
1062+
assert error_kind == ERR_NEVER
10491063

10501064
def sources(self) -> list[Value]:
10511065
return self.args

mypyc/irbuild/ll_builder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,6 +1821,7 @@ def call_c(
18211821
error_kind,
18221822
line,
18231823
var_arg_idx,
1824+
is_pure=desc.is_pure,
18241825
)
18251826
)
18261827
if desc.is_borrowed:
@@ -1903,6 +1904,7 @@ def primitive_op(
19031904
desc.ordering,
19041905
desc.extra_int_constants,
19051906
desc.priority,
1907+
is_pure=desc.is_pure,
19061908
)
19071909
return self.call_c(c_desc, args, line, result_type)
19081910

mypyc/primitives/int_ops.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription:
199199
return_type=bit_rprimitive,
200200
c_function_name="CPyTagged_IsEq_",
201201
error_kind=ERR_NEVER,
202+
is_pure=True,
202203
)
203204

204205
# Less than operation on two boxed tagged integers
@@ -207,6 +208,7 @@ def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription:
207208
return_type=bit_rprimitive,
208209
c_function_name="CPyTagged_IsLt_",
209210
error_kind=ERR_NEVER,
211+
is_pure=True,
210212
)
211213

212214
int64_divide_op = custom_op(

mypyc/primitives/registry.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class CFunctionDescription(NamedTuple):
6060
ordering: list[int] | None
6161
extra_int_constants: list[tuple[int, RType]]
6262
priority: int
63+
is_pure: bool
6364

6465

6566
# A description for C load operations including LoadGlobal and LoadAddress
@@ -97,6 +98,7 @@ def method_op(
9798
steals: StealsDescription = False,
9899
is_borrowed: bool = False,
99100
priority: int = 1,
101+
is_pure: bool = False,
100102
) -> CFunctionDescription:
101103
"""Define a c function call op that replaces a method call.
102104
@@ -121,6 +123,8 @@ def method_op(
121123
steals: description of arguments that this steals (ref count wise)
122124
is_borrowed: if True, returned value is borrowed (no need to decrease refcount)
123125
priority: if multiple ops match, the one with the highest priority is picked
126+
is_pure: if True, declare that the C function has no side effects, takes immutable
127+
arguments, and never raises an exception
124128
"""
125129
if extra_int_constants is None:
126130
extra_int_constants = []
@@ -138,6 +142,7 @@ def method_op(
138142
ordering,
139143
extra_int_constants,
140144
priority,
145+
is_pure=is_pure,
141146
)
142147
ops.append(desc)
143148
return desc
@@ -183,6 +188,7 @@ def function_op(
183188
ordering,
184189
extra_int_constants,
185190
priority,
191+
is_pure=False,
186192
)
187193
ops.append(desc)
188194
return desc
@@ -228,6 +234,7 @@ def binary_op(
228234
ordering=ordering,
229235
extra_int_constants=extra_int_constants,
230236
priority=priority,
237+
is_pure=False,
231238
)
232239
ops.append(desc)
233240
return desc
@@ -244,6 +251,8 @@ def custom_op(
244251
extra_int_constants: list[tuple[int, RType]] | None = None,
245252
steals: StealsDescription = False,
246253
is_borrowed: bool = False,
254+
*,
255+
is_pure: bool = False,
247256
) -> CFunctionDescription:
248257
"""Create a one-off CallC op that can't be automatically generated from the AST.
249258
@@ -264,6 +273,7 @@ def custom_op(
264273
ordering,
265274
extra_int_constants,
266275
0,
276+
is_pure=is_pure,
267277
)
268278

269279

@@ -279,6 +289,7 @@ def custom_primitive_op(
279289
extra_int_constants: list[tuple[int, RType]] | None = None,
280290
steals: StealsDescription = False,
281291
is_borrowed: bool = False,
292+
is_pure: bool = False,
282293
) -> PrimitiveDescription:
283294
"""Define a primitive op that can't be automatically generated based on the AST.
284295
@@ -299,6 +310,7 @@ def custom_primitive_op(
299310
ordering=ordering,
300311
extra_int_constants=extra_int_constants,
301312
priority=0,
313+
is_pure=is_pure,
302314
)
303315

304316

@@ -314,6 +326,7 @@ def unary_op(
314326
steals: StealsDescription = False,
315327
is_borrowed: bool = False,
316328
priority: int = 1,
329+
is_pure: bool = False,
317330
) -> CFunctionDescription:
318331
"""Define a c function call op for an unary operation.
319332
@@ -338,6 +351,7 @@ def unary_op(
338351
ordering,
339352
extra_int_constants,
340353
priority,
354+
is_pure=is_pure,
341355
)
342356
ops.append(desc)
343357
return desc

0 commit comments

Comments
 (0)