Skip to content

Commit 2d76be2

Browse files
authored
gh-111962: Make dtoa thread-safe in --disable-gil builds. (#112049)
This updates `dtoa.c` to avoid using the Bigint free-list in --disable-gil builds and to pre-computes the needed powers of 5 during interpreter initialization. * gh-111962: Make dtoa thread-safe in `--disable-gil` builds. This avoids using the Bigint free-list in `--disable-gil` builds and pre-computes the needed powers of 5 during interpreter initialization. * Fix size of cached powers of 5 array. We need the powers of 5 up to 5**512 because we only jump straight to underflow when the exponent is less than -512 (or larger than 308). * Rename Py_NOGIL to Py_GIL_DISABLED * Changes from review * Fix assertion placement
1 parent 9f67042 commit 2d76be2

File tree

3 files changed

+70
-30
lines changed

3 files changed

+70
-30
lines changed

Include/internal/pycore_dtoa.h

+11-5
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,20 @@ struct _dtoa_state {
3535
/* The size of the Bigint freelist */
3636
#define Bigint_Kmax 7
3737

38+
/* The size of the cached powers of 5 array */
39+
#define Bigint_Pow5size 8
40+
3841
#ifndef PRIVATE_MEM
3942
#define PRIVATE_MEM 2304
4043
#endif
4144
#define Bigint_PREALLOC_SIZE \
4245
((PRIVATE_MEM+sizeof(double)-1)/sizeof(double))
4346

4447
struct _dtoa_state {
45-
/* p5s is a linked list of powers of 5 of the form 5**(2**i), i >= 2 */
48+
// p5s is an array of powers of 5 of the form:
49+
// 5**(2**(i+2)) for 0 <= i < Bigint_Pow5size
50+
struct Bigint *p5s[Bigint_Pow5size];
4651
// XXX This should be freed during runtime fini.
47-
struct Bigint *p5s;
4852
struct Bigint *freelist[Bigint_Kmax+1];
4953
double preallocated[Bigint_PREALLOC_SIZE];
5054
double *preallocated_next;
@@ -57,16 +61,18 @@ struct _dtoa_state {
5761
#endif // !Py_USING_MEMORY_DEBUGGER
5862

5963

60-
/* These functions are used by modules compiled as C extension like math:
61-
they must be exported. */
62-
6364
extern double _Py_dg_strtod(const char *str, char **ptr);
6465
extern char* _Py_dg_dtoa(double d, int mode, int ndigits,
6566
int *decpt, int *sign, char **rve);
6667
extern void _Py_dg_freedtoa(char *s);
6768

6869
#endif // _PY_SHORT_FLOAT_REPR == 1
6970

71+
72+
extern PyStatus _PyDtoa_Init(PyInterpreterState *interp);
73+
extern void _PyDtoa_Fini(PyInterpreterState *interp);
74+
75+
7076
#ifdef __cplusplus
7177
}
7278
#endif

Python/dtoa.c

+53-25
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ BCinfo {
309309
// struct Bigint is defined in pycore_dtoa.h.
310310
typedef struct Bigint Bigint;
311311

312-
#ifndef Py_USING_MEMORY_DEBUGGER
312+
#if !defined(Py_GIL_DISABLED) && !defined(Py_USING_MEMORY_DEBUGGER)
313313

314314
/* Memory management: memory is allocated from, and returned to, Kmax+1 pools
315315
of memory, where pool k (0 <= k <= Kmax) is for Bigints b with b->maxwds ==
@@ -428,7 +428,7 @@ Bfree(Bigint *v)
428428
}
429429
}
430430

431-
#endif /* Py_USING_MEMORY_DEBUGGER */
431+
#endif /* !defined(Py_GIL_DISABLED) && !defined(Py_USING_MEMORY_DEBUGGER) */
432432

433433
#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \
434434
y->wds*sizeof(Long) + 2*sizeof(int))
@@ -673,10 +673,17 @@ mult(Bigint *a, Bigint *b)
673673
static Bigint *
674674
pow5mult(Bigint *b, int k)
675675
{
676-
Bigint *b1, *p5, *p51;
676+
Bigint *b1, *p5, **p5s;
677677
int i;
678678
static const int p05[3] = { 5, 25, 125 };
679679

680+
// For double-to-string conversion, the maximum value of k is limited by
681+
// DBL_MAX_10_EXP (308), the maximum decimal base-10 exponent for binary64.
682+
// For string-to-double conversion, the extreme case is constrained by our
683+
// hardcoded exponent limit before we underflow of -512, adjusted by
684+
// STRTOD_DIGLIM-DBL_DIG-1, giving a maximum of k=535.
685+
assert(0 <= k && k < 1024);
686+
680687
if ((i = k & 3)) {
681688
b = multadd(b, p05[i-1], 0);
682689
if (b == NULL)
@@ -686,18 +693,11 @@ pow5mult(Bigint *b, int k)
686693
if (!(k >>= 2))
687694
return b;
688695
PyInterpreterState *interp = _PyInterpreterState_GET();
689-
p5 = interp->dtoa.p5s;
690-
if (!p5) {
691-
/* first time */
692-
p5 = i2b(625);
693-
if (p5 == NULL) {
694-
Bfree(b);
695-
return NULL;
696-
}
697-
interp->dtoa.p5s = p5;
698-
p5->next = 0;
699-
}
696+
p5s = interp->dtoa.p5s;
700697
for(;;) {
698+
assert(p5s != interp->dtoa.p5s + Bigint_Pow5size);
699+
p5 = *p5s;
700+
p5s++;
701701
if (k & 1) {
702702
b1 = mult(b, p5);
703703
Bfree(b);
@@ -707,17 +707,6 @@ pow5mult(Bigint *b, int k)
707707
}
708708
if (!(k >>= 1))
709709
break;
710-
p51 = p5->next;
711-
if (!p51) {
712-
p51 = mult(p5,p5);
713-
if (p51 == NULL) {
714-
Bfree(b);
715-
return NULL;
716-
}
717-
p51->next = 0;
718-
p5->next = p51;
719-
}
720-
p5 = p51;
721710
}
722711
return b;
723712
}
@@ -2811,3 +2800,42 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
28112800
}
28122801

28132802
#endif // _PY_SHORT_FLOAT_REPR == 1
2803+
2804+
PyStatus
2805+
_PyDtoa_Init(PyInterpreterState *interp)
2806+
{
2807+
#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER)
2808+
Bigint **p5s = interp->dtoa.p5s;
2809+
2810+
// 5**4 = 625
2811+
Bigint *p5 = i2b(625);
2812+
if (p5 == NULL) {
2813+
return PyStatus_NoMemory();
2814+
}
2815+
p5s[0] = p5;
2816+
2817+
// compute 5**8, 5**16, 5**32, ..., 5**512
2818+
for (Py_ssize_t i = 1; i < Bigint_Pow5size; i++) {
2819+
p5 = mult(p5, p5);
2820+
if (p5 == NULL) {
2821+
return PyStatus_NoMemory();
2822+
}
2823+
p5s[i] = p5;
2824+
}
2825+
2826+
#endif
2827+
return PyStatus_Ok();
2828+
}
2829+
2830+
void
2831+
_PyDtoa_Fini(PyInterpreterState *interp)
2832+
{
2833+
#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER)
2834+
Bigint **p5s = interp->dtoa.p5s;
2835+
for (Py_ssize_t i = 0; i < Bigint_Pow5size; i++) {
2836+
Bigint *p5 = p5s[i];
2837+
p5s[i] = NULL;
2838+
Bfree(p5);
2839+
}
2840+
#endif
2841+
}

Python/pylifecycle.c

+6
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,11 @@ pycore_interp_init(PyThreadState *tstate)
820820
return status;
821821
}
822822

823+
status = _PyDtoa_Init(interp);
824+
if (_PyStatus_EXCEPTION(status)) {
825+
return status;
826+
}
827+
823828
// The GC must be initialized before the first GC collection.
824829
status = _PyGC_Init(interp);
825830
if (_PyStatus_EXCEPTION(status)) {
@@ -1776,6 +1781,7 @@ finalize_interp_clear(PyThreadState *tstate)
17761781
_PyXI_Fini(tstate->interp);
17771782
_PyExc_ClearExceptionGroupType(tstate->interp);
17781783
_Py_clear_generic_types(tstate->interp);
1784+
_PyDtoa_Fini(tstate->interp);
17791785

17801786
/* Clear interpreter state and all thread states */
17811787
_PyInterpreterState_Clear(tstate);

0 commit comments

Comments
 (0)