Skip to content

Commit 794e7d1

Browse files
authored
bpo-29782: Consolidate _Py_Bit_Length() (GH-20739)
In GH-2866, _Py_Bit_Length() was added to pymath.h for lack of a better location. GH-20518 added a more appropriate header file for bit utilities. It also shows how to properly use intrinsics. This allows reconsidering bpo-29782. * Move the function to the new header. * Changed return type to match __builtin_clzl() and reviewed usage. * Use intrinsics where available. * Pick a fallback implementation suitable for inlining.
1 parent 25f38d7 commit 794e7d1

File tree

6 files changed

+100
-36
lines changed

6 files changed

+100
-36
lines changed

Include/internal/pycore_bitutils.h

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
- _Py_bswap64(uint64_t)
88
*/
99

10-
#ifndef Py_INTERNAL_BSWAP_H
11-
#define Py_INTERNAL_BSWAP_H
10+
#ifndef Py_INTERNAL_BITUTILS_H
11+
#define Py_INTERNAL_BITUTILS_H
1212
#ifdef __cplusplus
1313
extern "C" {
1414
#endif
@@ -131,8 +131,47 @@ _Py_popcount32(uint32_t x)
131131
}
132132

133133

134+
// Return the index of the most significant 1 bit in 'x'. This is the smallest
135+
// integer k such that x < 2**k. Equivalent to floor(log2(x)) + 1 for x != 0.
136+
static inline int
137+
_Py_bit_length(unsigned long x)
138+
{
139+
#if (defined(__clang__) || defined(__GNUC__))
140+
if (x != 0) {
141+
// __builtin_clzl() is available since GCC 3.4.
142+
// Undefined behavior for x == 0.
143+
return (int)sizeof(unsigned long) * 8 - __builtin_clzl(x);
144+
}
145+
else {
146+
return 0;
147+
}
148+
#elif defined(_MSC_VER)
149+
// _BitScanReverse() is documented to search 32 bits.
150+
Py_BUILD_ASSERT(sizeof(unsigned long) <= 4);
151+
unsigned long msb;
152+
if (_BitScanReverse(&msb, x)) {
153+
return (int)msb + 1;
154+
}
155+
else {
156+
return 0;
157+
}
158+
#else
159+
const int BIT_LENGTH_TABLE[32] = {
160+
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
161+
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
162+
};
163+
int msb = 0;
164+
while (x >= 32) {
165+
msb += 6;
166+
x >>= 6;
167+
}
168+
msb += BIT_LENGTH_TABLE[x];
169+
return msb;
170+
#endif
171+
}
172+
173+
134174
#ifdef __cplusplus
135175
}
136176
#endif
137-
#endif /* !Py_INTERNAL_BSWAP_H */
138-
177+
#endif /* !Py_INTERNAL_BITUTILS_H */

Include/pymath.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,4 @@ PyAPI_FUNC(void) _Py_set_387controlword(unsigned short);
227227
* behavior. */
228228
#define _Py_InIntegralTypeRange(type, v) (_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type))
229229

230-
/* Return the smallest integer k such that n < 2**k, or 0 if n == 0.
231-
* Equivalent to floor(log2(x))+1. Also equivalent to: bitwidth_of_type -
232-
* count_leading_zero_bits(x)
233-
*/
234-
#ifndef Py_LIMITED_API
235-
PyAPI_FUNC(unsigned int) _Py_bit_length(unsigned long d);
236-
#endif
237-
238230
#endif /* Py_PYMATH_H */

Modules/_testinternalcapi.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,45 @@ test_popcount(PyObject *self, PyObject *Py_UNUSED(args))
102102
}
103103

104104

105+
static int
106+
check_bit_length(unsigned long x, int expected)
107+
{
108+
// Use volatile to prevent the compiler to optimize out the whole test
109+
volatile unsigned long u = x;
110+
int len = _Py_bit_length(u);
111+
if (len != expected) {
112+
PyErr_Format(PyExc_AssertionError,
113+
"_Py_bit_length(%lu) returns %i, expected %i",
114+
x, len, expected);
115+
return -1;
116+
}
117+
return 0;
118+
}
119+
120+
121+
static PyObject*
122+
test_bit_length(PyObject *self, PyObject *Py_UNUSED(args))
123+
{
124+
#define CHECK(X, RESULT) \
125+
do { \
126+
if (check_bit_length(X, RESULT) < 0) { \
127+
return NULL; \
128+
} \
129+
} while (0)
130+
131+
CHECK(0, 0);
132+
CHECK(1, 1);
133+
CHECK(0x1000, 13);
134+
CHECK(0x1234, 13);
135+
CHECK(0x54321, 19);
136+
CHECK(0x7FFFFFFF, 31);
137+
CHECK(0xFFFFFFFF, 32);
138+
Py_RETURN_NONE;
139+
140+
#undef CHECK
141+
}
142+
143+
105144
#define TO_PTR(ch) ((void*)(uintptr_t)ch)
106145
#define FROM_PTR(ptr) ((uintptr_t)ptr)
107146
#define VALUE(key) (1 + ((int)(key) - 'a'))
@@ -197,6 +236,7 @@ static PyMethodDef TestMethods[] = {
197236
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
198237
{"test_bswap", test_bswap, METH_NOARGS},
199238
{"test_popcount", test_popcount, METH_NOARGS},
239+
{"test_bit_length", test_bit_length, METH_NOARGS},
200240
{"test_hashtable", test_hashtable, METH_NOARGS},
201241
{NULL, NULL} /* sentinel */
202242
};

Modules/mathmodule.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ raised for division by zero and mod by zero.
5353
*/
5454

5555
#include "Python.h"
56+
#include "pycore_bitutils.h" // _Py_bit_length()
5657
#include "pycore_dtoa.h"
5758
#include "_math.h"
5859

Objects/longobject.c

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,13 @@ _PyLong_Sign(PyObject *vv)
695695
return Py_SIZE(v) == 0 ? 0 : (Py_SIZE(v) < 0 ? -1 : 1);
696696
}
697697

698+
static int
699+
bit_length_digit(digit x)
700+
{
701+
Py_BUILD_ASSERT(PyLong_SHIFT <= sizeof(unsigned long) * 8);
702+
return _Py_bit_length((unsigned long)x);
703+
}
704+
698705
size_t
699706
_PyLong_NumBits(PyObject *vv)
700707
{
@@ -712,7 +719,7 @@ _PyLong_NumBits(PyObject *vv)
712719
if ((size_t)(ndigits - 1) > SIZE_MAX / (size_t)PyLong_SHIFT)
713720
goto Overflow;
714721
result = (size_t)(ndigits - 1) * (size_t)PyLong_SHIFT;
715-
msd_bits = _Py_bit_length(msd);
722+
msd_bits = bit_length_digit(msd);
716723
if (SIZE_MAX - msd_bits < result)
717724
goto Overflow;
718725
result += msd_bits;
@@ -1822,7 +1829,7 @@ long_format_binary(PyObject *aa, int base, int alternate,
18221829
return -1;
18231830
}
18241831
size_a_in_bits = (size_a - 1) * PyLong_SHIFT +
1825-
_Py_bit_length(a->ob_digit[size_a - 1]);
1832+
bit_length_digit(a->ob_digit[size_a - 1]);
18261833
/* Allow 1 character for a '-' sign. */
18271834
sz = negative + (size_a_in_bits + (bits - 1)) / bits;
18281835
}
@@ -2642,7 +2649,7 @@ x_divrem(PyLongObject *v1, PyLongObject *w1, PyLongObject **prem)
26422649

26432650
/* normalize: shift w1 left so that its top digit is >= PyLong_BASE/2.
26442651
shift v1 left by the same amount. Results go into w and v. */
2645-
d = PyLong_SHIFT - _Py_bit_length(w1->ob_digit[size_w-1]);
2652+
d = PyLong_SHIFT - bit_length_digit(w1->ob_digit[size_w-1]);
26462653
carry = v_lshift(w->ob_digit, w1->ob_digit, size_w, d);
26472654
assert(carry == 0);
26482655
carry = v_lshift(v->ob_digit, v1->ob_digit, size_v, d);
@@ -2764,7 +2771,7 @@ _PyLong_Frexp(PyLongObject *a, Py_ssize_t *e)
27642771
*e = 0;
27652772
return 0.0;
27662773
}
2767-
a_bits = _Py_bit_length(a->ob_digit[a_size-1]);
2774+
a_bits = bit_length_digit(a->ob_digit[a_size-1]);
27682775
/* The following is an overflow-free version of the check
27692776
"if ((a_size - 1) * PyLong_SHIFT + a_bits > PY_SSIZE_T_MAX) ..." */
27702777
if (a_size >= (PY_SSIZE_T_MAX - 1) / PyLong_SHIFT + 1 &&
@@ -3857,8 +3864,8 @@ long_true_divide(PyObject *v, PyObject *w)
38573864
/* Extreme underflow */
38583865
goto underflow_or_zero;
38593866
/* Next line is now safe from overflowing a Py_ssize_t */
3860-
diff = diff * PyLong_SHIFT + _Py_bit_length(a->ob_digit[a_size - 1]) -
3861-
_Py_bit_length(b->ob_digit[b_size - 1]);
3867+
diff = diff * PyLong_SHIFT + bit_length_digit(a->ob_digit[a_size - 1]) -
3868+
bit_length_digit(b->ob_digit[b_size - 1]);
38623869
/* Now diff = a_bits - b_bits. */
38633870
if (diff > DBL_MAX_EXP)
38643871
goto overflow;
@@ -3934,7 +3941,7 @@ long_true_divide(PyObject *v, PyObject *w)
39343941
}
39353942
x_size = Py_ABS(Py_SIZE(x));
39363943
assert(x_size > 0); /* result of division is never zero */
3937-
x_bits = (x_size-1)*PyLong_SHIFT+_Py_bit_length(x->ob_digit[x_size-1]);
3944+
x_bits = (x_size-1)*PyLong_SHIFT+bit_length_digit(x->ob_digit[x_size-1]);
39383945

39393946
/* The number of extra bits that have to be rounded away. */
39403947
extra_bits = Py_MAX(x_bits, DBL_MIN_EXP - shift) - DBL_MANT_DIG;
@@ -4748,7 +4755,7 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg)
47484755
alloc_b = Py_SIZE(b);
47494756
/* reduce until a fits into 2 digits */
47504757
while ((size_a = Py_SIZE(a)) > 2) {
4751-
nbits = _Py_bit_length(a->ob_digit[size_a-1]);
4758+
nbits = bit_length_digit(a->ob_digit[size_a-1]);
47524759
/* extract top 2*PyLong_SHIFT bits of a into x, along with
47534760
corresponding bits of b into y */
47544761
size_b = Py_SIZE(b);
@@ -5269,7 +5276,7 @@ int_bit_length_impl(PyObject *self)
52695276
return PyLong_FromLong(0);
52705277

52715278
msd = ((PyLongObject *)self)->ob_digit[ndigits-1];
5272-
msd_bits = _Py_bit_length(msd);
5279+
msd_bits = bit_length_digit(msd);
52735280

52745281
if (ndigits <= PY_SSIZE_T_MAX/PyLong_SHIFT)
52755282
return PyLong_FromSsize_t((ndigits-1)*PyLong_SHIFT + msd_bits);

Python/pymath.c

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,3 @@ round(double x)
7979
return copysign(y, x);
8080
}
8181
#endif /* HAVE_ROUND */
82-
83-
static const unsigned int BitLengthTable[32] = {
84-
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
85-
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
86-
};
87-
88-
unsigned int _Py_bit_length(unsigned long d) {
89-
unsigned int d_bits = 0;
90-
while (d >= 32) {
91-
d_bits += 6;
92-
d >>= 6;
93-
}
94-
d_bits += BitLengthTable[d];
95-
return d_bits;
96-
}

0 commit comments

Comments
 (0)