-
-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-111962: Make dtoa thread-safe in --disable-gil
builds.
#112049
Changes from 3 commits
d53c7f4
4de2fb8
893f264
4907b59
d9536e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,16 +35,19 @@ struct _dtoa_state { | |
/* The size of the Bigint freelist */ | ||
#define Bigint_Kmax 7 | ||
|
||
/* The size of the cached powers of 5 array */ | ||
#define Bigint_Pow5max 8 | ||
|
||
#ifndef PRIVATE_MEM | ||
#define PRIVATE_MEM 2304 | ||
#endif | ||
#define Bigint_PREALLOC_SIZE \ | ||
((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) | ||
|
||
struct _dtoa_state { | ||
/* p5s is a linked list of powers of 5 of the form 5**(2**i), i >= 2 */ | ||
/* p5s is an array of powers of 5 of the form 5**(2**i), i >= 2 */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Could we adjust this comment to be more explicit about the indexing - i.e., make it clear that |
||
struct Bigint *p5s[Bigint_Pow5max]; | ||
// XXX This should be freed during runtime fini. | ||
struct Bigint *p5s; | ||
struct Bigint *freelist[Bigint_Kmax+1]; | ||
double preallocated[Bigint_PREALLOC_SIZE]; | ||
double *preallocated_next; | ||
|
@@ -57,16 +60,18 @@ struct _dtoa_state { | |
#endif // !Py_USING_MEMORY_DEBUGGER | ||
|
||
|
||
/* These functions are used by modules compiled as C extension like math: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleted because the comment is out of date, presumably? 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, these comments were about why the below functions used |
||
they must be exported. */ | ||
|
||
extern double _Py_dg_strtod(const char *str, char **ptr); | ||
extern char* _Py_dg_dtoa(double d, int mode, int ndigits, | ||
int *decpt, int *sign, char **rve); | ||
extern void _Py_dg_freedtoa(char *s); | ||
|
||
#endif // _PY_SHORT_FLOAT_REPR == 1 | ||
|
||
|
||
extern PyStatus _PyDtoa_Init(PyInterpreterState *interp); | ||
extern void _PyDtoa_Fini(PyInterpreterState *interp); | ||
|
||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -309,7 +309,7 @@ BCinfo { | |
// struct Bigint is defined in pycore_dtoa.h. | ||
typedef struct Bigint Bigint; | ||
|
||
#ifndef Py_USING_MEMORY_DEBUGGER | ||
#if !defined(Py_GIL_DISABLED) && !defined(Py_USING_MEMORY_DEBUGGER) | ||
|
||
/* Memory management: memory is allocated from, and returned to, Kmax+1 pools | ||
of memory, where pool k (0 <= k <= Kmax) is for Bigints b with b->maxwds == | ||
|
@@ -428,7 +428,7 @@ Bfree(Bigint *v) | |
} | ||
} | ||
|
||
#endif /* Py_USING_MEMORY_DEBUGGER */ | ||
#endif /* !defined(Py_GIL_DISABLED) && !defined(Py_USING_MEMORY_DEBUGGER) */ | ||
|
||
#define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ | ||
y->wds*sizeof(Long) + 2*sizeof(int)) | ||
|
@@ -673,7 +673,7 @@ mult(Bigint *a, Bigint *b) | |
static Bigint * | ||
pow5mult(Bigint *b, int k) | ||
{ | ||
Bigint *b1, *p5, *p51; | ||
Bigint *b1, *p5, **p5s; | ||
int i; | ||
static const int p05[3] = { 5, 25, 125 }; | ||
|
||
|
@@ -685,19 +685,12 @@ pow5mult(Bigint *b, int k) | |
|
||
if (!(k >>= 2)) | ||
return b; | ||
assert(k < (1 << (Bigint_Pow5max))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This single line is by far the hardest to review, and reminds me yet again why I'd be delighted to see This says that I do believe that there's an upper limit on possible
And based on the above, 1023 certainly seems plausible as an upper bound for possible @colesbury Does the above roughly match your reasoning? Would it be possible to add a comment to the code justifying the assertion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a comment and moved the assertion up. The limits are related to the maximum base-10 exponent. For double-to-string, that's DBL_MAX_10_EXP (308). As you say, we set the limits ourselves for string-to-double, which are e=308 for overflow and e=-512 for underflow. But our exponent can be adjusted based on the number of digits, so we can see values as larges as k=535 |
||
PyInterpreterState *interp = _PyInterpreterState_GET(); | ||
p5 = interp->dtoa.p5s; | ||
if (!p5) { | ||
/* first time */ | ||
p5 = i2b(625); | ||
if (p5 == NULL) { | ||
Bfree(b); | ||
return NULL; | ||
} | ||
interp->dtoa.p5s = p5; | ||
p5->next = 0; | ||
} | ||
p5s = interp->dtoa.p5s; | ||
for(;;) { | ||
p5 = *p5s; | ||
p5s++; | ||
if (k & 1) { | ||
b1 = mult(b, p5); | ||
Bfree(b); | ||
|
@@ -707,17 +700,6 @@ pow5mult(Bigint *b, int k) | |
} | ||
if (!(k >>= 1)) | ||
break; | ||
p51 = p5->next; | ||
if (!p51) { | ||
p51 = mult(p5,p5); | ||
if (p51 == NULL) { | ||
Bfree(b); | ||
return NULL; | ||
} | ||
p51->next = 0; | ||
p5->next = p51; | ||
} | ||
p5 = p51; | ||
} | ||
return b; | ||
} | ||
|
@@ -2811,3 +2793,42 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, | |
} | ||
|
||
#endif // _PY_SHORT_FLOAT_REPR == 1 | ||
|
||
PyStatus | ||
_PyDtoa_Init(PyInterpreterState *interp) | ||
{ | ||
#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER) | ||
Bigint **p5s = interp->dtoa.p5s; | ||
|
||
// 5**4 = 625 | ||
Bigint *p5 = i2b(625); | ||
if (p5 == NULL) { | ||
return PyStatus_NoMemory(); | ||
} | ||
p5s[0] = p5; | ||
|
||
// compute 5**8, 5**16, 5**32, ..., 5**512 | ||
for (Py_ssize_t i = 1; i < Bigint_Pow5max; i++) { | ||
p5 = mult(p5, p5); | ||
if (p5 == NULL) { | ||
return PyStatus_NoMemory(); | ||
} | ||
p5s[i] = p5; | ||
} | ||
|
||
#endif | ||
return PyStatus_Ok(); | ||
} | ||
|
||
void | ||
_PyDtoa_Fini(PyInterpreterState *interp) | ||
{ | ||
#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER) | ||
Bigint **p5s = interp->dtoa.p5s; | ||
for (Py_ssize_t i = 0; i < Bigint_Pow5max; i++) { | ||
Bigint *p5 = p5s[i]; | ||
p5s[i] = NULL; | ||
Bfree(p5); | ||
} | ||
#endif | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I'm wondering whether there's a better name here; the "max" part seems potentially confusing (the max value stored here would be
5**2**9
, right?).Bigint_Pow5count
?Bigint_Pow5size
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed it to
Bigint_Pow5size