From 286cc8f5f4c5d35c9e5e937384cd0552b8ca01ee Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 28 Aug 2025 14:57:46 +0100 Subject: [PATCH 1/7] [mypyc] Refactor IR build for equality --- mypyc/irbuild/ll_builder.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 112bbdbb50ed..9229a5803e57 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -1395,12 +1395,6 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: # Special case various ops if op in ("is", "is not"): return self.translate_is_op(lreg, rreg, op, line) - # TODO: modify 'str' to use same interface as 'compare_bytes' as it avoids - # call to PyErr_Occurred() - if is_str_rprimitive(ltype) and is_str_rprimitive(rtype) and op in ("==", "!="): - return self.compare_strings(lreg, rreg, op, line) - if is_bytes_rprimitive(ltype) and is_bytes_rprimitive(rtype) and op in ("==", "!="): - return self.compare_bytes(lreg, rreg, op, line) if ( is_bool_or_bit_rprimitive(ltype) and is_bool_or_bit_rprimitive(rtype) @@ -1545,6 +1539,10 @@ def compare_strings(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: elif op == "!=": eq = self.primitive_op(str_eq, [lhs, rhs], line) return self.add(ComparisonOp(eq, self.false(), ComparisonOp.EQ, line)) + + # TODO: modify 'str' to use same interface as 'compare_bytes' as it would avoid + # call to PyErr_Occurred() below + compare_result = self.call_c(unicode_compare, [lhs, rhs], line) error_constant = Integer(-1, c_int_rprimitive, line) compare_error_check = self.add( @@ -2480,13 +2478,21 @@ def translate_special_method_call( return primitive_op def translate_eq_cmp(self, lreg: Value, rreg: Value, expr_op: str, line: int) -> Value | None: - """Add a equality comparison operation. + """Add an equality comparison operation. + + Note that this doesn't cover all possible types. Args: expr_op: either '==' or '!=' """ ltype = lreg.type rtype = rreg.type + + if is_str_rprimitive(ltype) and is_str_rprimitive(rtype): + return self.compare_strings(lreg, rreg, expr_op, line) + if is_bytes_rprimitive(ltype) and is_bytes_rprimitive(rtype): + return self.compare_bytes(lreg, rreg, expr_op, line) + if not (isinstance(ltype, RInstance) and ltype == rtype): return None From 956c7078bbce30c28ff7fedb16b6c711426db228 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 29 Aug 2025 10:20:43 +0100 Subject: [PATCH 2/7] [mypyc] Refactor IR build for "not" operation --- mypyc/irbuild/ll_builder.py | 16 ++++++++++++---- mypyc/primitives/generic_ops.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 9229a5803e57..14a5af0e65d7 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -139,6 +139,7 @@ from mypyc.primitives.generic_ops import ( generic_len_op, generic_ssize_t_len_op, + not_op, py_call_op, py_call_with_kwargs_op, py_call_with_posargs_op, @@ -1647,14 +1648,21 @@ def bool_comparison_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Va return self.comparison_op(lreg, rreg, op_id, line) def unary_not(self, value: Value, line: int) -> Value: - mask = Integer(1, value.type, line) - return self.int_op(value.type, value, mask, IntOp.XOR, line) + typ = value.type + if is_bool_or_bit_rprimitive(typ): + mask = Integer(1, value.type, line) + return self.int_op(value.type, value, mask, IntOp.XOR, line) + else: + primitive_ops_candidates = unary_ops.get("not", []) + target = self.matching_primitive_op(primitive_ops_candidates, [value], line) + assert target + return target def unary_op(self, value: Value, expr_op: str, line: int) -> Value: + if expr_op == "not": + return self.unary_not(value, line) typ = value.type if is_bool_or_bit_rprimitive(typ): - if expr_op == "not": - return self.unary_not(value, line) if expr_op == "+": return value if is_fixed_width_rtype(typ): diff --git a/mypyc/primitives/generic_ops.py b/mypyc/primitives/generic_ops.py index 8a4ddc370280..ea19d6dd8099 100644 --- a/mypyc/primitives/generic_ops.py +++ b/mypyc/primitives/generic_ops.py @@ -158,7 +158,7 @@ priority=0, ) -unary_op( +not_op = unary_op( name="not", arg_type=object_rprimitive, return_type=c_int_rprimitive, From 3646976ff926e7e4f84d09bcd0dd30c104868d6d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 29 Aug 2025 10:31:25 +0100 Subject: [PATCH 3/7] [mypyc] Refactor IR build for unary "+" --- mypyc/irbuild/ll_builder.py | 39 ++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 14a5af0e65d7..51bf6485aae3 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -1491,6 +1491,7 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: def dunder_op(self, lreg: Value, rreg: Value | None, op: str, line: int) -> Value | None: """ Dispatch a dunder method if applicable. + For example for `a + b` it will use `a.__add__(b)` which can lead to higher performance due to the fact that the method could be already compiled and optimized instead of going all the way through `PyNumber_Add(a, b)` python api (making a jump into the python DL). @@ -1647,24 +1648,40 @@ def bool_comparison_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Va op_id = ComparisonOp.signed_ops[op] return self.comparison_op(lreg, rreg, op_id, line) + def _non_specialized_unary_op(self, value: Value, op: str, line: int) -> Value: + if isinstance(value.type, RInstance): + result = self.dunder_op(value, None, op, line) + if result is not None: + return result + primitive_ops_candidates = unary_ops.get(op, []) + target = self.matching_primitive_op(primitive_ops_candidates, [value], line) + assert target, "Unsupported unary operation: %s" % op + return target + def unary_not(self, value: Value, line: int) -> Value: typ = value.type if is_bool_or_bit_rprimitive(typ): mask = Integer(1, value.type, line) return self.int_op(value.type, value, mask, IntOp.XOR, line) - else: - primitive_ops_candidates = unary_ops.get("not", []) - target = self.matching_primitive_op(primitive_ops_candidates, [value], line) - assert target - return target + return self._non_specialized_unary_op(value, "not", line) + + def unary_plus(self, value: Value, line: int) -> Value: + typ = value.type + if ( + is_tagged(typ) + or is_float_rprimitive(typ) + or is_bool_or_bit_rprimitive(typ) + or is_fixed_width_rtype(typ) + ): + return value + return self._non_specialized_unary_op(value, "+", line) def unary_op(self, value: Value, expr_op: str, line: int) -> Value: if expr_op == "not": return self.unary_not(value, line) + elif expr_op == "+": + return self.unary_plus(value, line) typ = value.type - if is_bool_or_bit_rprimitive(typ): - if expr_op == "+": - return value if is_fixed_width_rtype(typ): if expr_op == "-": # Translate to '0 - x' @@ -1677,13 +1694,9 @@ def unary_op(self, value: Value, expr_op: str, line: int) -> Value: # Translate to 'x ^ 0xff...' mask = (1 << (typ.size * 8)) - 1 return self.int_op(typ, value, Integer(mask, typ), IntOp.XOR, line) - elif expr_op == "+": - return value if is_float_rprimitive(typ): if expr_op == "-": return self.add(FloatNeg(value, line)) - elif expr_op == "+": - return value if isinstance(value, Integer): # TODO: Overflow? Unsigned? @@ -1691,8 +1704,6 @@ def unary_op(self, value: Value, expr_op: str, line: int) -> Value: if is_short_int_rprimitive(typ): num >>= 1 return Integer(-num, typ, value.line) - if is_tagged(typ) and expr_op == "+": - return value if isinstance(value, Float): return Float(-value.value, value.line) if isinstance(typ, RInstance): From 1942ccdbae4a4633b96cd69bd7440d2a31fda858 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 29 Aug 2025 10:35:04 +0100 Subject: [PATCH 4/7] [mypyc] Refactor IR build for unary "-" --- mypyc/irbuild/ll_builder.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 51bf6485aae3..a7832b5c58f0 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -1665,6 +1665,20 @@ def unary_not(self, value: Value, line: int) -> Value: return self.int_op(value.type, value, mask, IntOp.XOR, line) return self._non_specialized_unary_op(value, "not", line) + def unary_minus(self, value: Value, line: int) -> Value: + typ = value.type + if isinstance(value, Integer): + # TODO: Overflow? Unsigned? + return Integer(-value.numeric_value(), typ, line) + elif isinstance(value, Float): + return Float(-value.value, line) + elif is_fixed_width_rtype(typ): + # Translate to '0 - x' + return self.int_op(typ, Integer(0, typ), value, IntOp.SUB, line) + elif is_float_rprimitive(typ): + return self.add(FloatNeg(value, line)) + return self._non_specialized_unary_op(value, "-", line) + def unary_plus(self, value: Value, line: int) -> Value: typ = value.type if ( @@ -1679,14 +1693,13 @@ def unary_plus(self, value: Value, line: int) -> Value: def unary_op(self, value: Value, expr_op: str, line: int) -> Value: if expr_op == "not": return self.unary_not(value, line) + elif expr_op == "-": + return self.unary_minus(value, line) elif expr_op == "+": return self.unary_plus(value, line) typ = value.type if is_fixed_width_rtype(typ): - if expr_op == "-": - # Translate to '0 - x' - return self.int_op(typ, Integer(0, typ), value, IntOp.SUB, line) - elif expr_op == "~": + if expr_op == "~": if typ.is_signed: # Translate to 'x ^ -1' return self.int_op(typ, value, Integer(-1, typ), IntOp.XOR, line) @@ -1694,18 +1707,7 @@ def unary_op(self, value: Value, expr_op: str, line: int) -> Value: # Translate to 'x ^ 0xff...' mask = (1 << (typ.size * 8)) - 1 return self.int_op(typ, value, Integer(mask, typ), IntOp.XOR, line) - if is_float_rprimitive(typ): - if expr_op == "-": - return self.add(FloatNeg(value, line)) - if isinstance(value, Integer): - # TODO: Overflow? Unsigned? - num = value.value - if is_short_int_rprimitive(typ): - num >>= 1 - return Integer(-num, typ, value.line) - if isinstance(value, Float): - return Float(-value.value, value.line) if isinstance(typ, RInstance): result = self.dunder_op(value, None, expr_op, line) if result is not None: From b9c7f1e8246acbc9ec5c1cfeaa87b39bb2363d57 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 29 Aug 2025 10:37:24 +0100 Subject: [PATCH 5/7] [mypyc] Refactor IR build for unary "~" --- mypyc/irbuild/ll_builder.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index a7832b5c58f0..1e7e51d4884e 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -1690,6 +1690,18 @@ def unary_plus(self, value: Value, line: int) -> Value: return value return self._non_specialized_unary_op(value, "+", line) + def unary_invert(self, value: Value, line: int) -> Value: + typ = value.type + if is_fixed_width_rtype(typ): + if typ.is_signed: + # Translate to 'x ^ -1' + return self.int_op(typ, value, Integer(-1, typ), IntOp.XOR, line) + else: + # Translate to 'x ^ 0xff...' + mask = (1 << (typ.size * 8)) - 1 + return self.int_op(typ, value, Integer(mask, typ), IntOp.XOR, line) + return self._non_specialized_unary_op(value, "~", line) + def unary_op(self, value: Value, expr_op: str, line: int) -> Value: if expr_op == "not": return self.unary_not(value, line) @@ -1697,25 +1709,9 @@ def unary_op(self, value: Value, expr_op: str, line: int) -> Value: return self.unary_minus(value, line) elif expr_op == "+": return self.unary_plus(value, line) - typ = value.type - if is_fixed_width_rtype(typ): - if expr_op == "~": - if typ.is_signed: - # Translate to 'x ^ -1' - return self.int_op(typ, value, Integer(-1, typ), IntOp.XOR, line) - else: - # Translate to 'x ^ 0xff...' - mask = (1 << (typ.size * 8)) - 1 - return self.int_op(typ, value, Integer(mask, typ), IntOp.XOR, line) - - if isinstance(typ, RInstance): - result = self.dunder_op(value, None, expr_op, line) - if result is not None: - return result - primitive_ops_candidates = unary_ops.get(expr_op, []) - target = self.matching_primitive_op(primitive_ops_candidates, [value], line) - assert target, "Unsupported unary operation: %s" % expr_op - return target + elif expr_op == "~": + return self.unary_invert(value, line) + raise RuntimeError("Unsupported unary operation: %s" % expr_op) def make_dict(self, key_value_pairs: Sequence[DictEntry], line: int) -> Value: result: Value | None = None From 96f79ccb9c07672479f2949caa145bf0f1f107f8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 29 Aug 2025 10:41:04 +0100 Subject: [PATCH 6/7] Clean up --- mypyc/irbuild/ll_builder.py | 18 +++++++++++------- mypyc/primitives/generic_ops.py | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 1e7e51d4884e..96f126bd58e3 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -139,7 +139,6 @@ from mypyc.primitives.generic_ops import ( generic_len_op, generic_ssize_t_len_op, - not_op, py_call_op, py_call_with_kwargs_op, py_call_with_posargs_op, @@ -1659,6 +1658,7 @@ def _non_specialized_unary_op(self, value: Value, op: str, line: int) -> Value: return target def unary_not(self, value: Value, line: int) -> Value: + """Perform unary 'not'.""" typ = value.type if is_bool_or_bit_rprimitive(typ): mask = Integer(1, value.type, line) @@ -1666,6 +1666,7 @@ def unary_not(self, value: Value, line: int) -> Value: return self._non_specialized_unary_op(value, "not", line) def unary_minus(self, value: Value, line: int) -> Value: + """Perform unary '-'.""" typ = value.type if isinstance(value, Integer): # TODO: Overflow? Unsigned? @@ -1680,6 +1681,7 @@ def unary_minus(self, value: Value, line: int) -> Value: return self._non_specialized_unary_op(value, "-", line) def unary_plus(self, value: Value, line: int) -> Value: + """Perform unary '+'.""" typ = value.type if ( is_tagged(typ) @@ -1691,6 +1693,7 @@ def unary_plus(self, value: Value, line: int) -> Value: return self._non_specialized_unary_op(value, "+", line) def unary_invert(self, value: Value, line: int) -> Value: + """Perform unary '~'.""" typ = value.type if is_fixed_width_rtype(typ): if typ.is_signed: @@ -1702,16 +1705,17 @@ def unary_invert(self, value: Value, line: int) -> Value: return self.int_op(typ, value, Integer(mask, typ), IntOp.XOR, line) return self._non_specialized_unary_op(value, "~", line) - def unary_op(self, value: Value, expr_op: str, line: int) -> Value: - if expr_op == "not": + def unary_op(self, value: Value, op: str, line: int) -> Value: + """Perform a unary operation.""" + if op == "not": return self.unary_not(value, line) - elif expr_op == "-": + elif op == "-": return self.unary_minus(value, line) - elif expr_op == "+": + elif op == "+": return self.unary_plus(value, line) - elif expr_op == "~": + elif op == "~": return self.unary_invert(value, line) - raise RuntimeError("Unsupported unary operation: %s" % expr_op) + raise RuntimeError("Unsupported unary operation: %s" % op) def make_dict(self, key_value_pairs: Sequence[DictEntry], line: int) -> Value: result: Value | None = None diff --git a/mypyc/primitives/generic_ops.py b/mypyc/primitives/generic_ops.py index ea19d6dd8099..8a4ddc370280 100644 --- a/mypyc/primitives/generic_ops.py +++ b/mypyc/primitives/generic_ops.py @@ -158,7 +158,7 @@ priority=0, ) -not_op = unary_op( +unary_op( name="not", arg_type=object_rprimitive, return_type=c_int_rprimitive, From 20b2ed76f98fbead69ea19743c08e82530feb1a8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 29 Aug 2025 12:10:45 +0100 Subject: [PATCH 7/7] Address feedback --- mypyc/irbuild/ll_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 96f126bd58e3..f82be9fe706f 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -1661,8 +1661,8 @@ def unary_not(self, value: Value, line: int) -> Value: """Perform unary 'not'.""" typ = value.type if is_bool_or_bit_rprimitive(typ): - mask = Integer(1, value.type, line) - return self.int_op(value.type, value, mask, IntOp.XOR, line) + mask = Integer(1, typ, line) + return self.int_op(typ, value, mask, IntOp.XOR, line) return self._non_specialized_unary_op(value, "not", line) def unary_minus(self, value: Value, line: int) -> Value: