From 8d6a97ba4ce02d3150d7fcba401c2c7de553cee0 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Fri, 26 Sep 2025 10:37:23 +0000 Subject: [PATCH 1/8] call C len --- mypyc/irbuild/ll_builder.py | 26 ++++++++++ mypyc/test-data/irbuild-dunders.test | 72 +++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 37f2add4abbd..ab1926c93bc2 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2441,9 +2441,35 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val offset = Integer(1, c_pyssize_t_rprimitive, line) return self.int_op(short_int_rprimitive, size_value, offset, IntOp.LEFT_SHIFT, line) + # --- Optimized dispatch for RInstance (native/user-defined classes) --- if isinstance(typ, RInstance): # TODO: Support use_pyssize_t assert not use_pyssize_t + class_ir = typ.class_ir + + # Only optimize for native extension classes (not built-in base, not Python subclass) + if class_ir.is_ext_class and not class_ir.inherits_python and class_ir.has_method("__len__"): + # 1. Direct C call for final native methods and exact type + if class_ir.is_method_final("__len__"): + decl = class_ir.method_decl("__len__") + length = self.call(decl, [val], [ARG_POS], [None], line) + + # Coerce/check result and error handling as before + length = self.coerce(length, int_rprimitive, line) + ok, fail = BasicBlock(), BasicBlock() + cond = self.binary_op(length, Integer(0), ">=", line) + self.add_bool_branch(cond, ok, fail) + self.activate_block(fail) + self.add( + RaiseStandardError( + RaiseStandardError.VALUE_ERROR, "__len__() should return >= 0", line + ) + ) + self.add(Unreachable()) + self.activate_block(ok) + return length + + # 4. Fallback: generic method call for non-native or ambiguous cases length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() diff --git a/mypyc/test-data/irbuild-dunders.test b/mypyc/test-data/irbuild-dunders.test index 1796a7e2160e..5e3b8085437f 100644 --- a/mypyc/test-data/irbuild-dunders.test +++ b/mypyc/test-data/irbuild-dunders.test @@ -1,24 +1,92 @@ # Test cases for (some) dunder methods [case testDundersLen] +from typing import final + class C: def __len__(self) -> int: return 2 - +@final +class D: + def __len__(self) -> int: + return 2 +class E: + @final + def __len__(self) -> int: + return 2 +class F(C): + """def __len__(self) -> int: + return 3""" def f(c: C) -> int: return len(c) +def g(d: D) -> int: + return len(d) +def h(e: E) -> int: + return len(e) +def i(f: F) -> int: + return len(f) [out] def C.__len__(self): self :: __main__.C L0: return 4 +def D.__len__(self): + self :: __main__.D +L0: + return 4 +def E.__len__(self): + self :: __main__.E +L0: + return 4 def f(c): c :: __main__.C r0 :: int r1 :: bit r2 :: bool L0: - r0 = c.__len__() + r0 = C.__len__(c) + r1 = int_ge r0, 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = raise ValueError('__len__() should return >= 0') + unreachable +L2: + return r0 +def g(d): + d :: __main__.D + r0 :: int + r1 :: bit + r2 :: bool +L0: + r0 = D.__len__(d) + r1 = int_ge r0, 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = raise ValueError('__len__() should return >= 0') + unreachable +L2: + return r0 +def h(e): + e :: __main__.E + r0 :: int + r1 :: bit + r2 :: bool +L0: + r0 = E.__len__(e) + r1 = int_ge r0, 0 + if r1 goto L2 else goto L1 :: bool +L1: + r2 = raise ValueError('__len__() should return >= 0') + unreachable +L2: + return r0 +def i(f): + f :: __main__.F + r0 :: int + r1 :: bit + r2 :: bool +L0: + r0 = C.__len__(f) r1 = int_ge r0, 0 if r1 goto L2 else goto L1 :: bool L1: From 19864033114964c13b2c5b6f58d6c8fc90841a46 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:43:48 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index ab1926c93bc2..5b15d3ee6842 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2448,7 +2448,11 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val class_ir = typ.class_ir # Only optimize for native extension classes (not built-in base, not Python subclass) - if class_ir.is_ext_class and not class_ir.inherits_python and class_ir.has_method("__len__"): + if ( + class_ir.is_ext_class + and not class_ir.inherits_python + and class_ir.has_method("__len__") + ): # 1. Direct C call for final native methods and exact type if class_ir.is_method_final("__len__"): decl = class_ir.method_decl("__len__") From 28abab52b0d405c9dfa3c852de8e555c82119176 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 06:59:39 -0400 Subject: [PATCH 3/8] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 41 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 5b15d3ee6842..540af306e67c 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2441,39 +2441,38 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val offset = Integer(1, c_pyssize_t_rprimitive, line) return self.int_op(short_int_rprimitive, size_value, offset, IntOp.LEFT_SHIFT, line) - # --- Optimized dispatch for RInstance (native/user-defined classes) --- if isinstance(typ, RInstance): # TODO: Support use_pyssize_t assert not use_pyssize_t class_ir = typ.class_ir - # Only optimize for native extension classes (not built-in base, not Python subclass) + # Optimize for native extension classes (not built-in base, not Python subclass) + # Direct C call for final native methods and exact type if ( class_ir.is_ext_class and not class_ir.inherits_python and class_ir.has_method("__len__") + and class_ir.is_method_final("__len__") ): - # 1. Direct C call for final native methods and exact type - if class_ir.is_method_final("__len__"): - decl = class_ir.method_decl("__len__") - length = self.call(decl, [val], [ARG_POS], [None], line) - - # Coerce/check result and error handling as before - length = self.coerce(length, int_rprimitive, line) - ok, fail = BasicBlock(), BasicBlock() - cond = self.binary_op(length, Integer(0), ">=", line) - self.add_bool_branch(cond, ok, fail) - self.activate_block(fail) - self.add( - RaiseStandardError( - RaiseStandardError.VALUE_ERROR, "__len__() should return >= 0", line - ) + decl = class_ir.method_decl("__len__") + length = self.call(decl, [val], [ARG_POS], [None], line) + + # Coerce/check result and error handling as before + length = self.coerce(length, int_rprimitive, line) + ok, fail = BasicBlock(), BasicBlock() + cond = self.binary_op(length, Integer(0), ">=", line) + self.add_bool_branch(cond, ok, fail) + self.activate_block(fail) + self.add( + RaiseStandardError( + RaiseStandardError.VALUE_ERROR, "__len__() should return >= 0", line ) - self.add(Unreachable()) - self.activate_block(ok) - return length + ) + self.add(Unreachable()) + self.activate_block(ok) + return length - # 4. Fallback: generic method call for non-native or ambiguous cases + # Fallback: generic method call for non-native or ambiguous cases length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() From 0fae618556cb8e722d912d1c11e2b0976618f46e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 07:01:32 -0400 Subject: [PATCH 4/8] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 540af306e67c..5d72c9423b87 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2446,14 +2446,8 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val assert not use_pyssize_t class_ir = typ.class_ir - # Optimize for native extension classes (not built-in base, not Python subclass) - # Direct C call for final native methods and exact type - if ( - class_ir.is_ext_class - and not class_ir.inherits_python - and class_ir.has_method("__len__") - and class_ir.is_method_final("__len__") - ): + # Direct C call for final native __len__ methods + if class_ir.has_method("__len__") and class_ir.is_method_final("__len__"): decl = class_ir.method_decl("__len__") length = self.call(decl, [val], [ARG_POS], [None], line) From fff8658cc88d5e7ac2b218db5ce2629d94036ef2 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 07:05:08 -0400 Subject: [PATCH 5/8] deduplicate --- mypyc/irbuild/ll_builder.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 5d72c9423b87..d507b68eda7b 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2450,24 +2450,10 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val if class_ir.has_method("__len__") and class_ir.is_method_final("__len__"): decl = class_ir.method_decl("__len__") length = self.call(decl, [val], [ARG_POS], [None], line) - - # Coerce/check result and error handling as before - length = self.coerce(length, int_rprimitive, line) - ok, fail = BasicBlock(), BasicBlock() - cond = self.binary_op(length, Integer(0), ">=", line) - self.add_bool_branch(cond, ok, fail) - self.activate_block(fail) - self.add( - RaiseStandardError( - RaiseStandardError.VALUE_ERROR, "__len__() should return >= 0", line - ) - ) - self.add(Unreachable()) - self.activate_block(ok) - return length - - # Fallback: generic method call for non-native or ambiguous cases - length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) + else: + # Fallback: generic method call for non-native or ambiguous cases + length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) + length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() cond = self.binary_op(length, Integer(0), ">=", line) From 3bb6177758420dbb8ff59d34dd52bb83099a6863 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:08:10 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index d507b68eda7b..0e7bf115708b 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2453,7 +2453,7 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val else: # Fallback: generic method call for non-native or ambiguous cases length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) - + length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() cond = self.binary_op(length, Integer(0), ">=", line) From d244e55730f35c52645e26d517d94628d65036bc Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 26 Sep 2025 07:08:31 -0400 Subject: [PATCH 7/8] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 0e7bf115708b..42e6b2992180 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2444,16 +2444,15 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val if isinstance(typ, RInstance): # TODO: Support use_pyssize_t assert not use_pyssize_t - class_ir = typ.class_ir - - # Direct C call for final native __len__ methods - if class_ir.has_method("__len__") and class_ir.is_method_final("__len__"): - decl = class_ir.method_decl("__len__") + + if typ.class_ir.has_method("__len__") and typ.class_ir.is_method_final("__len__"): + # Direct C call for final native __len__ methods + decl = typ.class_ir.method_decl("__len__") length = self.call(decl, [val], [ARG_POS], [None], line) else: # Fallback: generic method call for non-native or ambiguous cases length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) - + length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() cond = self.binary_op(length, Integer(0), ">=", line) From 8e55b5e81f4e35f27ca3de6ef9504c8acd5fbd4c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:13:08 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- 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 42e6b2992180..e6833e3c1a02 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2444,7 +2444,7 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val if isinstance(typ, RInstance): # TODO: Support use_pyssize_t assert not use_pyssize_t - + if typ.class_ir.has_method("__len__") and typ.class_ir.is_method_final("__len__"): # Direct C call for final native __len__ methods decl = typ.class_ir.method_decl("__len__") @@ -2452,7 +2452,7 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val else: # Fallback: generic method call for non-native or ambiguous cases length = self.gen_method_call(val, "__len__", [], int_rprimitive, line) - + length = self.coerce(length, int_rprimitive, line) ok, fail = BasicBlock(), BasicBlock() cond = self.binary_op(length, Integer(0), ">=", line)