Bug 590834 - Clean up number-to-string code. r=jwalden.

This commit is contained in:
Nicholas Nethercote 2010-09-13 13:08:25 -07:00
Родитель bf6194eb25
Коммит 8883e09bc0
12 изменённых файлов: 165 добавлений и 109 удалений

Просмотреть файл

@ -38,7 +38,7 @@
#include "CTypes.h"
#include "Library.h"
#include "jsdtoa.h"
#include "jsnum.h"
#include <limits>
#include <math.h>
@ -2352,9 +2352,8 @@ BuildDataSource(JSContext* cx,
case TYPE_##name: { \
/* Serialize as a primitive double. */ \
double fp = *static_cast<type*>(data); \
char buf[DTOSTR_STANDARD_BUFFER_SIZE]; \
char* str = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, buf, sizeof(buf), \
DTOSTR_STANDARD, 0, fp); \
ToCStringBuf cbuf; \
char* str = NumberToCString(cx, &cbuf, fp); \
if (!str) { \
JS_ReportOutOfMemory(cx); \
return false; \

Просмотреть файл

@ -60,7 +60,6 @@
#include "jscntxt.h"
#include "jsversion.h"
#include "jsdate.h"
#include "jsdtoa.h"
#include "jsemit.h"
#include "jsexn.h"
#include "jsfun.h"

Просмотреть файл

@ -90,7 +90,6 @@
#include "jscntxt.h"
#include "jsversion.h"
#include "jsdbgapi.h" /* for js_TraceWatchPoints */
#include "jsdtoa.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsinterp.h"

Просмотреть файл

@ -1152,9 +1152,12 @@ struct JSThreadData {
DtoaState *dtoaState;
/*
* State used to cache some double-to-string conversions. A stupid
* optimization aimed directly at v8-splay.js, which stupidly converts
* many doubles multiple times in a row.
* A single-entry cache for some base-10 double-to-string conversions.
* This helps date-format-xparb.js. It also avoids skewing the results
* for v8-splay.js when measured by the SunSpider harness, where the splay
* tree initialization (which includes many repeated double-to-string
* conversions) is erroneously included in the measurement; see bug
* 562553.
*/
struct {
jsdouble d;

Просмотреть файл

@ -2342,19 +2342,20 @@ date_toDateString(JSContext *cx, uintN argc, Value *vp)
#if JS_HAS_TOSOURCE
#include <string.h>
#include "jsdtoa.h"
#include "jsnum.h"
static JSBool
date_toSource(JSContext *cx, uintN argc, Value *vp)
{
jsdouble utctime;
char buf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr, *bytes;
char *numStr, *bytes;
JSString *str;
if (!GetUTCTime(cx, ComputeThisFromVp(cx, vp), vp, &utctime))
return JS_FALSE;
numStr = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, buf, sizeof buf, DTOSTR_STANDARD, 0, utctime);
ToCStringBuf cbuf;
numStr = NumberToCString(cx, &cbuf, utctime);
if (!numStr) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;

Просмотреть файл

@ -103,9 +103,18 @@ typedef enum JSDToStrMode {
#define DTOSTR_VARIABLE_BUFFER_SIZE(precision) ((precision)+24 > DTOSTR_STANDARD_BUFFER_SIZE ? (precision)+24 : DTOSTR_STANDARD_BUFFER_SIZE)
/*
* Convert dval according to the given mode and return a pointer to the resulting ASCII string.
* The result is held somewhere in buffer, but not necessarily at the beginning. The size of
* buffer is given in bufferSize, and must be at least as large as given by the above macros.
* DO NOT USE THIS FUNCTION IF YOU CAN AVOID IT. js::NumberToCString() is a
* better function to use.
*
* Convert dval according to the given mode and return a pointer to the
* resulting ASCII string. If mode == DTOSTR_STANDARD and precision == 0 it's
* equivalent to ToString() as specified by ECMA-262-5 section 9.8.1, but it
* doesn't handle integers specially so should be avoided in that case (that's
* why js::NumberToCString() is better).
*
* The result is held somewhere in buffer, but not necessarily at the
* beginning. The size of buffer is given in bufferSize, and must be at least
* as large as given by the above macros.
*
* Return NULL if out of memory.
*/
@ -114,15 +123,22 @@ js_dtostr(DtoaState *state, char *buffer, size_t bufferSize, JSDToStrMode mode,
double dval);
/*
* Convert d to a string in the given base. The integral part of d will be printed exactly
* in that base, regardless of how large it is, because there is no exponential notation for non-base-ten
* numbers. The fractional part will be rounded to as few digits as possible while still preserving
* the round-trip property (analogous to that of printing decimal numbers). In other words, if one were
* to read the resulting string in via a hypothetical base-number-reading routine that rounds to the nearest
* IEEE double (and to an even significand if there are two equally near doubles), then the result would
* equal d (except for -0.0, which converts to "0", and NaN, which is not equal to itself).
* DO NOT USE THIS FUNCTION IF YOU CAN AVOID IT. js::NumberToCString() is a
* better function to use.
*
* Return NULL if out of memory. If the result is not NULL, it must be released via free().
* Convert d to a string in the given base. The integral part of d will be
* printed exactly in that base, regardless of how large it is, because there
* is no exponential notation for non-base-ten numbers. The fractional part
* will be rounded to as few digits as possible while still preserving the
* round-trip property (analogous to that of printing decimal numbers). In
* other words, if one were to read the resulting string in via a hypothetical
* base-number-reading routine that rounds to the nearest IEEE double (and to
* an even significand if there are two equally near doubles), then the result
* would equal d (except for -0.0, which converts to "0", and NaN, which is
* not equal to itself).
*
* Return NULL if out of memory. If the result is not NULL, it must be
* released via js_free().
*/
char *
js_dtobasestr(DtoaState *state, int base, double d);

Просмотреть файл

@ -50,7 +50,6 @@
#include "jsstdint.h"
#include "jsbit.h"
#include "jscntxt.h"
#include "jsdtoa.h"
#include "jsgc.h"
#include "jslock.h"
#include "jsscope.h"

Просмотреть файл

@ -559,7 +559,6 @@ Number(JSContext *cx, uintN argc, Value *vp)
static JSBool
num_toSource(JSContext *cx, uintN argc, Value *vp)
{
char numBuf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr;
char buf[64];
JSString *str;
@ -567,8 +566,8 @@ num_toSource(JSContext *cx, uintN argc, Value *vp)
if (!js_GetPrimitiveThis(cx, vp, &js_NumberClass, &primp))
return JS_FALSE;
double d = primp->toNumber();
numStr = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, numBuf, sizeof numBuf,
DTOSTR_STANDARD, 0, d);
ToCStringBuf cbuf;
char *numStr = NumberToCString(cx, &cbuf, d);
if (!numStr) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
@ -582,17 +581,28 @@ num_toSource(JSContext *cx, uintN argc, Value *vp)
}
#endif
/* The buf must be big enough for MIN_INT to fit including '-' and '\0'. */
ToCStringBuf::ToCStringBuf() :dbuf(NULL)
{
JS_STATIC_ASSERT(sbufSize >= DTOSTR_STANDARD_BUFFER_SIZE);
}
ToCStringBuf::~ToCStringBuf()
{
if (dbuf)
js_free(dbuf);
}
/* Returns a non-NULL pointer to inside cbuf. */
static char *
IntToCString(jsint i, jsint base, char *buf, size_t bufSize)
IntToCString(ToCStringBuf *cbuf, jsint i, jsint base = 10)
{
char *cp;
jsuint u;
u = (i < 0) ? -i : i;
cp = buf + bufSize; /* one past last buffer cell */
*--cp = '\0'; /* null terminate the string to be */
cp = cbuf->sbuf + cbuf->sbufSize; /* one past last buffer cell */
*--cp = '\0'; /* null terminate the string to be */
/*
* Build the string from behind. We use multiply and subtraction
@ -625,7 +635,7 @@ IntToCString(jsint i, jsint base, char *buf, size_t bufSize)
if (i < 0)
*--cp = '-';
JS_ASSERT(cp >= buf);
JS_ASSERT(cp >= cbuf->sbuf);
return cp;
}
@ -645,8 +655,9 @@ num_toString(JSContext *cx, uintN argc, Value *vp)
return JS_FALSE;
if (base < 2 || base > 36) {
char numBuf[12];
char *numStr = IntToCString(base, 10, numBuf, sizeof numBuf);
ToCStringBuf cbuf;
char *numStr = IntToCString(&cbuf, base); /* convert the base itself to a string */
JS_ASSERT(numStr);
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_RADIX,
numStr);
return JS_FALSE;
@ -808,12 +819,10 @@ num_to(JSContext *cx, JSDToStrMode zeroArgMode, JSDToStrMode oneArgMode,
return JS_FALSE;
precision = js_DoubleToInteger(precision);
if (precision < precisionMin || precision > precisionMax) {
numStr = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, buf, sizeof buf,
DTOSTR_STANDARD, 0, precision);
if (!numStr)
JS_ReportOutOfMemory(cx);
else
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PRECISION_RANGE, numStr);
ToCStringBuf cbuf;
numStr = IntToCString(&cbuf, jsint(precision));
JS_ASSERT(numStr);
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PRECISION_RANGE, numStr);
return JS_FALSE;
}
}
@ -845,8 +854,8 @@ num_toFixed(JSContext *cx, uintN argc, Value *vp)
static JSBool
num_toExponential(JSContext *cx, uintN argc, Value *vp)
{
return num_to(cx, DTOSTR_STANDARD_EXPONENTIAL, DTOSTR_EXPONENTIAL, 0,
MAX_PRECISION, 1, argc, vp);
return num_to(cx, DTOSTR_STANDARD_EXPONENTIAL, DTOSTR_EXPONENTIAL, 0, MAX_PRECISION, 1,
argc, vp);
}
static JSBool
@ -1023,33 +1032,32 @@ js_InitNumberClass(JSContext *cx, JSObject *obj)
return proto;
}
/*
* Convert a number to C string. The buf must be large enough to accommodate
* the result, including '-' and '\0', if base == 10 or d is an integer that
* fits in 32 bits. The caller must free the resulting pointer if it does not
* point into buf.
*/
namespace js {
static char *
NumberToCString(JSContext *cx, jsdouble d, jsint base, char *buf, size_t bufSize)
FracNumberToCString(JSContext *cx, ToCStringBuf *cbuf, jsdouble d, jsint base = 10)
{
#ifdef DEBUG
{
int32 _;
JS_ASSERT(!JSDOUBLE_IS_INT32(d, &_));
}
#endif
return (base == 10)
? js_dtostr(JS_THREAD_DATA(cx)->dtoaState, cbuf->sbuf, cbuf->sbufSize,
DTOSTR_STANDARD, 0, d)
: cbuf->dbuf = js_dtobasestr(JS_THREAD_DATA(cx)->dtoaState, base, d);
}
char *
NumberToCString(JSContext *cx, ToCStringBuf *cbuf, jsdouble d, jsint base/* = 10*/)
{
int32_t i;
char *numStr;
return (JSDOUBLE_IS_INT32(d, &i))
? IntToCString(cbuf, i, base)
: FracNumberToCString(cx, cbuf, d, base);
}
JS_ASSERT(bufSize >= DTOSTR_STANDARD_BUFFER_SIZE);
if (JSDOUBLE_IS_INT32(d, &i)) {
numStr = IntToCString(i, base, buf, bufSize);
} else {
if (base == 10)
numStr = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, buf, bufSize,
DTOSTR_STANDARD, 0, d);
else
numStr = js_dtobasestr(JS_THREAD_DATA(cx)->dtoaState, base, d);
if (!numStr) {
JS_ReportOutOfMemory(cx);
return NULL;
}
}
return numStr;
}
JSString * JS_FASTCALL
@ -1058,22 +1066,17 @@ js_IntToString(JSContext *cx, jsint i)
if (jsuint(i) < INT_STRING_LIMIT)
return JSString::intString(i);
char buf[12];
return js_NewStringCopyZ(cx, IntToCString(i, 10, buf, sizeof buf));
ToCStringBuf cbuf;
return js_NewStringCopyZ(cx, IntToCString(&cbuf, i));
}
static JSString * JS_FASTCALL
js_NumberToStringWithBase(JSContext *cx, jsdouble d, jsint base)
{
/*
* The longest possible result here that would need to fit in buf is
* (-0x80000000).toString(2), which has length 33. (This can produce
* longer results, but in those cases buf is not used; see comment at
* NumberToCString.)
*/
char buf[34];
ToCStringBuf cbuf;
char *numStr;
JSString *s;
JSThreadData *data;
/*
* Caller is responsible for error reporting. When called from trace,
@ -1092,19 +1095,35 @@ js_NumberToStringWithBase(JSContext *cx, jsdouble d, jsint base)
return JSString::intString(i);
return JSString::unitString(jschar('a' + i - 10));
}
data = JS_THREAD_DATA(cx);
if (data->dtoaCache.s && data->dtoaCache.base == base && data->dtoaCache.d == d)
return data->dtoaCache.s;
numStr = IntToCString(&cbuf, i, base);
JS_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize);
} else {
data = JS_THREAD_DATA(cx);
if (data->dtoaCache.s && data->dtoaCache.base == base && data->dtoaCache.d == d)
return data->dtoaCache.s;
numStr = FracNumberToCString(cx, &cbuf, d, base);
if (!numStr) {
JS_ReportOutOfMemory(cx);
return NULL;
}
JS_ASSERT_IF(base == 10,
!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize);
JS_ASSERT_IF(base != 10,
cbuf.dbuf && cbuf.dbuf == numStr);
}
JSThreadData *data = JS_THREAD_DATA(cx);
if (data->dtoaCache.s && data->dtoaCache.base == base && data->dtoaCache.d == d)
return data->dtoaCache.s;
numStr = NumberToCString(cx, d, base, buf, sizeof buf);
if (!numStr)
return NULL;
s = js_NewStringCopyZ(cx, numStr);
if (!(numStr >= buf && numStr < buf + sizeof buf))
js_free(numStr);
data->dtoaCache.base = base;
data->dtoaCache.d = d;
data->dtoaCache.s = s;
return s;
}
@ -1118,24 +1137,24 @@ JSBool JS_FASTCALL
js_NumberValueToCharBuffer(JSContext *cx, const Value &v, JSCharBuffer &cb)
{
/* Convert to C-string. */
static const size_t arrSize = DTOSTR_STANDARD_BUFFER_SIZE;
char arr[arrSize];
ToCStringBuf cbuf;
const char *cstr;
if (v.isInt32()) {
cstr = IntToCString(v.toInt32(), 10, arr, arrSize);
cstr = IntToCString(&cbuf, v.toInt32());
} else {
cstr = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, arr, arrSize,
DTOSTR_STANDARD, 0, v.toDouble());
cstr = NumberToCString(cx, &cbuf, v.toDouble());
if (!cstr) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
}
if (!cstr)
return JS_FALSE;
/*
* Inflate to jschar string. The input C-string characters are < 127, so
* even if jschars are UTF-8, all chars should map to one jschar.
*/
size_t cstrlen = strlen(cstr);
JS_ASSERT(cstrlen < arrSize);
JS_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize);
size_t sizeBefore = cb.length();
if (!cb.growByUninitialized(cstrlen))
return JS_FALSE;

Просмотреть файл

@ -189,6 +189,11 @@ extern const char js_parseInt_str[];
extern JSString * JS_FASTCALL
js_IntToString(JSContext *cx, jsint i);
/*
* When base == 10, this function implements ToString() as specified by
* ECMA-262-5 section 9.8.1; but note that it handles integers specially for
* performance. See also js::NumberToCString().
*/
extern JSString * JS_FASTCALL
js_NumberToString(JSContext *cx, jsdouble d);
@ -201,6 +206,35 @@ js_NumberValueToCharBuffer(JSContext *cx, const js::Value &v, JSCharBuffer &cb);
namespace js {
/*
* Usually a small amount of static storage is enough, but sometimes we need
* to dynamically allocate much more. This struct encapsulates that.
* Dynamically allocated memory will be freed when the object is destroyed.
*/
struct ToCStringBuf
{
/*
* The longest possible result that would need to fit in sbuf is
* (-0x80000000).toString(2), which has length 33. Longer cases are
* possible, but they'll go in dbuf.
*/
static const size_t sbufSize = 34;
char sbuf[sbufSize];
char *dbuf; /* must be allocated with js_malloc() */
ToCStringBuf();
~ToCStringBuf();
};
/*
* Convert a number to a C string. When base==10, this function implements
* ToString() as specified by ECMA-262-5 section 9.8.1. It handles integral
* values cheaply. Return NULL if we ran out of memory. See also
* js_NumberToCString().
*/
extern char *
NumberToCString(JSContext *cx, ToCStringBuf *cbuf, jsdouble d, jsint base = 10);
/*
* The largest positive integer such that all positive integers less than it
* may be precisely represented using the IEEE-754 double-precision format.

Просмотреть файл

@ -46,7 +46,6 @@
#include "jsatom.h"
#include "jsbool.h"
#include "jscntxt.h"
#include "jsdtoa.h"
#include "jsfun.h"
#include "jsinterp.h"
#include "jsiter.h"
@ -527,21 +526,11 @@ Str(JSContext *cx, jsid id, JSObject *holder, StringifyContext *scx, Value *vp,
return js_AppendLiteral(scx->cb, "null");
}
char numBuf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr;
jsdouble d = vp->isInt32() ? jsdouble(vp->toInt32()) : vp->toDouble();
numStr = js_dtostr(JS_THREAD_DATA(cx)->dtoaState, numBuf, sizeof numBuf,
DTOSTR_STANDARD, 0, d);
if (!numStr) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
jschar dstr[DTOSTR_STANDARD_BUFFER_SIZE];
size_t dbufSize = DTOSTR_STANDARD_BUFFER_SIZE;
if (!js_InflateStringToBuffer(cx, numStr, strlen(numStr), dstr, &dbufSize))
JSCharBuffer cb(cx);
if (!js_NumberValueToCharBuffer(cx, *vp, cb))
return JS_FALSE;
return scx->cb.append(dstr, dbufSize);
return scx->cb.append(cb.begin(), cb.length());
}
if (vp->isObject() && !IsFunctionObject(*vp) && !IsXML(*vp)) {

Просмотреть файл

@ -52,7 +52,6 @@
#include "jsstdint.h"
#include "jsarena.h" /* Added by JSIFY */
#include "jsutil.h" /* Added by JSIFY */
#include "jsdtoa.h"
#include "jsprf.h"
#include "jsapi.h"
#include "jsarray.h"
@ -1126,7 +1125,7 @@ SprintDoubleValue(Sprinter *sp, jsval v, JSOp *opp)
{
jsdouble d;
ptrdiff_t todo;
char *s, buf[DTOSTR_STANDARD_BUFFER_SIZE];
char *s;
JS_ASSERT(JSVAL_IS_DOUBLE(v));
d = JSVAL_TO_DOUBLE(v);
@ -1143,8 +1142,8 @@ SprintDoubleValue(Sprinter *sp, jsval v, JSOp *opp)
: "1 / 0");
*opp = JSOP_DIV;
} else {
s = js_dtostr(JS_THREAD_DATA(sp->context)->dtoaState, buf, sizeof buf,
DTOSTR_STANDARD, 0, d);
ToCStringBuf cbuf;
s = NumberToCString(sp->context, &cbuf, d);
if (!s) {
JS_ReportOutOfMemory(sp->context);
return -1;

Просмотреть файл

@ -56,7 +56,6 @@
#include "jsarena.h" /* Added by JSIFY */
#include "jsbit.h"
#include "jsutil.h" /* Added by JSIFY */
#include "jsdtoa.h"
#include "jsprf.h"
#include "jsapi.h"
#include "jsatom.h"