Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9e3d54a
[mypyc] feat: new primitive for `int.bit_length`
BobTheBuidler Aug 16, 2025
24a7ae7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 16, 2025
7dbbb77
Update int_ops.py
BobTheBuidler Aug 16, 2025
b133de3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 16, 2025
9d4116e
add headers
BobTheBuidler Aug 16, 2025
499bfb4
Merge branch 'bit-length' of https://github.com/BobTheBuidler/mypy in…
BobTheBuidler Aug 16, 2025
f718507
fixture
BobTheBuidler Aug 16, 2025
bb0a5eb
fix ir
BobTheBuidler Aug 16, 2025
ae266ad
implement c
BobTheBuidler Aug 16, 2025
e83203b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 16, 2025
1545a8d
more run tests
BobTheBuidler Aug 16, 2025
0854952
fix c
BobTheBuidler Aug 16, 2025
1e10c40
Merge branch 'bit-length' of https://github.com/BobTheBuidler/mypy in…
BobTheBuidler Aug 16, 2025
be93fe7
optimize fast path for short
BobTheBuidler Aug 16, 2025
d7b0a0c
fix macro
BobTheBuidler Aug 16, 2025
1eb2c7e
Update run-integers.test
BobTheBuidler Aug 19, 2025
71c30a0
msc
BobTheBuidler Aug 19, 2025
37c6109
Merge branch 'bit-length' of https://github.com/BobTheBuidler/mypy in…
BobTheBuidler Aug 19, 2025
243cc9a
Update int_ops.c
BobTheBuidler Aug 20, 2025
3f55d82
Merge branch 'master' into bit-length
BobTheBuidler Aug 23, 2025
06fd2b2
lets see if the test works if I use `Any` instead of `int`
BobTheBuidler Sep 8, 2025
f2db97a
Revert "lets see if the test works if I use `Any` instead of `int`"
BobTheBuidler Sep 8, 2025
ef46238
CPyTagged_AsObject
BobTheBuidler Sep 9, 2025
ef11a21
Update run-integers.test
BobTheBuidler Sep 9, 2025
76c2d0d
Update run-integers.test
BobTheBuidler Sep 9, 2025
8256fbb
Update run-integers.test
BobTheBuidler Sep 9, 2025
9e22723
Merge branch 'master' into bit-length
BobTheBuidler Sep 9, 2025
5b58d41
Merge branch 'master' into bit-length
BobTheBuidler Sep 13, 2025
fd26207
Merge branch 'master' into bit-length
BobTheBuidler Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ CPyTagged CPyTagged_Remainder_(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_BitwiseLongOp_(CPyTagged a, CPyTagged b, char op);
CPyTagged CPyTagged_Rshift_(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_Lshift_(CPyTagged left, CPyTagged right);
CPyTagged CPyTagged_BitLength(CPyTagged self);

PyObject *CPyTagged_Str(CPyTagged n);
CPyTagged CPyTagged_FromFloat(double f);
Expand Down
64 changes: 64 additions & 0 deletions mypyc/lib-rt/int_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include <Python.h>
#include "CPy.h"

#ifdef _MSC_VER
#include <intrin.h>
#endif

#ifndef _WIN32
// On 64-bit Linux and macOS, ssize_t and long are both 64 bits, and
// PyLong_FromLong is faster than PyLong_FromSsize_t, so use the faster one
Expand All @@ -15,6 +19,17 @@
#define CPyLong_FromSsize_t PyLong_FromSsize_t
#endif

#if defined(__GNUC__) || defined(__clang__)
# if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8)
# define CPY_CLZ(x) __builtin_clzll((unsigned long long)(x))
# define CPY_BITS 64
# else
# define CPY_CLZ(x) __builtin_clz((unsigned int)(x))
# define CPY_BITS 32
# endif
#endif


CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value) {
// We use a Python object if the value shifted left by 1 is too
// large for Py_ssize_t
Expand Down Expand Up @@ -581,3 +596,52 @@ double CPyTagged_TrueDivide(CPyTagged x, CPyTagged y) {
}
return 1.0;
}

// int.bit_length()
CPyTagged CPyTagged_BitLength(CPyTagged self) {
// Handle zero
if (self == 0) {
return 0;
}

// Fast path for small (tagged) ints
if (CPyTagged_CheckShort(self)) {
Py_ssize_t val = CPyTagged_ShortAsSsize_t(self);
Py_ssize_t absval = val < 0 ? -val : val;
int bits = 0;
if (absval) {
#if defined(_MSC_VER)
#if defined(_WIN64)
unsigned long idx;
if (_BitScanReverse64(&idx, (unsigned __int64)absval)) {
bits = (int)(idx + 1);
}
#else
unsigned long idx;
if (_BitScanReverse(&idx, (unsigned long)absval)) {
bits = (int)(idx + 1);
}
#endif
#elif defined(__GNUC__) || defined(__clang__)
bits = (int)(CPY_BITS - CPY_CLZ(absval));
#else
// Fallback to loop if no builtin
while (absval) {
absval >>= 1;
bits++;
}
#endif
}
return bits << 1;
}

// Slow path for big ints
PyObject *pyint = CPyTagged_AsObject(self);
int bits = _PyLong_NumBits(pyint);
Py_DECREF(pyint);
if (bits < 0) {
// _PyLong_NumBits sets an error on failure
return CPY_INT_TAG;
}
return bits << 1;
}
18 changes: 17 additions & 1 deletion mypyc/primitives/int_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@
str_rprimitive,
void_rtype,
)
from mypyc.primitives.registry import binary_op, custom_op, function_op, load_address_op, unary_op
from mypyc.primitives.registry import (
binary_op,
custom_op,
function_op,
load_address_op,
method_op,
unary_op,
)

# Constructors for builtins.int and native int types have the same behavior. In
# interpreted mode, native int types are just aliases to 'int'.
Expand Down Expand Up @@ -305,3 +312,12 @@ def int_unary_op(name: str, c_function_name: str) -> PrimitiveDescription:
c_function_name="PyLong_Check",
error_kind=ERR_NEVER,
)

# int.bit_length()
method_op(
name="bit_length",
arg_types=[int_rprimitive],
return_type=int_rprimitive,
c_function_name="CPyTagged_BitLength",
error_kind=ERR_MAGIC,
)
1 change: 1 addition & 0 deletions mypyc/test-data/fixtures/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def __lt__(self, n: int) -> bool: pass
def __gt__(self, n: int) -> bool: pass
def __le__(self, n: int) -> bool: pass
def __ge__(self, n: int) -> bool: pass
def bit_length(self) -> int: pass

class str:
@overload
Expand Down
10 changes: 10 additions & 0 deletions mypyc/test-data/irbuild-int.test
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,13 @@ L0:
r0 = CPyTagged_Invert(n)
x = r0
return x

[case testIntBitLength]
def f(x: int) -> int:
return x.bit_length()
[out]
def f(x):
x, r0 :: int
L0:
r0 = CPyTagged_BitLength(x)
return r0
14 changes: 14 additions & 0 deletions mypyc/test-data/run-integers.test
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,17 @@ class subc(int):
[file userdefinedint.py]
class int:
pass

[case testBitLength]
def bit_length(n: int) -> int:
return n.bit_length()
def bit_length_python(n: int) -> int:
return getattr(n, "bit_length")()
def test_bit_length() -> None:
for n in range(256):
i = 1 << n
assert bit_length(i) == bit_length_python(i)
assert bit_length(-(i)) == bit_length_python(-(i))
i -= 1
assert bit_length(i) == bit_length_python(i)
assert bit_length(-(i)) == bit_length_python(-(i))
Loading