Skip to content

Commit 5ef22ab

Browse files
committed
pythongh-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. This makes two changes to dtoa
1 parent babb787 commit 5ef22ab

File tree

3 files changed

+62
-30
lines changed

3 files changed

+62
-30
lines changed

Include/internal/pycore_dtoa.h

+10-5
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,19 @@ 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_Pow5max 7
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 5**(2**i), i >= 2 */
49+
struct Bigint *p5s[Bigint_Pow5max];
4650
// XXX This should be freed during runtime fini.
47-
struct Bigint *p5s;
4851
struct Bigint *freelist[Bigint_Kmax+1];
4952
double preallocated[Bigint_PREALLOC_SIZE];
5053
double *preallocated_next;
@@ -57,16 +60,18 @@ struct _dtoa_state {
5760
#endif // !Py_USING_MEMORY_DEBUGGER
5861

5962

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

6868
#endif // _PY_SHORT_FLOAT_REPR == 1
6969

70+
71+
extern PyStatus _PyDtoa_Init(PyInterpreterState *interp);
72+
extern void _PyDtoa_Fini(PyInterpreterState *interp);
73+
74+
7075
#ifdef __cplusplus
7176
}
7277
#endif

Python/dtoa.c

+46-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_NOGIL) && !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_NOGIL) && !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,7 +673,7 @@ 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

@@ -685,19 +685,12 @@ pow5mult(Bigint *b, int k)
685685

686686
if (!(k >>= 2))
687687
return b;
688+
assert(k < (1 << (Bigint_Pow5max)));
688689
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-
}
690+
p5s = interp->dtoa.p5s;
700691
for(;;) {
692+
p5 = *p5s;
693+
p5s++;
701694
if (k & 1) {
702695
b1 = mult(b, p5);
703696
Bfree(b);
@@ -707,17 +700,6 @@ pow5mult(Bigint *b, int k)
707700
}
708701
if (!(k >>= 1))
709702
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;
721703
}
722704
return b;
723705
}
@@ -2811,3 +2793,42 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
28112793
}
28122794

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

Python/pylifecycle.c

+6
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,11 @@ pycore_interp_init(PyThreadState *tstate)
825825
return status;
826826
}
827827

828+
status = _PyDtoa_Init(interp);
829+
if (_PyStatus_EXCEPTION(status)) {
830+
return status;
831+
}
832+
828833
// The GC must be initialized before the first GC collection.
829834
status = _PyGC_Init(interp);
830835
if (_PyStatus_EXCEPTION(status)) {
@@ -1781,6 +1786,7 @@ finalize_interp_clear(PyThreadState *tstate)
17811786
_PyXI_Fini(tstate->interp);
17821787
_PyExc_ClearExceptionGroupType(tstate->interp);
17831788
_Py_clear_generic_types(tstate->interp);
1789+
_PyDtoa_Fini(tstate->interp);
17841790

17851791
/* Clear interpreter state and all thread states */
17861792
_PyInterpreterState_Clear(tstate);

0 commit comments

Comments
 (0)