[DFG][FTL] Efficiently execute number#toString()
https://bugs.webkit.org/show_bug.cgi?id=170007
Reviewed by Keith Miller.
JSTests:
* microbenchmarks/number-to-string-strength-reduction.js: Added.
(test):
* microbenchmarks/number-to-string-with-radix-10.js: Added.
(test):
* microbenchmarks/number-to-string-with-radix-cse.js: Added.
(test):
* microbenchmarks/number-to-string-with-radix.js: Added.
(test):
* stress/number-to-string-strength-reduction.js: Added.
(shouldBe):
(test):
* stress/number-to-string-with-radix-10.js: Added.
(shouldBe):
(test):
* stress/number-to-string-with-radix-cse.js: Added.
(shouldBe):
(test):
* stress/number-to-string-with-radix-invalid.js: Added.
(shouldThrow):
* stress/number-to-string-with-radix-watchpoint.js: Added.
(shouldBe):
(test):
(i.i.1e3.Number.prototype.toString):
* stress/number-to-string-with-radix.js: Added.
(shouldBe):
(test):
Source/JavaScriptCore:
In JS, the natural way to convert number to string with radix is `number.toString(radix)`.
However, our IC only cares about cells. If the base value is a number, it always goes to the slow path.
While extending our IC for number and boolean, the most meaningful use of this IC is calling `number.toString(radix)`.
So, in this patch, we first add a fast path for this in DFG by using watchpoint. We set up a watchpoint for
Number.prototype.toString. And if this watchpoint is kept alive and GetById(base, "toString")'s base should be
speculated as Number, we emit Number related Checks and convert GetById to Number.prototype.toString constant.
It removes costly GetById slow path, and makes it non-clobbering node (JSConstant).
In addition, we add NumberToStringWithValidRadixConstant node. We have NumberToStringWithRadix node, but it may
throw an error if the valid value is incorrect (for example, number.toString(2000)). So its clobbering rule is
conservatively use read(World)/write(Heap). But in reality, `number.toString` is mostly called with the constant
radix, and we can easily figure out this radix is valid (2 <= radix && radix < 32).
We add a rule to the constant folding phase to convert NumberToStringWithRadix to NumberToStringWithValidRadixConstant.
It ensures that it has valid constant radix. And we relax our clobbering rule for NumberToStringWithValidRadixConstant.
Added microbenchmarks show performance improvement.
baseline patched
number-to-string-with-radix-cse 43.8312+-1.3017 ^ 7.4930+-0.5105 ^ definitely 5.8496x faster
number-to-string-with-radix-10 7.2775+-0.5225 ^ 2.1906+-0.1864 ^ definitely 3.3222x faster
number-to-string-with-radix 39.7378+-1.4921 ^ 16.6137+-0.7776 ^ definitely 2.3919x faster
number-to-string-strength-reduction 94.9667+-2.7157 ^ 9.3060+-0.7202 ^ definitely 10.2049x faster
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGGraph.h:
(JSC::DFG::Graph::isWatchingGlobalObjectWatchpoint):
(JSC::DFG::Graph::isWatchingArrayIteratorProtocolWatchpoint):
(JSC::DFG::Graph::isWatchingNumberToStringWatchpoint):
* dfg/DFGNode.h:
(JSC::DFG::Node::convertToNumberToStringWithValidRadixConstant):
(JSC::DFG::Node::hasValidRadixConstant):
(JSC::DFG::Node::validRadixConstant):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructor):
(JSC::DFG::SpeculativeJIT::compileNumberToStringWithValidRadixConstant):
(JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber): Deleted.
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGStrengthReductionPhase.cpp:
(JSC::DFG::StrengthReductionPhase::handleNode):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileNumberToStringWithValidRadixConstant):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::JSGlobalObject):
(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::numberToStringWatchpoint):
(JSC::JSGlobalObject::numberProtoToStringFunction const):
* runtime/NumberPrototype.cpp:
(JSC::NumberPrototype::finishCreation):
(JSC::toStringWithRadixInternal):
(JSC::toStringWithRadix):
(JSC::int32ToStringInternal):
(JSC::numberToStringInternal):
* runtime/NumberPrototype.h:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@221601 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index 1ba586a..ee79af3 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,37 @@
+2017-09-03 Yusuke Suzuki <utatane.tea@gmail.com>
+
+ [DFG][FTL] Efficiently execute number#toString()
+ https://bugs.webkit.org/show_bug.cgi?id=170007
+
+ Reviewed by Keith Miller.
+
+ * microbenchmarks/number-to-string-strength-reduction.js: Added.
+ (test):
+ * microbenchmarks/number-to-string-with-radix-10.js: Added.
+ (test):
+ * microbenchmarks/number-to-string-with-radix-cse.js: Added.
+ (test):
+ * microbenchmarks/number-to-string-with-radix.js: Added.
+ (test):
+ * stress/number-to-string-strength-reduction.js: Added.
+ (shouldBe):
+ (test):
+ * stress/number-to-string-with-radix-10.js: Added.
+ (shouldBe):
+ (test):
+ * stress/number-to-string-with-radix-cse.js: Added.
+ (shouldBe):
+ (test):
+ * stress/number-to-string-with-radix-invalid.js: Added.
+ (shouldThrow):
+ * stress/number-to-string-with-radix-watchpoint.js: Added.
+ (shouldBe):
+ (test):
+ (i.i.1e3.Number.prototype.toString):
+ * stress/number-to-string-with-radix.js: Added.
+ (shouldBe):
+ (test):
+
2017-09-02 Yusuke Suzuki <utatane.tea@gmail.com>
[DFG] Relax arity requirement
diff --git a/JSTests/microbenchmarks/number-to-string-strength-reduction.js b/JSTests/microbenchmarks/number-to-string-strength-reduction.js
new file mode 100644
index 0000000..2e70095
--- /dev/null
+++ b/JSTests/microbenchmarks/number-to-string-strength-reduction.js
@@ -0,0 +1,9 @@
+function test()
+{
+ var target = 42;
+ return target.toString(16);
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i)
+ test();
diff --git a/JSTests/microbenchmarks/number-to-string-with-radix-10.js b/JSTests/microbenchmarks/number-to-string-with-radix-10.js
new file mode 100644
index 0000000..9c083cb
--- /dev/null
+++ b/JSTests/microbenchmarks/number-to-string-with-radix-10.js
@@ -0,0 +1,10 @@
+function test()
+{
+ for (var i = 0; i < 10; ++i)
+ var result = i.toString(10);
+ return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+ test();
diff --git a/JSTests/microbenchmarks/number-to-string-with-radix-cse.js b/JSTests/microbenchmarks/number-to-string-with-radix-cse.js
new file mode 100644
index 0000000..f6bc152
--- /dev/null
+++ b/JSTests/microbenchmarks/number-to-string-with-radix-cse.js
@@ -0,0 +1,14 @@
+function test()
+{
+ for (var i = 0; i < 1e2; ++i) {
+ i.toString(16);
+ i.toString(16);
+ i.toString(16);
+ i.toString(16);
+ i.toString(16);
+ }
+}
+noInline(test);
+
+for (var i = 0; i < 1e3; ++i)
+ test();
diff --git a/JSTests/microbenchmarks/number-to-string-with-radix.js b/JSTests/microbenchmarks/number-to-string-with-radix.js
new file mode 100644
index 0000000..0213020
--- /dev/null
+++ b/JSTests/microbenchmarks/number-to-string-with-radix.js
@@ -0,0 +1,16 @@
+function test()
+{
+ for (var i = 0; i < 10; ++i) {
+ var result = '';
+ result += i.toString(2);
+ result += i.toString(4);
+ result += i.toString(8);
+ result += i.toString(16);
+ result += i.toString(32);
+ }
+ return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+ test();
diff --git a/JSTests/stress/number-to-string-strength-reduction.js b/JSTests/stress/number-to-string-strength-reduction.js
new file mode 100644
index 0000000..6f0993f
--- /dev/null
+++ b/JSTests/stress/number-to-string-strength-reduction.js
@@ -0,0 +1,14 @@
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+ var target = 42;
+ return target.toString(16);
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i)
+ shouldBe(test(), `2a`);
diff --git a/JSTests/stress/number-to-string-with-radix-10.js b/JSTests/stress/number-to-string-with-radix-10.js
new file mode 100644
index 0000000..bf8f6a0
--- /dev/null
+++ b/JSTests/stress/number-to-string-with-radix-10.js
@@ -0,0 +1,15 @@
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test()
+{
+ for (var i = 0; i < 10; ++i)
+ shouldBe(i.toString(10), "" + i);
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+ test();
diff --git a/JSTests/stress/number-to-string-with-radix-cse.js b/JSTests/stress/number-to-string-with-radix-cse.js
new file mode 100644
index 0000000..db78216
--- /dev/null
+++ b/JSTests/stress/number-to-string-with-radix-cse.js
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+ var result;
+ for (var i = 0; i < 1e2; ++i) {
+ i.toString(16);
+ i.toString(16);
+ i.toString(16);
+ i.toString(16);
+ result = i.toString(16);
+ }
+ return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e3; ++i)
+ shouldBe(test(), `63`);
diff --git a/JSTests/stress/number-to-string-with-radix-invalid.js b/JSTests/stress/number-to-string-with-radix-invalid.js
new file mode 100644
index 0000000..0690965
--- /dev/null
+++ b/JSTests/stress/number-to-string-with-radix-invalid.js
@@ -0,0 +1,24 @@
+function shouldThrow(func, errorMessage) {
+ var errorThrown = false;
+ var error = null;
+ try {
+ func();
+ } catch (e) {
+ errorThrown = true;
+ error = e;
+ }
+ if (!errorThrown)
+ throw new Error('not thrown');
+ if (String(error) !== errorMessage)
+ throw new Error(`bad error: ${String(error)}`);
+}
+
+function test(i, radix)
+{
+ return i.toString(radix);
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i) {
+ shouldThrow(() => test(i, 42), `RangeError: toString() radix argument must be between 2 and 36`);
+}
diff --git a/JSTests/stress/number-to-string-with-radix-watchpoint.js b/JSTests/stress/number-to-string-with-radix-watchpoint.js
new file mode 100644
index 0000000..7968f58
--- /dev/null
+++ b/JSTests/stress/number-to-string-with-radix-watchpoint.js
@@ -0,0 +1,27 @@
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+ for (var i = 0; i < 10; ++i) {
+ var result = '';
+ result += i.toString(2);
+ result += i.toString(4);
+ result += i.toString(8);
+ result += i.toString(16);
+ result += i.toString(32);
+ }
+ return result;
+}
+noInline(test);
+
+var result = `1001211199`;
+for (var i = 0; i < 1e4; ++i) {
+ if (i === 1e3) {
+ Number.prototype.toString = function (radix) { return "Hello"; }
+ result = `HelloHelloHelloHelloHello`;
+ }
+ shouldBe(test(), result);
+}
diff --git a/JSTests/stress/number-to-string-with-radix.js b/JSTests/stress/number-to-string-with-radix.js
new file mode 100644
index 0000000..e51a976
--- /dev/null
+++ b/JSTests/stress/number-to-string-with-radix.js
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+ for (var i = 0; i < 10; ++i) {
+ var result = '';
+ result += i.toString(2);
+ result += i.toString(4);
+ result += i.toString(8);
+ result += i.toString(16);
+ result += i.toString(32);
+ }
+ return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+ shouldBe(test(), `1001211199`);
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 637ffac..dd0d378 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,88 @@
+2017-09-03 Yusuke Suzuki <utatane.tea@gmail.com>
+
+ [DFG][FTL] Efficiently execute number#toString()
+ https://bugs.webkit.org/show_bug.cgi?id=170007
+
+ Reviewed by Keith Miller.
+
+ In JS, the natural way to convert number to string with radix is `number.toString(radix)`.
+ However, our IC only cares about cells. If the base value is a number, it always goes to the slow path.
+
+ While extending our IC for number and boolean, the most meaningful use of this IC is calling `number.toString(radix)`.
+ So, in this patch, we first add a fast path for this in DFG by using watchpoint. We set up a watchpoint for
+ Number.prototype.toString. And if this watchpoint is kept alive and GetById(base, "toString")'s base should be
+ speculated as Number, we emit Number related Checks and convert GetById to Number.prototype.toString constant.
+ It removes costly GetById slow path, and makes it non-clobbering node (JSConstant).
+
+ In addition, we add NumberToStringWithValidRadixConstant node. We have NumberToStringWithRadix node, but it may
+ throw an error if the valid value is incorrect (for example, number.toString(2000)). So its clobbering rule is
+ conservatively use read(World)/write(Heap). But in reality, `number.toString` is mostly called with the constant
+ radix, and we can easily figure out this radix is valid (2 <= radix && radix < 32).
+ We add a rule to the constant folding phase to convert NumberToStringWithRadix to NumberToStringWithValidRadixConstant.
+ It ensures that it has valid constant radix. And we relax our clobbering rule for NumberToStringWithValidRadixConstant.
+
+ Added microbenchmarks show performance improvement.
+
+ baseline patched
+
+ number-to-string-with-radix-cse 43.8312+-1.3017 ^ 7.4930+-0.5105 ^ definitely 5.8496x faster
+ number-to-string-with-radix-10 7.2775+-0.5225 ^ 2.1906+-0.1864 ^ definitely 3.3222x faster
+ number-to-string-with-radix 39.7378+-1.4921 ^ 16.6137+-0.7776 ^ definitely 2.3919x faster
+ number-to-string-strength-reduction 94.9667+-2.7157 ^ 9.3060+-0.7202 ^ definitely 10.2049x faster
+
+ * dfg/DFGAbstractInterpreterInlines.h:
+ (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+ * dfg/DFGClobberize.h:
+ (JSC::DFG::clobberize):
+ * dfg/DFGConstantFoldingPhase.cpp:
+ (JSC::DFG::ConstantFoldingPhase::foldConstants):
+ * dfg/DFGDoesGC.cpp:
+ (JSC::DFG::doesGC):
+ * dfg/DFGFixupPhase.cpp:
+ (JSC::DFG::FixupPhase::fixupNode):
+ * dfg/DFGGraph.h:
+ (JSC::DFG::Graph::isWatchingGlobalObjectWatchpoint):
+ (JSC::DFG::Graph::isWatchingArrayIteratorProtocolWatchpoint):
+ (JSC::DFG::Graph::isWatchingNumberToStringWatchpoint):
+ * dfg/DFGNode.h:
+ (JSC::DFG::Node::convertToNumberToStringWithValidRadixConstant):
+ (JSC::DFG::Node::hasValidRadixConstant):
+ (JSC::DFG::Node::validRadixConstant):
+ * dfg/DFGNodeType.h:
+ * dfg/DFGPredictionPropagationPhase.cpp:
+ * dfg/DFGSafeToExecute.h:
+ (JSC::DFG::safeToExecute):
+ * dfg/DFGSpeculativeJIT.cpp:
+ (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructor):
+ (JSC::DFG::SpeculativeJIT::compileNumberToStringWithValidRadixConstant):
+ (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber): Deleted.
+ * dfg/DFGSpeculativeJIT.h:
+ * dfg/DFGSpeculativeJIT32_64.cpp:
+ (JSC::DFG::SpeculativeJIT::compile):
+ * dfg/DFGSpeculativeJIT64.cpp:
+ (JSC::DFG::SpeculativeJIT::compile):
+ * dfg/DFGStrengthReductionPhase.cpp:
+ (JSC::DFG::StrengthReductionPhase::handleNode):
+ * ftl/FTLCapabilities.cpp:
+ (JSC::FTL::canCompile):
+ * ftl/FTLLowerDFGToB3.cpp:
+ (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+ (JSC::FTL::DFG::LowerDFGToB3::compileNumberToStringWithValidRadixConstant):
+ * runtime/JSGlobalObject.cpp:
+ (JSC::JSGlobalObject::JSGlobalObject):
+ (JSC::JSGlobalObject::init):
+ (JSC::JSGlobalObject::visitChildren):
+ * runtime/JSGlobalObject.h:
+ (JSC::JSGlobalObject::numberToStringWatchpoint):
+ (JSC::JSGlobalObject::numberProtoToStringFunction const):
+ * runtime/NumberPrototype.cpp:
+ (JSC::NumberPrototype::finishCreation):
+ (JSC::toStringWithRadixInternal):
+ (JSC::toStringWithRadix):
+ (JSC::int32ToStringInternal):
+ (JSC::numberToStringInternal):
+ * runtime/NumberPrototype.h:
+
2017-09-04 Yusuke Suzuki <utatane.tea@gmail.com>
[DFG] Consider increasing the number of DFG worklist threads
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index a1a216a..9da194b 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -1949,10 +1949,25 @@
break;
}
- case NumberToStringWithRadix:
+ case NumberToStringWithRadix: {
+ JSValue radixValue = forNode(node->child2()).m_value;
+ if (radixValue && radixValue.isInt32()) {
+ int32_t radix = radixValue.asInt32();
+ if (2 <= radix && radix <= 36) {
+ m_state.setFoundConstants(true);
+ forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
+ break;
+ }
+ }
clobberWorld(node->origin.semantic, clobberLimit);
forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
break;
+ }
+
+ case NumberToStringWithValidRadixConstant: {
+ forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
+ break;
+ }
case NewStringObject: {
ASSERT(node->structure()->classInfo() == StringObject::info());
diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h
index 6691bf9..56c06b3 100644
--- a/Source/JavaScriptCore/dfg/DFGClobberize.h
+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h
@@ -1584,9 +1584,14 @@
return;
case NumberToStringWithRadix:
+ // If the radix is invalid, NumberToStringWithRadix can throw an error.
read(World);
write(Heap);
return;
+
+ case NumberToStringWithValidRadixConstant:
+ def(PureValue(node, node->validRadixConstant()));
+ return;
case LastNodeType:
RELEASE_ASSERT_NOT_REACHED();
diff --git a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
index 19e98e8..5ebe854 100644
--- a/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
@@ -645,6 +645,23 @@
break;
}
+ case NumberToStringWithRadix: {
+ JSValue radixValue = m_state.forNode(node->child2()).m_value;
+ if (radixValue && radixValue.isInt32()) {
+ int32_t radix = radixValue.asInt32();
+ if (2 <= radix && radix <= 36) {
+ if (radix == 10) {
+ node->setOpAndDefaultFlags(ToString);
+ node->child2() = Edge();
+ } else
+ node->convertToNumberToStringWithValidRadixConstant(radix);
+ changed = true;
+ break;
+ }
+ }
+ break;
+ }
+
case Check: {
alreadyHandled = true;
m_interpreter.execute(indexInBlock);
diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
index 6d9d228..25b1ffc 100644
--- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
+++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp
@@ -178,6 +178,7 @@
case ToString:
case CallStringConstructor:
case NumberToStringWithRadix:
+ case NumberToStringWithValidRadixConstant:
case In:
case HasOwnProperty:
case Jump:
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index aa0be3c..1a3cbc9 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -1311,14 +1311,13 @@
// FIXME: This should be done in the ByteCodeParser based on reading the
// PolymorphicAccess, which will surely tell us that this is a AccessCase::ArrayLength.
// https://bugs.webkit.org/show_bug.cgi?id=154990
+ auto uid = m_graph.identifiers()[node->identifierNumber()];
if (node->child1()->shouldSpeculateCellOrOther()
&& !m_graph.hasExitSite(node->origin.semantic, BadType)
&& !m_graph.hasExitSite(node->origin.semantic, BadCache)
&& !m_graph.hasExitSite(node->origin.semantic, BadIndexingType)
&& !m_graph.hasExitSite(node->origin.semantic, ExoticObjectMode)) {
- auto uid = m_graph.identifiers()[node->identifierNumber()];
-
if (uid == vm().propertyNames->length.impl()) {
attemptToMakeGetArrayLength(node);
break;
@@ -1333,6 +1332,30 @@
}
}
+ if (node->child1()->shouldSpeculateNumber()) {
+ if (uid == vm().propertyNames->toString.impl()) {
+ if (m_graph.isWatchingNumberToStringWatchpoint(node)) {
+ JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic);
+ if (node->child1()->shouldSpeculateInt32()) {
+ insertCheck<Int32Use>(node->child1().node());
+ m_graph.convertToConstant(node, m_graph.freeze(globalObject->numberProtoToStringFunction()));
+ break;
+ }
+
+ if (enableInt52() && node->child1()->shouldSpeculateAnyInt()) {
+ insertCheck<Int52RepUse>(node->child1().node());
+ m_graph.convertToConstant(node, m_graph.freeze(globalObject->numberProtoToStringFunction()));
+ break;
+ }
+
+ ASSERT(node->child1()->shouldSpeculateNumber());
+ insertCheck<DoubleRepUse>(node->child1().node());
+ m_graph.convertToConstant(node, m_graph.freeze(globalObject->numberProtoToStringFunction()));
+ break;
+ }
+ }
+ }
+
if (node->child1()->shouldSpeculateCell())
fixEdge<CellUse>(node->child1());
break;
@@ -2022,6 +2045,7 @@
case PutByValWithThis:
case GetByValWithThis:
case CompareEqPtr:
+ case NumberToStringWithValidRadixConstant:
break;
#else
default:
diff --git a/Source/JavaScriptCore/dfg/DFGGraph.h b/Source/JavaScriptCore/dfg/DFGGraph.h
index 6630815..5c39c17 100644
--- a/Source/JavaScriptCore/dfg/DFGGraph.h
+++ b/Source/JavaScriptCore/dfg/DFGGraph.h
@@ -730,10 +730,8 @@
return watchpoints().isWatched(globalObject->havingABadTimeWatchpoint());
}
- bool isWatchingArrayIteratorProtocolWatchpoint(Node* node)
+ bool isWatchingGlobalObjectWatchpoint(JSGlobalObject* globalObject, InlineWatchpointSet& set)
{
- JSGlobalObject* globalObject = globalObjectFor(node->origin.semantic);
- InlineWatchpointSet& set = globalObject->arrayIteratorProtocolWatchpoint();
if (watchpoints().isWatched(set))
return true;
@@ -749,6 +747,20 @@
return false;
}
+
+ bool isWatchingArrayIteratorProtocolWatchpoint(Node* node)
+ {
+ JSGlobalObject* globalObject = globalObjectFor(node->origin.semantic);
+ InlineWatchpointSet& set = globalObject->arrayIteratorProtocolWatchpoint();
+ return isWatchingGlobalObjectWatchpoint(globalObject, set);
+ }
+
+ bool isWatchingNumberToStringWatchpoint(Node* node)
+ {
+ JSGlobalObject* globalObject = globalObjectFor(node->origin.semantic);
+ InlineWatchpointSet& set = globalObject->numberToStringWatchpoint();
+ return isWatchingGlobalObjectWatchpoint(globalObject, set);
+ }
Profiler::Compilation* compilation() { return m_plan.compilation.get(); }
diff --git a/Source/JavaScriptCore/dfg/DFGNode.h b/Source/JavaScriptCore/dfg/DFGNode.h
index be5762b..2e3585d 100644
--- a/Source/JavaScriptCore/dfg/DFGNode.h
+++ b/Source/JavaScriptCore/dfg/DFGNode.h
@@ -691,6 +691,15 @@
children.setChild2(Edge());
m_opInfo = cell;
}
+
+ void convertToNumberToStringWithValidRadixConstant(int32_t radix)
+ {
+ ASSERT(m_op == NumberToStringWithRadix);
+ ASSERT(2 <= radix && radix <= 36);
+ setOpAndDefaultFlags(NumberToStringWithValidRadixConstant);
+ children.setChild2(Edge());
+ m_opInfo = radix;
+ }
void convertToDirectCall(FrozenValue*);
@@ -2583,6 +2592,17 @@
return m_opInfo.as<BucketOwnerType>();
}
+ bool hasValidRadixConstant()
+ {
+ return op() == NumberToStringWithValidRadixConstant;
+ }
+
+ int32_t validRadixConstant()
+ {
+ ASSERT(hasValidRadixConstant());
+ return m_opInfo.as<int32_t>();
+ }
+
uint32_t errorType()
{
ASSERT(op() == ThrowStaticError);
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index e9f076c..a2cb823 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -354,6 +354,7 @@
macro(CallObjectConstructor, NodeResultJS) \
macro(CallStringConstructor, NodeResultJS | NodeMustGenerate) \
macro(NumberToStringWithRadix, NodeResultJS | NodeMustGenerate) \
+ macro(NumberToStringWithValidRadixConstant, NodeResultJS) \
macro(NewStringObject, NodeResultJS) \
macro(MakeRope, NodeResultJS) \
macro(In, NodeResultBoolean | NodeMustGenerate) \
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index e55b1f7..1088995 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -912,6 +912,7 @@
case CallStringConstructor:
case ToString:
case NumberToStringWithRadix:
+ case NumberToStringWithValidRadixConstant:
case MakeRope:
case StrCat: {
setPrediction(SpecString);
diff --git a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
index ad76b33..eae374b 100644
--- a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
+++ b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h
@@ -288,6 +288,7 @@
case ToString:
case ToNumber:
case NumberToStringWithRadix:
+ case NumberToStringWithValidRadixConstant:
case SetFunctionName:
case StrCat:
case CallStringConstructor:
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index c202908..db8c036 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -8353,7 +8353,7 @@
case Int32Use:
case Int52RepUse:
case DoubleRepUse:
- compileToStringOrCallStringConstructorOnNumber(node);
+ compileNumberToStringWithValidRadixConstant(node, 10);
return;
default:
@@ -8434,11 +8434,16 @@
}
}
-void SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber(Node* node)
+void SpeculativeJIT::compileNumberToStringWithValidRadixConstant(Node* node)
+{
+ compileNumberToStringWithValidRadixConstant(node, node->validRadixConstant());
+}
+
+void SpeculativeJIT::compileNumberToStringWithValidRadixConstant(Node* node, int32_t radix)
{
auto callToString = [&] (auto operation, GPRReg resultGPR, auto valueReg) {
flushRegisters();
- callOperation(operation, resultGPR, valueReg, CCallHelpers::TrustedImm32(10));
+ callOperation(operation, resultGPR, valueReg, TrustedImm32(radix));
m_jit.exceptionCheck();
cellResult(resultGPR, node);
};
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
index f34fd46..328cbb1 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
@@ -2766,8 +2766,9 @@
void emitSwitch(Node*);
void compileToStringOrCallStringConstructor(Node*);
- void compileToStringOrCallStringConstructorOnNumber(Node*);
void compileNumberToStringWithRadix(Node*);
+ void compileNumberToStringWithValidRadixConstant(Node*);
+ void compileNumberToStringWithValidRadixConstant(Node*, int32_t radix);
void compileNewStringObject(Node*);
void compileNewTypedArray(Node*);
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index 8465de2..80710ae 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -2843,6 +2843,11 @@
break;
}
+ case NumberToStringWithValidRadixConstant: {
+ compileNumberToStringWithValidRadixConstant(node);
+ break;
+ }
+
case GetByValWithThis: {
JSValueOperand base(this, node->child1());
JSValueRegs baseRegs = base.jsValueRegs();
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index c2fdce9..309c47b 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -5303,6 +5303,11 @@
break;
}
+ case NumberToStringWithValidRadixConstant: {
+ compileNumberToStringWithValidRadixConstant(node);
+ break;
+ }
+
case IsObject: {
JSValueOperand value(this, node->child1());
GPRTemporary result(this, Reuse, value);
diff --git a/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp b/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
index 65afb4a..60092b6 100644
--- a/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGStrengthReductionPhase.cpp
@@ -426,6 +426,19 @@
break;
}
+ case NumberToStringWithValidRadixConstant: {
+ Edge& child1 = m_node->child1();
+ if (child1->hasConstant()) {
+ JSValue value = child1->constant()->value();
+ if (value && value.isNumber()) {
+ String result = toStringWithRadix(value.asNumber(), m_node->validRadixConstant());
+ m_node->convertToLazyJSConstant(m_graph, LazyJSValue::newString(m_graph, result));
+ m_changed = true;
+ }
+ }
+ break;
+ }
+
case GetArrayLength: {
if (m_node->arrayMode().type() == Array::Generic
|| m_node->arrayMode().type() == Array::String) {
diff --git a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
index c215a63..d1fa639 100644
--- a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
+++ b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp
@@ -284,6 +284,7 @@
case DefineAccessorProperty:
case ToLowerCase:
case NumberToStringWithRadix:
+ case NumberToStringWithValidRadixConstant:
case CheckSubClass:
case CallDOM:
case CallDOMGetter:
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index e7b2462..12c043e 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -1123,6 +1123,9 @@
case NumberToStringWithRadix:
compileNumberToStringWithRadix();
break;
+ case NumberToStringWithValidRadixConstant:
+ compileNumberToStringWithValidRadixConstant();
+ break;
case CheckSubClass:
compileCheckSubClass();
break;
@@ -10419,6 +10422,23 @@
}
}
+ void compileNumberToStringWithValidRadixConstant()
+ {
+ switch (m_node->child1().useKind()) {
+ case Int32Use:
+ setJSValue(vmCall(pointerType(), m_out.operation(operationInt32ToStringWithValidRadix), m_callFrame, lowInt32(m_node->child1()), m_out.constInt32(m_node->validRadixConstant())));
+ break;
+ case Int52RepUse:
+ setJSValue(vmCall(pointerType(), m_out.operation(operationInt52ToStringWithValidRadix), m_callFrame, lowStrictInt52(m_node->child1()), m_out.constInt32(m_node->validRadixConstant())));
+ break;
+ case DoubleRepUse:
+ setJSValue(vmCall(pointerType(), m_out.operation(operationDoubleToStringWithValidRadix), m_callFrame, lowDouble(m_node->child1()), m_out.constInt32(m_node->validRadixConstant())));
+ break;
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+ }
+
void compileResolveScopeForHoistingFuncDeclInEval()
{
UniquedStringImpl* uid = m_graph.identifiers()[m_node->identifierNumber()];
diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp
index f916a46..0c3a765 100644
--- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp
@@ -325,6 +325,7 @@
, m_mapSetWatchpoint(IsWatched)
, m_setAddWatchpoint(IsWatched)
, m_arraySpeciesWatchpoint(ClearWatchpoint)
+ , m_numberToStringWatchpoint(IsWatched)
, m_templateRegistry(vm)
, m_runtimeFlags()
, m_globalObjectMethodTable(globalObjectMethodTable ? globalObjectMethodTable : &s_globalObjectMethodTable)
@@ -1015,6 +1016,13 @@
m_setPrototypeAddWatchpoint = std::make_unique<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>>(vm, condition, m_setAddWatchpoint);
m_setPrototypeAddWatchpoint->install();
}
+
+ {
+ ObjectPropertyCondition condition = setupAdaptiveWatchpoint(numberPrototype(), m_vm.propertyNames->toString);
+ m_numberPrototypeToStringWatchpoint = std::make_unique<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>>(vm, condition, m_numberToStringWatchpoint);
+ m_numberPrototypeToStringWatchpoint->install();
+ m_numberProtoToStringFunction.set(vm, this, jsCast<JSFunction*>(numberPrototype()->getDirect(vm, vm.propertyNames->toString)));
+ }
}
resetPrototype(vm, getPrototypeDirect());
@@ -1264,6 +1272,7 @@
thisObject->m_iteratorProtocolFunction.visit(visitor);
thisObject->m_promiseResolveFunction.visit(visitor);
visitor.append(thisObject->m_objectProtoValueOfFunction);
+ visitor.append(thisObject->m_numberProtoToStringFunction);
visitor.append(thisObject->m_newPromiseCapabilityFunction);
visitor.append(thisObject->m_functionProtoHasInstanceSymbolFunction);
thisObject->m_throwTypeErrorGetterSetter.visit(visitor);
diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.h b/Source/JavaScriptCore/runtime/JSGlobalObject.h
index 6c29d1a..1fb0e96 100644
--- a/Source/JavaScriptCore/runtime/JSGlobalObject.h
+++ b/Source/JavaScriptCore/runtime/JSGlobalObject.h
@@ -269,6 +269,7 @@
LazyProperty<JSGlobalObject, JSFunction> m_iteratorProtocolFunction;
LazyProperty<JSGlobalObject, JSFunction> m_promiseResolveFunction;
WriteBarrier<JSFunction> m_objectProtoValueOfFunction;
+ WriteBarrier<JSFunction> m_numberProtoToStringFunction;
WriteBarrier<JSFunction> m_newPromiseCapabilityFunction;
WriteBarrier<JSFunction> m_functionProtoHasInstanceSymbolFunction;
LazyProperty<JSGlobalObject, GetterSetter> m_throwTypeErrorGetterSetter;
@@ -413,6 +414,7 @@
InlineWatchpointSet& mapSetWatchpoint() { return m_mapSetWatchpoint; }
InlineWatchpointSet& setAddWatchpoint() { return m_setAddWatchpoint; }
InlineWatchpointSet& arraySpeciesWatchpoint() { return m_arraySpeciesWatchpoint; }
+ InlineWatchpointSet& numberToStringWatchpoint() { return m_numberToStringWatchpoint; }
// If this hasn't been invalidated, it means the array iterator protocol
// is not observable to user code yet.
InlineWatchpointSet m_arrayIteratorProtocolWatchpoint;
@@ -422,6 +424,7 @@
InlineWatchpointSet m_mapSetWatchpoint;
InlineWatchpointSet m_setAddWatchpoint;
InlineWatchpointSet m_arraySpeciesWatchpoint;
+ InlineWatchpointSet m_numberToStringWatchpoint;
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_arrayPrototypeSymbolIteratorWatchpoint;
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_arrayIteratorPrototypeNext;
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_mapPrototypeSymbolIteratorWatchpoint;
@@ -432,6 +435,7 @@
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_stringIteratorPrototypeNextWatchpoint;
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_mapPrototypeSetWatchpoint;
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_setPrototypeAddWatchpoint;
+ std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_numberPrototypeToStringWatchpoint;
bool isArrayPrototypeIteratorProtocolFastAndNonObservable();
bool isMapPrototypeIteratorProtocolFastAndNonObservable();
@@ -542,6 +546,7 @@
JSFunction* iteratorProtocolFunction() const { return m_iteratorProtocolFunction.get(this); }
JSFunction* promiseResolveFunction() const { return m_promiseResolveFunction.get(this); }
JSFunction* objectProtoValueOfFunction() const { return m_objectProtoValueOfFunction.get(); }
+ JSFunction* numberProtoToStringFunction() const { return m_numberProtoToStringFunction.get(); }
JSFunction* newPromiseCapabilityFunction() const { return m_newPromiseCapabilityFunction.get(); }
JSFunction* functionProtoHasInstanceSymbolFunction() const { return m_functionProtoHasInstanceSymbolFunction.get(); }
JSObject* regExpProtoExecFunction() const { return m_regExpProtoExec.get(); }
diff --git a/Source/JavaScriptCore/runtime/NumberPrototype.cpp b/Source/JavaScriptCore/runtime/NumberPrototype.cpp
index 0a7441b..75fcee3 100644
--- a/Source/JavaScriptCore/runtime/NumberPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/NumberPrototype.cpp
@@ -58,7 +58,6 @@
/* Source for NumberPrototype.lut.h
@begin numberPrototypeTable
- toString numberProtoFuncToString DontEnum|Function 1 NumberPrototypeToStringIntrinsic
toLocaleString numberProtoFuncToLocaleString DontEnum|Function 0
valueOf numberProtoFuncValueOf DontEnum|Function 0
toFixed numberProtoFuncToFixed DontEnum|Function 1
@@ -79,10 +78,9 @@
Base::finishCreation(vm);
setInternalValue(vm, jsNumber(0));
+ JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, numberProtoFuncToString, DontEnum, 1, NumberPrototypeToStringIntrinsic);
#if ENABLE(INTL)
JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("toLocaleString", numberPrototypeToLocaleStringCodeGenerator, DontEnum);
-#else
- UNUSED_PARAM(globalObject);
#endif // ENABLE(INTL)
ASSERT(inherits(vm, info()));
@@ -165,7 +163,7 @@
return startOfResultString;
}
-static char* toStringWithRadix(RadixBuffer& buffer, double originalNumber, unsigned radix)
+static char* toStringWithRadixInternal(RadixBuffer& buffer, double originalNumber, unsigned radix)
{
ASSERT(std::isfinite(originalNumber));
ASSERT(radix >= 2 && radix <= 36);
@@ -359,7 +357,7 @@
return startOfResultString;
}
-static String toStringWithRadix(int32_t number, unsigned radix)
+static String toStringWithRadixInternal(int32_t number, unsigned radix)
{
LChar buf[1 + 32]; // Worst case is radix == 2, which gives us 32 digits + sign.
LChar* end = std::end(buf);
@@ -384,6 +382,21 @@
return String(p, static_cast<unsigned>(end - p));
}
+String toStringWithRadix(double doubleValue, int32_t radix)
+{
+ ASSERT(2 <= radix && radix <= 36);
+
+ int32_t integerValue = static_cast<int32_t>(doubleValue);
+ if (integerValue == doubleValue)
+ return toStringWithRadixInternal(integerValue, radix);
+
+ if (radix == 10 || !std::isfinite(doubleValue))
+ return String::numberToStringECMAScript(doubleValue);
+
+ RadixBuffer buffer;
+ return toStringWithRadixInternal(buffer, doubleValue, radix);
+}
+
// toExponential converts a number to a string, always formatting as an exponential.
// This method takes an optional argument specifying a number of *decimal places*
// to round the significand to (or, put another way, this method optionally rounds
@@ -520,7 +533,7 @@
if (radix == 10)
return jsNontrivialString(&vm, vm.numericStrings.add(value));
- return jsNontrivialString(&vm, toStringWithRadix(value, radix));
+ return jsNontrivialString(&vm, toStringWithRadixInternal(value, radix));
}
@@ -539,7 +552,7 @@
return jsNontrivialString(&vm, String::numberToStringECMAScript(doubleValue));
RadixBuffer buffer;
- return jsString(&vm, toStringWithRadix(buffer, doubleValue, radix));
+ return jsString(&vm, toStringWithRadixInternal(buffer, doubleValue, radix));
}
JSString* int32ToString(VM& vm, int32_t value, int32_t radix)
diff --git a/Source/JavaScriptCore/runtime/NumberPrototype.h b/Source/JavaScriptCore/runtime/NumberPrototype.h
index 3c069cb..039b0d5 100644
--- a/Source/JavaScriptCore/runtime/NumberPrototype.h
+++ b/Source/JavaScriptCore/runtime/NumberPrototype.h
@@ -54,5 +54,6 @@
JSString* int32ToString(VM&, int32_t value, int32_t radix);
JSString* int52ToString(VM&, int64_t value, int32_t radix);
JSString* numberToString(VM&, double value, int32_t radix);
+String toStringWithRadix(double doubleValue, int32_t radix);
} // namespace JSC