Skip to content

Commit 813bc56

Browse files
authored
gh-130599: use static constants str-to-int conversion (gh-130714)
Avoid a data race in free-threaded builds due to mutating global arrays at runtime. Instead, compute the constants with an external Python script and then define them as static global constant arrays. These constants are used by `long_from_non_binary_base()`.
1 parent bbf1979 commit 813bc56

File tree

3 files changed

+133
-25
lines changed

3 files changed

+133
-25
lines changed

Objects/longobject.c

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,58 @@ that triggers it(!). Instead the code was tested by artificially allocating
28202820
just 1 digit at the start, so that the copying code was exercised for every
28212821
digit beyond the first.
28222822
***/
2823+
2824+
// Tables are computed by Tools/scripts/long_conv_tables.py
2825+
#if PYLONG_BITS_IN_DIGIT == 15
2826+
static const double log_base_BASE[37] = {0.0, 0.0, 0.0,
2827+
0.10566416671474375, 0.0, 0.15479520632582416,
2828+
0.17233083338141042, 0.18715699480384027, 0.0,
2829+
0.2113283334294875, 0.22146187299249084, 0.23062877457581984,
2830+
0.2389975000480771, 0.24669598120940617, 0.25382366147050694,
2831+
0.26045937304056793, 0.0, 0.27249752275002265,
2832+
0.27799500009615413, 0.2831951675629057, 0.28812853965915747,
2833+
0.29282116151858406, 0.2972954412424865, 0.3015707970704675,
2834+
0.3056641667147438, 0.30959041265164833, 0.3133626478760728,
2835+
0.31699250014423125, 0.3204903281371736, 0.3238653996751715,
2836+
0.3271260397072346, 0.3302797540257917, 0.0,
2837+
0.3362929412905636, 0.3391641894166893, 0.34195220112966446,
2838+
0.34466166676282084};
2839+
static const int convwidth_base[37] = {0, 0, 0, 9, 0, 6, 5, 5, 0,
2840+
4, 4, 4, 4, 4, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
2841+
3, 3, 0, 2, 2, 2, 2};
2842+
static const twodigits convmultmax_base[37] = {0, 0, 0, 19683, 0,
2843+
15625, 7776, 16807, 0, 6561, 10000, 14641, 20736, 28561, 2744,
2844+
3375, 0, 4913, 5832, 6859, 8000, 9261, 10648, 12167, 13824,
2845+
15625, 17576, 19683, 21952, 24389, 27000, 29791, 0, 1089,
2846+
1156, 1225, 1296};
2847+
#elif PYLONG_BITS_IN_DIGIT == 30
2848+
static const double log_base_BASE[37] = {0.0, 0.0, 0.0,
2849+
0.05283208335737188, 0.0, 0.07739760316291208,
2850+
0.08616541669070521, 0.09357849740192013, 0.0,
2851+
0.10566416671474375, 0.11073093649624542, 0.11531438728790992,
2852+
0.11949875002403855, 0.12334799060470308, 0.12691183073525347,
2853+
0.13022968652028397, 0.0, 0.13624876137501132,
2854+
0.13899750004807707, 0.14159758378145285, 0.14406426982957873,
2855+
0.14641058075929203, 0.14864772062124326, 0.15078539853523376,
2856+
0.1528320833573719, 0.15479520632582416, 0.1566813239380364,
2857+
0.15849625007211562, 0.1602451640685868, 0.16193269983758574,
2858+
0.1635630198536173, 0.16513987701289584, 0.0,
2859+
0.1681464706452818, 0.16958209470834465, 0.17097610056483223,
2860+
0.17233083338141042};
2861+
static const int convwidth_base[37] = {0, 0, 0, 18, 0, 12, 11, 10,
2862+
0, 9, 9, 8, 8, 8, 7, 7, 0, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6,
2863+
6, 6, 6, 0, 5, 5, 5, 5};
2864+
static const twodigits convmultmax_base[37] = {0, 0, 0, 387420489,
2865+
0, 244140625, 362797056, 282475249, 0, 387420489, 1000000000,
2866+
214358881, 429981696, 815730721, 105413504, 170859375, 0,
2867+
410338673, 612220032, 893871739, 64000000, 85766121,
2868+
113379904, 148035889, 191102976, 244140625, 308915776,
2869+
387420489, 481890304, 594823321, 729000000, 887503681, 0,
2870+
39135393, 45435424, 52521875, 60466176};
2871+
#else
2872+
#error "invalid PYLONG_BITS_IN_DIGIT value"
2873+
#endif
2874+
28232875
static int
28242876
long_from_non_binary_base(const char *start, const char *end, Py_ssize_t digits, int base, PyLongObject **res)
28252877
{
@@ -2832,28 +2884,7 @@ long_from_non_binary_base(const char *start, const char *end, Py_ssize_t digits,
28322884
PyLongObject *z;
28332885
const char *p;
28342886

2835-
static double log_base_BASE[37] = {0.0e0,};
2836-
static int convwidth_base[37] = {0,};
2837-
static twodigits convmultmax_base[37] = {0,};
2838-
2839-
if (log_base_BASE[base] == 0.0) {
2840-
twodigits convmax = base;
2841-
int i = 1;
2842-
2843-
log_base_BASE[base] = (log((double)base) /
2844-
log((double)PyLong_BASE));
2845-
for (;;) {
2846-
twodigits next = convmax * base;
2847-
if (next > PyLong_BASE) {
2848-
break;
2849-
}
2850-
convmax = next;
2851-
++i;
2852-
}
2853-
convmultmax_base[base] = convmax;
2854-
assert(i > 0);
2855-
convwidth_base[base] = i;
2856-
}
2887+
assert(log_base_BASE[base] != 0.0);
28572888

28582889
/* Create an int object that can contain the largest possible
28592890
* integer with this base and length. Note that there's no

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ Python/thread_pthread.h PyThread__init_thread lib_initialized -
2828
##-----------------------
2929
## other values (not Python-specific)
3030

31+
# static tables computed by external script
32+
Objects/longobject.c - log_base_BASE -
33+
Objects/longobject.c - convwidth_base -
34+
Objects/longobject.c - convmultmax_base -
35+
3136
## cached computed data - set lazily (*after* first init)
3237
# XXX Are these safe relative to write races?
33-
Objects/longobject.c long_from_non_binary_base log_base_BASE -
34-
Objects/longobject.c long_from_non_binary_base convwidth_base -
35-
Objects/longobject.c long_from_non_binary_base convmultmax_base -
3638
Objects/unicodeobject.c - bloom_linebreak -
3739
# This is safe:
3840
Objects/unicodeobject.c _init_global_state initialized -

Tools/scripts/long_conv_tables.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Compute tables for longobject.c long_from_non_binary_base(). They are used
4+
# for conversions of strings to integers with a non-binary base.
5+
6+
import math
7+
import textwrap
8+
9+
10+
def format_array(name, values):
11+
values = [str(v) for v in values]
12+
values = ', '.join(values)
13+
result = f'{name} = {{{values}}};'
14+
result = textwrap.wrap(
15+
result,
16+
initial_indent=' ' * 4,
17+
subsequent_indent=' ' * 8,
18+
)
19+
return '\n'.join(result)
20+
21+
22+
def conv_tables(long_bits):
23+
PyLong_BASE = 1 << long_bits
24+
log_base_BASE = [0.0] * 37
25+
convmultmax_base = [0] * 37
26+
convwidth_base = [0] * 37
27+
for base in range(2, 37):
28+
is_binary_base = (base & (base - 1)) == 0
29+
if is_binary_base:
30+
continue # don't need, leave as zero
31+
convmax = base
32+
i = 1
33+
log_base_BASE[base] = math.log(base) / math.log(PyLong_BASE)
34+
while True:
35+
next = convmax * base
36+
if next > PyLong_BASE:
37+
break
38+
convmax = next
39+
i += 1
40+
convmultmax_base[base] = convmax
41+
assert i > 0
42+
convwidth_base[base] = i
43+
return '\n'.join(
44+
[
45+
format_array(
46+
'static const double log_base_BASE[37]', log_base_BASE
47+
),
48+
format_array(
49+
'static const int convwidth_base[37]', convwidth_base
50+
),
51+
format_array(
52+
'static const twodigits convmultmax_base[37]',
53+
convmultmax_base,
54+
),
55+
]
56+
)
57+
58+
59+
def main():
60+
print(
61+
f'''\
62+
// Tables are computed by Tools/scripts/long_conv_tables.py
63+
#if PYLONG_BITS_IN_DIGIT == 15
64+
{conv_tables(15)}
65+
#elif PYLONG_BITS_IN_DIGIT == 30
66+
{conv_tables(30)}
67+
#else
68+
#error "invalid PYLONG_BITS_IN_DIGIT value"
69+
#endif
70+
'''
71+
)
72+
73+
74+
if __name__ == '__main__':
75+
main()

0 commit comments

Comments
 (0)