Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8e57622

Browse files
[mypyc] feat: new primitive for int.bit_length (#19673)
This PR adds a new primitive for `int.bit_length`.
1 parent b3e26e7 commit 8e57622

File tree

6 files changed

+107
-1
lines changed

6 files changed

+107
-1
lines changed

mypyc/lib-rt/CPy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ CPyTagged CPyTagged_Remainder_(CPyTagged left, CPyTagged right);
148148
CPyTagged CPyTagged_BitwiseLongOp_(CPyTagged a, CPyTagged b, char op);
149149
CPyTagged CPyTagged_Rshift_(CPyTagged left, CPyTagged right);
150150
CPyTagged CPyTagged_Lshift_(CPyTagged left, CPyTagged right);
151+
CPyTagged CPyTagged_BitLength(CPyTagged self);
151152

152153
PyObject *CPyTagged_Str(CPyTagged n);
153154
CPyTagged CPyTagged_FromFloat(double f);

mypyc/lib-rt/int_ops.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include <Python.h>
66
#include "CPy.h"
77

8+
#ifdef _MSC_VER
9+
#include <intrin.h>
10+
#endif
11+
812
#ifndef _WIN32
913
// On 64-bit Linux and macOS, ssize_t and long are both 64 bits, and
1014
// PyLong_FromLong is faster than PyLong_FromSsize_t, so use the faster one
@@ -15,6 +19,17 @@
1519
#define CPyLong_FromSsize_t PyLong_FromSsize_t
1620
#endif
1721

22+
#if defined(__GNUC__) || defined(__clang__)
23+
# if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || (defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8)
24+
# define CPY_CLZ(x) __builtin_clzll((unsigned long long)(x))
25+
# define CPY_BITS 64
26+
# else
27+
# define CPY_CLZ(x) __builtin_clz((unsigned int)(x))
28+
# define CPY_BITS 32
29+
# endif
30+
#endif
31+
32+
1833
CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value) {
1934
// We use a Python object if the value shifted left by 1 is too
2035
// large for Py_ssize_t
@@ -581,3 +596,52 @@ double CPyTagged_TrueDivide(CPyTagged x, CPyTagged y) {
581596
}
582597
return 1.0;
583598
}
599+
600+
// int.bit_length()
601+
CPyTagged CPyTagged_BitLength(CPyTagged self) {
602+
// Handle zero
603+
if (self == 0) {
604+
return 0;
605+
}
606+
607+
// Fast path for small (tagged) ints
608+
if (CPyTagged_CheckShort(self)) {
609+
Py_ssize_t val = CPyTagged_ShortAsSsize_t(self);
610+
Py_ssize_t absval = val < 0 ? -val : val;
611+
int bits = 0;
612+
if (absval) {
613+
#if defined(_MSC_VER)
614+
#if defined(_WIN64)
615+
unsigned long idx;
616+
if (_BitScanReverse64(&idx, (unsigned __int64)absval)) {
617+
bits = (int)(idx + 1);
618+
}
619+
#else
620+
unsigned long idx;
621+
if (_BitScanReverse(&idx, (unsigned long)absval)) {
622+
bits = (int)(idx + 1);
623+
}
624+
#endif
625+
#elif defined(__GNUC__) || defined(__clang__)
626+
bits = (int)(CPY_BITS - CPY_CLZ(absval));
627+
#else
628+
// Fallback to loop if no builtin
629+
while (absval) {
630+
absval >>= 1;
631+
bits++;
632+
}
633+
#endif
634+
}
635+
return bits << 1;
636+
}
637+
638+
// Slow path for big ints
639+
PyObject *pyint = CPyTagged_AsObject(self);
640+
int bits = _PyLong_NumBits(pyint);
641+
Py_DECREF(pyint);
642+
if (bits < 0) {
643+
// _PyLong_NumBits sets an error on failure
644+
return CPY_INT_TAG;
645+
}
646+
return bits << 1;
647+
}

mypyc/primitives/int_ops.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@
3131
str_rprimitive,
3232
void_rtype,
3333
)
34-
from mypyc.primitives.registry import binary_op, custom_op, function_op, load_address_op, unary_op
34+
from mypyc.primitives.registry import (
35+
binary_op,
36+
custom_op,
37+
function_op,
38+
load_address_op,
39+
method_op,
40+
unary_op,
41+
)
3542

3643
# Constructors for builtins.int and native int types have the same behavior. In
3744
# interpreted mode, native int types are just aliases to 'int'.
@@ -305,3 +312,12 @@ def int_unary_op(name: str, c_function_name: str) -> PrimitiveDescription:
305312
c_function_name="PyLong_Check",
306313
error_kind=ERR_NEVER,
307314
)
315+
316+
# int.bit_length()
317+
method_op(
318+
name="bit_length",
319+
arg_types=[int_rprimitive],
320+
return_type=int_rprimitive,
321+
c_function_name="CPyTagged_BitLength",
322+
error_kind=ERR_MAGIC,
323+
)

mypyc/test-data/fixtures/ir.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def __lt__(self, n: int) -> bool: pass
8686
def __gt__(self, n: int) -> bool: pass
8787
def __le__(self, n: int) -> bool: pass
8888
def __ge__(self, n: int) -> bool: pass
89+
def bit_length(self) -> int: pass
8990

9091
class str:
9192
@overload

mypyc/test-data/irbuild-int.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,13 @@ L0:
210210
r0 = CPyTagged_Invert(n)
211211
x = r0
212212
return x
213+
214+
[case testIntBitLength]
215+
def f(x: int) -> int:
216+
return x.bit_length()
217+
[out]
218+
def f(x):
219+
x, r0 :: int
220+
L0:
221+
r0 = CPyTagged_BitLength(x)
222+
return r0

mypyc/test-data/run-integers.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,3 +572,17 @@ class subc(int):
572572
[file userdefinedint.py]
573573
class int:
574574
pass
575+
576+
[case testBitLength]
577+
def bit_length(n: int) -> int:
578+
return n.bit_length()
579+
def bit_length_python(n: int) -> int:
580+
return getattr(n, "bit_length")()
581+
def test_bit_length() -> None:
582+
for n in range(256):
583+
i = 1 << n
584+
assert bit_length(i) == bit_length_python(i)
585+
assert bit_length(-(i)) == bit_length_python(-(i))
586+
i -= 1
587+
assert bit_length(i) == bit_length_python(i)
588+
assert bit_length(-(i)) == bit_length_python(-(i))

0 commit comments

Comments
 (0)