Commit 1fb752ad authored by fpizlo@apple.com's avatar fpizlo@apple.com
Browse files

FloatTypedArrayAdaptor::toJSValue should almost certainly not use jsNumber()...

FloatTypedArrayAdaptor::toJSValue should almost certainly not use jsNumber() since that attempts int conversions
https://bugs.webkit.org/show_bug.cgi?id=120228

Source/JavaScriptCore: 

Reviewed by Oliver Hunt.
        
It turns out that there were three problems:
        
- Using jsNumber() meant that we were converting doubles to integers and then
  possibly back again whenever doing a set() between floating point arrays.
        
- Slow-path accesses to double typed arrays were slower than necessary because
  of the to-int conversion attempt.
        
- The use of JSValue as an intermediate for converting between differen types
  in typedArray.set() resulted in worse code than I had previously expected.
        
This patch solves the problem by using template double-dispatch to ensure that
that C++ compiler sees the simplest possible combination of casts between any
combination of typed array types, while still preserving JS and typed array
conversion semantics. Conversions are done as follows:
        
    SourceAdaptor::convertTo<TargetAdaptor>(value)
        
Internally, convertTo() calls one of three possible methods on TargetAdaptor,
with one method for each of int32_t, uint32_t, and double. This means that the
C++ compiler will at worst see a widening cast to one of those types followed
by a narrowing conversion (not necessarily a cast - may have clamping or the
JS toInt32() function).
        
This change doesn't just affect typedArray.set(); it also affects slow-path
accesses to typed arrays as well. This patch also adds a bunch of new test
coverage.
        
This change is a ~50% speed-up on typedArray.set() involving floating point
types.

* GNUmakefile.list.am:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* runtime/GenericTypedArrayView.h:
(JSC::GenericTypedArrayView::set):
* runtime/JSDataViewPrototype.cpp:
(JSC::setData):
* runtime/JSGenericTypedArrayView.h:
(JSC::JSGenericTypedArrayView::setIndexQuicklyToDouble):
(JSC::JSGenericTypedArrayView::setIndexQuickly):
* runtime/JSGenericTypedArrayViewInlines.h:
(JSC::::setWithSpecificType):
(JSC::::set):
* runtime/ToNativeFromValue.h: Added.
(JSC::toNativeFromValue):
* runtime/TypedArrayAdaptors.h:
(JSC::IntegralTypedArrayAdaptor::toJSValue):
(JSC::IntegralTypedArrayAdaptor::toDouble):
(JSC::IntegralTypedArrayAdaptor::toNativeFromInt32):
(JSC::IntegralTypedArrayAdaptor::toNativeFromUint32):
(JSC::IntegralTypedArrayAdaptor::toNativeFromDouble):
(JSC::IntegralTypedArrayAdaptor::convertTo):
(JSC::FloatTypedArrayAdaptor::toJSValue):
(JSC::FloatTypedArrayAdaptor::toDouble):
(JSC::FloatTypedArrayAdaptor::toNativeFromInt32):
(JSC::FloatTypedArrayAdaptor::toNativeFromUint32):
(JSC::FloatTypedArrayAdaptor::toNativeFromDouble):
(JSC::FloatTypedArrayAdaptor::convertTo):
(JSC::Uint8ClampedAdaptor::toJSValue):
(JSC::Uint8ClampedAdaptor::toDouble):
(JSC::Uint8ClampedAdaptor::toNativeFromInt32):
(JSC::Uint8ClampedAdaptor::toNativeFromUint32):
(JSC::Uint8ClampedAdaptor::toNativeFromDouble):
(JSC::Uint8ClampedAdaptor::convertTo):

LayoutTests: 

Reviewed by Oliver Hunt.
        
Add coverage for three things:
        
- Typed array accesses with corner-case values.
        
- Typed array set() (i.e. copy) between arrays of different types.
        
- Performance of typedArray.set() involving different types.
        
This required some changes to our test harnesses, since they previously
couldn't consistently do numerical array comparisons in a reliable way.

* fast/js/regress/Float32Array-to-Float64Array-set-expected.txt: Added.
* fast/js/regress/Float32Array-to-Float64Array-set.html: Added.
* fast/js/regress/Float64Array-to-Int16Array-set-expected.txt: Added.
* fast/js/regress/Float64Array-to-Int16Array-set.html: Added.
* fast/js/regress/Int16Array-to-Int32Array-set-expected.txt: Added.
* fast/js/regress/Int16Array-to-Int32Array-set.html: Added.
* fast/js/regress/script-tests/Float32Array-to-Float64Array-set.js: Added.
* fast/js/regress/script-tests/Float64Array-to-Int16Array-set.js: Added.
* fast/js/regress/script-tests/Int16Array-to-Int32Array-set.js: Added.
* fast/js/resources/js-test-pre.js:
(areNumbersEqual):
(areArraysEqual):
(isResultCorrect):
* fast/js/resources/standalone-pre.js:
(areNumbersEqual):
(areArraysEqual):
(isTypedArray):
(isResultCorrect):
(stringify):
(shouldBe):
* fast/js/script-tests/typed-array-access.js: Added.
(bitsToString):
(bitsToValue):
(valueToBits):
(roundTrip):
* fast/js/script-tests/typed-array-set-different-types.js: Added.
(MyRandom):
(.reference):
(.usingConstruct):
* fast/js/typed-array-access-expected.txt: Added.
* fast/js/typed-array-access.html: Added.
* fast/js/typed-array-set-different-types-expected.txt: Added.
* fast/js/typed-array-set-different-types.html: Added.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@154569 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 57ac83d3
2013-08-24 Filip Pizlo <fpizlo@apple.com>
FloatTypedArrayAdaptor::toJSValue should almost certainly not use jsNumber() since that attempts int conversions
https://bugs.webkit.org/show_bug.cgi?id=120228
Reviewed by Oliver Hunt.
Add coverage for three things:
- Typed array accesses with corner-case values.
- Typed array set() (i.e. copy) between arrays of different types.
- Performance of typedArray.set() involving different types.
This required some changes to our test harnesses, since they previously
couldn't consistently do numerical array comparisons in a reliable way.
* fast/js/regress/Float32Array-to-Float64Array-set-expected.txt: Added.
* fast/js/regress/Float32Array-to-Float64Array-set.html: Added.
* fast/js/regress/Float64Array-to-Int16Array-set-expected.txt: Added.
* fast/js/regress/Float64Array-to-Int16Array-set.html: Added.
* fast/js/regress/Int16Array-to-Int32Array-set-expected.txt: Added.
* fast/js/regress/Int16Array-to-Int32Array-set.html: Added.
* fast/js/regress/script-tests/Float32Array-to-Float64Array-set.js: Added.
* fast/js/regress/script-tests/Float64Array-to-Int16Array-set.js: Added.
* fast/js/regress/script-tests/Int16Array-to-Int32Array-set.js: Added.
* fast/js/resources/js-test-pre.js:
(areNumbersEqual):
(areArraysEqual):
(isResultCorrect):
* fast/js/resources/standalone-pre.js:
(areNumbersEqual):
(areArraysEqual):
(isTypedArray):
(isResultCorrect):
(stringify):
(shouldBe):
* fast/js/script-tests/typed-array-access.js: Added.
(bitsToString):
(bitsToValue):
(valueToBits):
(roundTrip):
* fast/js/script-tests/typed-array-set-different-types.js: Added.
(MyRandom):
(.reference):
(.usingConstruct):
* fast/js/typed-array-access-expected.txt: Added.
* fast/js/typed-array-access.html: Added.
* fast/js/typed-array-set-different-types-expected.txt: Added.
* fast/js/typed-array-set-different-types.html: Added.
2013-08-25 Ryuan Choi <ryuan.choi@samsung.com>
 
Unreviewed EFL gardening.
JSRegress/Float32Array-to-Float64Array-set
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS no exception thrown
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test-pre.js"></script>
</head>
<body>
<script src="resources/regress-pre.js"></script>
<script src="script-tests/Float32Array-to-Float64Array-set.js"></script>
<script src="resources/regress-post.js"></script>
<script src="../resources/js-test-post.js"></script>
</body>
</html>
JSRegress/Float64Array-to-Int16Array-set
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS no exception thrown
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test-pre.js"></script>
</head>
<body>
<script src="resources/regress-pre.js"></script>
<script src="script-tests/Float64Array-to-Int16Array-set.js"></script>
<script src="resources/regress-post.js"></script>
<script src="../resources/js-test-post.js"></script>
</body>
</html>
JSRegress/Int16Array-to-Int32Array-set
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS no exception thrown
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test-pre.js"></script>
</head>
<body>
<script src="resources/regress-pre.js"></script>
<script src="script-tests/Int16Array-to-Int32Array-set.js"></script>
<script src="resources/regress-post.js"></script>
<script src="../resources/js-test-post.js"></script>
</body>
</html>
(function() {
var result = 0;
for (var i = 0; i < 100000; ++i) {
var array1 = new Float32Array(15);
for (var j = 0; j < array1.length; ++j)
array1[j] = i - j;
var array2 = new Float64Array(15);
for (var j = 0; j < 10; ++j)
array2.set(array1);
for (var j = 0; j < array2.length; ++j)
result += array2[j];
}
if (result != 74988750000)
throw "Error: bad result: " + result;
})();
(function() {
var result = 0;
for (var i = 0; i < 100000; ++i) {
var array1 = new Float64Array(15);
for (var j = 0; j < array1.length; ++j)
array1[j] = i - j;
var array2 = new Int16Array(15);
for (var j = 0; j < 10; ++j)
array2.set(array1);
for (var j = 0; j < array2.length; ++j)
result += array2[j];
}
if (result != 7243531440)
throw "Error: bad result: " + result;
})();
(function() {
var result = 0;
for (var i = 0; i < 100000; ++i) {
var array1 = new Int16Array(15);
for (var j = 0; j < array1.length; ++j)
array1[j] = i - j;
var array2 = new Int32Array(15);
for (var j = 0; j < 10; ++j)
array2.set(array1);
for (var j = 0; j < array2.length; ++j)
result += array2[j];
}
if (result != 7243531440)
throw "Error: bad result: " + result;
})();
......@@ -112,13 +112,24 @@ function testFailed(msg)
debug('<span><span class="fail">FAIL</span> ' + escapeHTML(msg) + '</span>');
}
function areNumbersEqual(_actual, _expected)
{
if (_expected === 0)
return _actual === _expected && (1/_actual) === (1/_expected);
if (_actual === _expected)
return true;
if (typeof(_expected) == "number" && isNaN(_expected))
return typeof(_actual) == "number" && isNaN(_actual);
return false;
}
function areArraysEqual(_a, _b)
{
try {
if (_a.length !== _b.length)
return false;
for (var i = 0; i < _a.length; i++)
if (_a[i] !== _b[i])
if (!areNumbersEqual(_a[i], _b[i]))
return false;
} catch (ex) {
return false;
......@@ -148,12 +159,8 @@ function isTypedArray(array)
function isResultCorrect(_actual, _expected)
{
if (_expected === 0)
return _actual === _expected && (1/_actual) === (1/_expected);
if (_actual === _expected)
if (areNumbersEqual(_actual, _expected))
return true;
if (typeof(_expected) == "number" && isNaN(_expected))
return typeof(_actual) == "number" && isNaN(_actual);
if (_expected
&& (Object.prototype.toString.call(_expected) ==
Object.prototype.toString.call([])
......
......@@ -30,15 +30,28 @@ function testFailed(msg)
print("FAIL", escapeString(msg));
}
function areNumbersEqual(_actual, _expected)
{
if (_expected === 0)
return _actual === _expected && (1/_actual) === (1/_expected);
if (_actual === _expected)
return true;
if (typeof(_expected) == "number" && isNaN(_expected))
return typeof(_actual) == "number" && isNaN(_actual);
return false;
}
function areArraysEqual(_a, _b)
{
if (Object.prototype.toString.call(_a) != Object.prototype.toString.call([]))
return false;
if (_a.length !== _b.length)
return false;
for (var i = 0; i < _a.length; i++)
if (_a[i] !== _b[i])
try {
if (_a.length !== _b.length)
return false;
for (var i = 0; i < _a.length; i++)
if (!areNumbersEqual(_a[i], _b[i]))
return false;
} catch (ex) {
return false;
}
return true;
}
......@@ -49,15 +62,27 @@ function isMinusZero(n)
return n === 0 && 1/n < 0;
}
function isTypedArray(array)
{
return array instanceof Int8Array
|| array instanceof Int16Array
|| array instanceof Int32Array
|| array instanceof Uint8Array
|| array instanceof Uint8ClampedArray
|| array instanceof Uint16Array
|| array instanceof Uint32Array
|| array instanceof Float32Array
|| array instanceof Float64Array;
}
function isResultCorrect(_actual, _expected)
{
if (_expected === 0)
return _actual === _expected && (1/_actual) === (1/_expected);
if (_actual === _expected)
if (areNumbersEqual(_actual, _expected))
return true;
if (typeof(_expected) == "number" && isNaN(_expected))
return typeof(_actual) == "number" && isNaN(_actual);
if (Object.prototype.toString.call(_expected) == Object.prototype.toString.call([]))
if (_expected
&& (Object.prototype.toString.call(_expected) ==
Object.prototype.toString.call([])
|| isTypedArray(_expected)))
return areArraysEqual(_actual, _expected);
return false;
}
......@@ -66,7 +91,10 @@ function stringify(v)
{
if (v === 0 && 1/v < 0)
return "-0";
else return "" + v;
else if (isTypedArray(v))
return v.__proto__.constructor.name + ":[" + Array.prototype.join.call(v, ",") + "]";
else
return "" + v;
}
function shouldBe(_a, _b)
......@@ -83,13 +111,13 @@ function shouldBe(_a, _b)
var _bv = eval(_b);
if (exception)
testFailed(_a + " should be " + _bv + ". Threw exception " + exception);
testFailed(_a + " should be " + stringify(_bv) + ". Threw exception " + exception);
else if (isResultCorrect(_av, _bv))
testPassed(_a + " is " + _b);
else if (typeof(_av) == typeof(_bv))
testFailed(_a + " should be " + _bv + ". Was " + stringify(_av) + ".");
testFailed(_a + " should be " + stringify(_bv) + ". Was " + stringify(_av) + ".");
else
testFailed(_a + " should be " + _bv + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ").");
testFailed(_a + " should be " + stringify(_bv) + " (of type " + typeof _bv + "). Was " + _av + " (of type " + typeof _av + ").");
}
function shouldBeTrue(_a) { shouldBe(_a, "true"); }
......
description(
"Tests that accesses to typed arrays handle value conversions correctly."
);
// FIXME: This test assumes little-endian. It would take some hackage to convert
// it to a big-endian test.
function bitsToString(array) {
return Array.prototype.join.call(array, ":");
}
// Call this like so:
//
// bitsToValue(type, expectedJavascriptValue, first32Bits, second32Bits)
//
// Note that the second32Bits are optional.
//
// Where 'type' is for example "Int32".
function bitsToValue(type, expected) {
var baseArray = new Int32Array(arguments.length - 2);
for (var i = 0; i < arguments.length - 2; ++i)
baseArray[i] = arguments[i + 2];
var actual = (new this[type + "Array"](baseArray.buffer, 0, 1))[0];
if (isResultCorrect(actual, expected))
testPassed("Reading bit pattern " + bitsToString(baseArray) + " with " + type + " resulted in " + stringify(actual));
else
testFailed("Reading bit pattern " + bitsToString(baseArray) + " with " + type + " should result in " + stringify(expected) + " but instead was " + stringify(actual));
}
// Same signature as bitsToValue, except that the meaning is reversed.
function valueToBits(type, input) {
var buffer = new ArrayBuffer((arguments.length - 2) * 4);
var baseArray = new this[type + "Array"](buffer);
baseArray[0] = input;
var expectedArray = new Int32Array(arguments.length - 2);
for (var i = 0; i < arguments.length - 2; ++i)
expectedArray[i] = arguments[i + 2];
var actualArray = new Int32Array(buffer);
if (isResultCorrect(actualArray, expectedArray))
testPassed("Writing the value " + stringify(input) + " with type " + type + " results in bit pattern " + bitsToString(actualArray));
else
testFailed("Writing the value " + stringify(input) + " with type " + type + " should result in bit pattern " + bitsToString(expectedArray) + " but instead was " + bitsToString(actualArray));
}
// Test that both directions work.
function roundTrip() {
bitsToValue.apply(this, arguments);
valueToBits.apply(this, arguments);
}
roundTrip("Int8", 0, 0);
roundTrip("Int8", 42, 42);
roundTrip("Int8", -1, 255);
bitsToValue("Int8", 112, -400);
bitsToValue("Int8", -112, 400);
valueToBits("Int8", 1000, 232);
valueToBits("Int8", -1000, 24);
valueToBits("Int8", 0.5, 0);
valueToBits("Int8", -0.5, 0);
valueToBits("Int8", -1.5, 255);
valueToBits("Int8", 6326266464264213, 21);
valueToBits("Int8", -6326266464264213, 235);
roundTrip("Uint8", 0, 0);
roundTrip("Uint8", 42, 42);
roundTrip("Uint8", 255, 255);
bitsToValue("Uint8", 144, 400);
bitsToValue("Uint8", 112, -400);
valueToBits("Uint8", 1000, 232);
valueToBits("Uint8", -1000, 24);
valueToBits("Uint8", 0.5, 0);
valueToBits("Uint8", -0.5, 0);
valueToBits("Uint8", -1.5, 255);
valueToBits("Uint8", 6326266464264213, 21);
valueToBits("Uint8", -6326266464264213, 235);
roundTrip("Uint8Clamped", 0, 0);
roundTrip("Uint8Clamped", 42, 42);
roundTrip("Uint8Clamped", 255, 255);
bitsToValue("Uint8Clamped", 144, 400);
bitsToValue("Uint8Clamped", 112, -400);
valueToBits("Uint8Clamped", 1000, 255);
valueToBits("Uint8Clamped", -1000, 0);
valueToBits("Uint8Clamped", 0.5, 0);
valueToBits("Uint8Clamped", -0.5, 0);
valueToBits("Uint8Clamped", -1.5, 0);
valueToBits("Uint8Clamped", 6326266464264213, 255);
valueToBits("Uint8Clamped", -6326266464264213, 0);
roundTrip("Int16", 0, 0);
roundTrip("Int16", 42, 42);
roundTrip("Int16", 400, 400);
roundTrip("Int16", -1, 65535);
roundTrip("Int16", -400, 65136);
roundTrip("Int16", 30901, 30901);
bitsToValue("Int16", 6784, 400000);
bitsToValue("Int16", -6784, -400000);
valueToBits("Int16", 100000, 34464);
valueToBits("Int16", -100000, 31072);
valueToBits("Int16", 0.5, 0);
valueToBits("Int16", -0.5, 0);
valueToBits("Int16", -1.5, 65535);
valueToBits("Int16", 6326266464264213, 24597);
valueToBits("Int16", -6326266464264213, 40939);
roundTrip("Uint16", 0, 0);
roundTrip("Uint16", 42, 42);
roundTrip("Uint16", 400, 400);
roundTrip("Uint16", 30901, 30901);
bitsToValue("Uint16", 6784, 400000);
bitsToValue("Uint16", 58752, -400000);
valueToBits("Int16", 100000, 34464);
valueToBits("Int16", -100000, 31072);
valueToBits("Uint16", -1, 65535);
valueToBits("Uint16", -400, 65136);
valueToBits("Uint16", 0.5, 0);
valueToBits("Uint16", 6326266464264213, 24597);
valueToBits("Uint16", -6326266464264213, 40939);
roundTrip("Int32", 0, 0);
roundTrip("Int32", 42, 42);
roundTrip("Int32", 1000000000, 1000000000);
roundTrip("Int32", -42, -42);
roundTrip("Int32", -1000000000, -1000000000);
valueToBits("Int32", 0.5, 0);
valueToBits("Int32", -0.5, 0);
valueToBits("Int32", -1.5, -1);
valueToBits("Int32", 6326266464264213, -1319411691);
valueToBits("Int32", -6326266464264213, 1319411691);
roundTrip("Uint32", 0, 0);
roundTrip("Uint32", 42, 42);
roundTrip("Uint32", 1000000000, 1000000000);
valueToBits("Uint32", -42, -42);
bitsToValue("Uint32", (-42)>>>0, -42);
valueToBits("Uint32", -1000000000, -1000000000);
bitsToValue("Uint32", (-1000000000)>>>0, -1000000000);
valueToBits("Uint32", 0.5, 0);
valueToBits("Uint32", -0.5, 0);
valueToBits("Uint32", -1.5, -1);
valueToBits("Uint32", 6326266464264213, -1319411691);
valueToBits("Uint32", -6326266464264213, 1319411691);
roundTrip("Float32", 0, 0);
roundTrip("Float32", 1.5, 1069547520);
valueToBits("Float32", 1000000000, 1315859240);
roundTrip("Float64", 0, 0, 0);
roundTrip("Float64", 1.5, 0, 1073217536);
roundTrip("Float64", 1000000000, 0, 1104006501);
description(
"Tests that typedArray.set(otherTypedArray) does the right conversions when dealing with different types."
);
function MyRandom(z, w) {
this.z = (z == null ? 42 : z);
this.w = (w == null ? 23 : w);
}
MyRandom.prototype.get = function() {
this.z = (36969 * (this.z & 65535) + (this.z >> 16)) | 0;
this.w = (18000 * (this.w & 65535) + (this.w >> 16)) | 0;
return ((this.z << 16) + this.w) | 0;
};
function testArraySet(fromType, toType) {
// This function tests type conversions of set() by asserting that they are
// equivalent to converting via JSValue.
var rand = new MyRandom();
function createIntArray() {
var numSequences = 3;
var numPerSequence = 4;
var masks = [255, 65535, -1];
var array = new fromType(numSequences * numPerSequence);
for (var i = 0; i < numSequences; ++i) {
for (var j = 0; j < numPerSequence; ++j)
array[i * numPerSequence + j] = rand.get() & masks[i];
}
return array;
}
function createFloatArray() {
var array = [0, 0/-1, 1, 42, 1.5, -2.5, 1/0, -1/0, 0/0];
for (var i = 3; i--;)
array.push(rand.get());
return new fromType(array);
}
var sourceArray;
if (fromType == Float32Array || fromType == Float64Array)
sourceArray = createFloatArray();
else
sourceArray = createIntArray();
function reference() {
var array = new toType(sourceArray.length);
for (var i = 0; i < sourceArray.length; ++i)
array[i] = sourceArray[i];
return array;
}
function usingConstruct() {
return new toType(sourceArray);
}
function usingSet() {
var array = new toType(sourceArray.length);
array.set(sourceArray);
return array;
}
var referenceArray = reference();
var actualConstructArray = usingConstruct();
if (isResultCorrect(referenceArray, actualConstructArray))
testPassed("Copying " + stringify(sourceArray) + " to an array of type " + toType.name + " using the constructor resulted in " + stringify(actualConstructArray));
else
testFailed("Copying " + stringify(sourceArray) + " to an array of type " + toType.name + " using the constructor should have resulted in " + stringify(referenceArray) + " but instead resulted in " + stringify(actualConstructArray));
var actualSetArray = usingSet();
if (isResultCorrect(referenceArray, actualSetArray))
testPassed("Copying " + stringify(sourceArray) + " to an array of type " + toType.name + " using typedArray.set() resulted in " + stringify(actualSetArray));
else
testFailed("Copying " + stringify(sourceArray) + " to an array of type " + toType.name + " using typedArray.set() should have resulted in " + stringify(referenceArray) + " but instead resulted in " + stringify(actualSetArray));
}
var types = [Int8Array