Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[JSC] Optimize parseFloat(number)
https://bugs.webkit.org/show_bug.cgi?id=258115
rdar://110822592

Reviewed by Mark Lam.

parseFloat(String(double)) can generate the exact same double *except for -0.0*.
This patch adds a fast path returning double when parseFloat's input is double.
We also extend parseInt optimization in C++ runtime function, which can now accept
radix = 10 cases too (it is already handled in DFG and FTL, but expand it for lower tiers).

* JSTests/stress/parse-float-double.js: Added.
(shouldBe):
(parseFloatDouble):
* JSTests/stress/parse-int-double-radix-undefined.js: Added.
(shouldBe):
(parseIntDouble):
* Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):

Canonical link: https://commits.webkit.org/265189@main
  • Loading branch information
Constellation committed Jun 15, 2023
1 parent 81c064d commit c6ee90f
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
31 changes: 31 additions & 0 deletions JSTests/stress/parse-float-double.js
@@ -0,0 +1,31 @@
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

function parseFloatDouble(value) {
return parseFloat(value);
}
noInline(parseFloatDouble);

for (var i = 0; i < 1e5; ++i) {
shouldBe(Object.is(parseFloatDouble(0.0), 0), true);
shouldBe(Object.is(parseFloatDouble(-0.0), 0), true); // Not -0 since -0.0.toString() is "0".
shouldBe(Object.is(parseFloatDouble(-1.0), -1.0), true);
shouldBe(Object.is(parseFloatDouble(-0.01), -0.01), true);
shouldBe(Object.is(parseFloatDouble(-1.1), -1.1), true);
shouldBe(Object.is(parseFloatDouble(-1.0), -1.0), true);
shouldBe(Object.is(parseFloatDouble(-0.9), -0.9), true);
shouldBe(Object.is(parseFloatDouble(-1.000000001), -1.000000001), true);
shouldBe(Object.is(parseFloatDouble(-0.000000001), -0.000000001), true);
shouldBe(Object.is(parseFloatDouble(0.000000001), 0.000000001), true);
shouldBe(Object.is(parseFloatDouble(0.000001), 0.000001), true);
shouldBe(Object.is(parseFloatDouble(0.000001), 0.000001), true);
shouldBe(Object.is(parseFloatDouble(0.0000001), 0.0000001), true);
shouldBe(Object.is(parseFloatDouble(-0.0000001), -0.0000001), true);
shouldBe(Object.is(parseFloatDouble(1e+21), 1e+21), true);
shouldBe(Object.is(parseFloatDouble(1e+20), 1e+20), true);
shouldBe(Object.is(parseFloatDouble(NaN), NaN), true);
shouldBe(Object.is(parseFloatDouble(Infinity), Infinity), true);
shouldBe(Object.is(parseFloatDouble(-Infinity), -Infinity), true);
}
30 changes: 30 additions & 0 deletions JSTests/stress/parse-int-double-radix-undefined.js
@@ -0,0 +1,30 @@
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

function parseIntDouble(value) {
return parseInt(value);
}
noInline(parseIntDouble);

for (var i = 0; i < 1e5; ++i) {
shouldBe(Object.is(parseIntDouble(-0.0), 0), true); // Not -0 since -0.0.toString() is "0".
shouldBe(Object.is(parseIntDouble(-1.0), -1.0), true);
shouldBe(Object.is(parseIntDouble(-0.01), -0), true);
shouldBe(Object.is(parseIntDouble(-1.1), -1.0), true);
shouldBe(Object.is(parseIntDouble(-1.0), -1.0), true);
shouldBe(Object.is(parseIntDouble(-0.9), -0.0), true);
shouldBe(Object.is(parseIntDouble(-1.000000001), -1.0), true);
shouldBe(Object.is(parseIntDouble(-0.000000001), -1), true); // Since it is -1e-9.
shouldBe(Object.is(parseIntDouble(0.000000001), 1), true); // Since it is 1e-9.
shouldBe(Object.is(parseIntDouble(0.000001), 0), true);
shouldBe(Object.is(parseIntDouble(0.000001), 0), true);
shouldBe(Object.is(parseIntDouble(0.0000001), 1), true); // Since it is 1e-6.
shouldBe(Object.is(parseIntDouble(-0.0000001), -1), true); // Since it is -1e-6.
shouldBe(Object.is(parseIntDouble(1e+21), 1), true); // Since it is 1e+21.
shouldBe(Object.is(parseIntDouble(1e+20), 1e+20), true);
shouldBe(Object.is(parseIntDouble(NaN), NaN), true);
shouldBe(Object.is(parseIntDouble(Infinity), NaN), true);
shouldBe(Object.is(parseIntDouble(-Infinity), NaN), true);
}
21 changes: 17 additions & 4 deletions Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp
Expand Up @@ -518,9 +518,13 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncParseInt, (JSGlobalObject* globalObject, Call
JSValue value = callFrame->argument(0);
JSValue radixValue = callFrame->argument(1);

if (value.isNumber() && radixValue.isUndefinedOrNull()) {
if (auto result = parseIntDouble(value.asNumber()))
return JSValue::encode(jsNumber(result.value()));
if (value.isNumber()) {
if (radixValue.isUndefinedOrNull() || (radixValue.isInt32() && radixValue.asInt32() == 10)) {
if (value.isInt32())
return JSValue::encode(value);
if (auto result = parseIntDouble(value.asDouble()))
return JSValue::encode(jsNumber(result.value()));
}
}

// If ToString throws, we shouldn't call ToInt32.
Expand All @@ -534,7 +538,16 @@ JSC_DEFINE_HOST_FUNCTION(globalFuncParseFloat, (JSGlobalObject* globalObject, Ca
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

auto* jsString = callFrame->argument(0).toString(globalObject);
JSValue value = callFrame->argument(0);
if (value.isNumber()) {
if (value.isInt32())
return JSValue::encode(value);
if (value.asDouble() == 0.0) // Makes -0.0 to 0.0 too.
return JSValue::encode(jsNumber(0.0));
return JSValue::encode(value);
}

auto* jsString = value.toString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
auto viewWithString = jsString->viewWithUnderlyingString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
Expand Down

0 comments on commit c6ee90f

Please sign in to comment.