v8/test/mjsunit/harmony/atomics.js
binji 7b300ba2e9 [SAB] Validate index before value conversion using ToIndex
It's required by the spec -- and observable -- that the index be validated
before the conversion of the value(s) via ToInteger.

The previous implementation also had an old test for validating the atomic
index, which has now been switched to ToIndex.

This also exposed an issue in the ia32 code generator: cmpxchg_b requires a
byte register, but the ia32 instruction selector was ensuring that the
new_value was a byte register, not the TempRegister. This change forces the
temp register to use edx, which always can be used as a byte register (dl).
This is the same behavior as currently used in UseByteRegister.

BUG=v8:4614
R=jarin@chromium.org,jkummerow@chromium.org

Review-Url: https://codereview.chromium.org/2814753003
Cr-Commit-Position: refs/heads/master@{#44626}
2017-04-12 19:08:40 +00:00

598 lines
20 KiB
JavaScript

// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-sharedarraybuffer
//
function toRangeWrapped(value) {
var range = this.max - this.min + 1;
while (value < this.min) {
value += range;
}
while (value > this.max) {
value -= range;
}
return value;
}
function makeConstructorObject(constr, min, max, toRange) {
var o = {constr: constr, min: min, max: max};
o.toRange = toRangeWrapped.bind(o);
return o;
}
var IntegerTypedArrayConstructors = [
makeConstructorObject(Int8Array, -128, 127),
makeConstructorObject(Int16Array, -32768, 32767),
makeConstructorObject(Int32Array, -0x80000000, 0x7fffffff),
makeConstructorObject(Uint8Array, 0, 255),
makeConstructorObject(Uint16Array, 0, 65535),
makeConstructorObject(Uint32Array, 0, 0xffffffff),
];
(function TestBadArray() {
var ab = new ArrayBuffer(16);
var u32a = new Uint32Array(16);
var sab = new SharedArrayBuffer(128);
var sf32a = new Float32Array(sab);
var sf64a = new Float64Array(sab);
var u8ca = new Uint8ClampedArray(sab);
// Atomic ops required integer shared typed arrays
var badArrayTypes = [
undefined, 1, 'hi', 3.4, ab, u32a, sab, sf32a, sf64a, u8ca
];
badArrayTypes.forEach(function(o) {
assertThrows(function() { Atomics.compareExchange(o, 0, 0, 0); },
TypeError);
assertThrows(function() { Atomics.load(o, 0); }, TypeError);
assertThrows(function() { Atomics.store(o, 0, 0); }, TypeError);
assertThrows(function() { Atomics.add(o, 0, 0); }, TypeError);
assertThrows(function() { Atomics.sub(o, 0, 0); }, TypeError);
assertThrows(function() { Atomics.and(o, 0, 0); }, TypeError);
assertThrows(function() { Atomics.or(o, 0, 0); }, TypeError);
assertThrows(function() { Atomics.xor(o, 0, 0); }, TypeError);
assertThrows(function() { Atomics.exchange(o, 0, 0); }, TypeError);
});
})();
(function TestBadIndex() {
var sab = new SharedArrayBuffer(8);
var si32a = new Int32Array(sab);
var si32a2 = new Int32Array(sab, 4);
// Indexes that are out of bounds when coerced via ToIndex should throw
// RangeError.
[-Infinity, Infinity].forEach(function(i) {
assertThrows(function() { Atomics.compareExchange(si32a, i, 0); },
RangeError);
assertThrows(function() { Atomics.load(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.store(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.add(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.sub(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.and(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.or(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.xor(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.exchange(si32a, i, 0); }, RangeError);
}, RangeError);
// Out-of-bounds indexes should throw RangeError.
[-1, 2, 100].forEach(function(i) {
assertThrows(function() { Atomics.compareExchange(si32a, i, 0, 0); },
RangeError);
assertThrows(function() { Atomics.load(si32a, i); }, RangeError);
assertThrows(function() { Atomics.store(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.add(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.sub(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.and(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.or(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.xor(si32a, i, 0); }, RangeError);
assertThrows(function() { Atomics.exchange(si32a, i, 0); }, RangeError);
}, RangeError);
// Out-of-bounds indexes for array with offset should throw RangeError.
[-1, 1, 100].forEach(function(i) {
assertThrows(function() { Atomics.compareExchange(si32a2, i, 0, 0); });
assertThrows(function() { Atomics.load(si32a2, i); }, RangeError);
assertThrows(function() { Atomics.store(si32a2, i, 0); }, RangeError);
assertThrows(function() { Atomics.add(si32a2, i, 0); }, RangeError);
assertThrows(function() { Atomics.sub(si32a2, i, 0); }, RangeError);
assertThrows(function() { Atomics.and(si32a2, i, 0); }, RangeError);
assertThrows(function() { Atomics.or(si32a2, i, 0); }, RangeError);
assertThrows(function() { Atomics.xor(si32a2, i, 0); }, RangeError);
assertThrows(function() { Atomics.exchange(si32a2, i, 0); }, RangeError);
});
// Monkey-patch length and make sure these functions still throw.
Object.defineProperty(si32a, 'length', {get: function() { return 1000; }});
[2, 100].forEach(function(i) {
assertThrows(function() { Atomics.compareExchange(si32a, i, 0, 0); });
assertThrows(function() { Atomics.load(si32a, i); });
assertThrows(function() { Atomics.store(si32a, i, 0); });
assertThrows(function() { Atomics.add(si32a, i, 0); });
assertThrows(function() { Atomics.sub(si32a, i, 0); });
assertThrows(function() { Atomics.and(si32a, i, 0); });
assertThrows(function() { Atomics.or(si32a, i, 0); });
assertThrows(function() { Atomics.xor(si32a, i, 0); });
assertThrows(function() { Atomics.exchange(si32a, i, 0); });
});
})();
(function TestGoodIndex() {
var sab = new SharedArrayBuffer(64);
var si32a = new Int32Array(sab);
var si32a2 = new Int32Array(sab, 32);
var testOp = function(op, ia, index, expectedIndex, name) {
for (var i = 0; i < ia.length; ++i)
ia[i] = i * 2;
ia[expectedIndex] = 0;
var result = op(ia, index, 0, 0);
assertEquals(0, result, name);
assertEquals(0, ia[expectedIndex], name);
for (var i = 0; i < ia.length; ++i) {
if (i == expectedIndex) continue;
assertEquals(i * 2, ia[i], name);
}
};
// These values all map to index 0
[-0, 0, 0.0, null, false, NaN, {}, '0.2', 'hi', undefined].forEach(
function(i) {
var name = String(i);
[si32a, si32a2].forEach(function(array) {
testOp(Atomics.compareExchange, array, i, 0, name);
testOp(Atomics.load, array, i, 0, name);
testOp(Atomics.store, array, i, 0, name);
testOp(Atomics.add, array, i, 0, name);
testOp(Atomics.sub, array, i, 0, name);
testOp(Atomics.and, array, i, 0, name);
testOp(Atomics.or, array, i, 0, name);
testOp(Atomics.xor, array, i, 0, name);
testOp(Atomics.exchange, array, i, 0, name);
});
});
// These values all map to index 3
var valueOf = {valueOf: function(){ return 3;}};
var toString = {toString: function(){ return '3';}};
[3, 3.0, '3', '3.0', valueOf, toString].forEach(function(i) {
var name = String(i);
[si32a, si32a2].forEach(function(array) {
testOp(Atomics.compareExchange, array, i, 3, name);
testOp(Atomics.load, array, i, 3, name);
testOp(Atomics.store, array, i, 3, name);
testOp(Atomics.add, array, i, 3, name);
testOp(Atomics.sub, array, i, 3, name);
testOp(Atomics.and, array, i, 3, name);
testOp(Atomics.or, array, i, 3, name);
testOp(Atomics.xor, array, i, 3, name);
testOp(Atomics.exchange, array, i, 3, name);
});
});
})();
function clearArray(sab) {
var ui8 = new Uint8Array(sab);
for (var i = 0; i < sab.byteLength; ++i) {
ui8[i] = 0;
}
}
(function TestCompareExchange() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
// array[i] == 0, CAS will store
assertEquals(0, Atomics.compareExchange(array, i, 0, 50), name);
assertEquals(50, array[i], name);
// array[i] == 50, CAS will not store
assertEquals(50, Atomics.compareExchange(array, i, 0, 100), name);
assertEquals(50, array[i], name);
}
})
});
})();
(function TestLoad() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
array[i] = 0;
assertEquals(0, Atomics.load(array, i), name);
array[i] = 50;
assertEquals(50, Atomics.load(array, i), name);
}
})
});
// Test Smi range
(function () {
var sab = new SharedArrayBuffer(4);
var i32 = new Int32Array(sab);
var u32 = new Uint32Array(sab);
function testLoad(signedValue, unsignedValue) {
u32[0] = unsignedValue;
assertEquals(unsignedValue, Atomics.load(u32, 0));
assertEquals(signedValue, Atomics.load(i32, 0));
}
testLoad(0x3fffffff, 0x3fffffff); // 2**30-1 (always smi)
testLoad(0x40000000, 0x40000000); // 2**30 (smi if signed and 32-bits)
testLoad(0x80000000, -0x80000000); // 2**31 (smi if signed and 32-bits)
testLoad(0xffffffff, -1); // 2**31 (smi if signed)
});
})();
(function TestStore() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
assertEquals(50, Atomics.store(array, i, 50), name);
assertEquals(50, array[i], name);
assertEquals(100, Atomics.store(array, i, 100), name);
assertEquals(100, array[i], name);
}
})
});
})();
(function TestAdd() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
assertEquals(0, Atomics.add(array, i, 50), name);
assertEquals(50, array[i], name);
assertEquals(50, Atomics.add(array, i, 70), name);
assertEquals(120, array[i], name);
}
})
});
})();
(function TestSub() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
array[i] = 120;
assertEquals(120, Atomics.sub(array, i, 50), name);
assertEquals(70, array[i], name);
assertEquals(70, Atomics.sub(array, i, 70), name);
assertEquals(0, array[i], name);
}
})
});
})();
(function TestAnd() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(sta);
for (var i = 0; i < array.length; ++i) {
array[i] = 0x3f;
assertEquals(0x3f, Atomics.and(array, i, 0x30), name);
assertEquals(0x30, array[i], name);
assertEquals(0x30, Atomics.and(array, i, 0x20), name);
assertEquals(0x20, array[i], name);
}
})
});
})();
(function TestOr() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
array[i] = 0x30;
assertEquals(0x30, Atomics.or(array, i, 0x1c), name);
assertEquals(0x3c, array[i], name);
assertEquals(0x3c, Atomics.or(array, i, 0x09), name);
assertEquals(0x3d, array[i], name);
}
})
});
})();
(function TestXor() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
array[i] = 0x30;
assertEquals(0x30, Atomics.xor(array, i, 0x1c), name);
assertEquals(0x2c, array[i], name);
assertEquals(0x2c, Atomics.xor(array, i, 0x09), name);
assertEquals(0x25, array[i], name);
}
})
});
})();
(function TestExchange() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var sta2 = new t.constr(sab, 5 * t.constr.BYTES_PER_ELEMENT);
[sta, sta2].forEach(function(array) {
clearArray(array.buffer);
var name = Object.prototype.toString.call(array);
for (var i = 0; i < array.length; ++i) {
array[i] = 0x30;
assertEquals(0x30, Atomics.exchange(array, i, 0x1c), name);
assertEquals(0x1c, array[i], name);
assertEquals(0x1c, Atomics.exchange(array, i, 0x09), name);
assertEquals(0x09, array[i], name);
}
})
});
})();
(function TestIsLockFree() {
// Various invalid cases.
var valueOf = {valueOf: function(){ return 3;}};
var toString = {toString: function(){ return '3';}};
var invalid = [3.14, 'foo', Infinity, NaN, false, undefined, valueOf,
toString];
invalid.forEach(function(v) {
assertEquals(false, Atomics.isLockFree(v), JSON.stringify(v));
});
// For all platforms we support, 1, 2 and 4 bytes should be lock-free.
assertEquals(true, Atomics.isLockFree(1));
assertEquals(true, Atomics.isLockFree(2));
assertEquals(true, Atomics.isLockFree(4));
// Sizes that aren't equal to a typedarray BYTES_PER_ELEMENT always return
// false.
var validSizes = {};
IntegerTypedArrayConstructors.forEach(function(t) {
validSizes[t.constr.BYTES_PER_ELEMENT] = true;
});
for (var i = 0; i < 1000; ++i) {
if (!validSizes[i]) {
assertEquals(false, Atomics.isLockFree(i));
}
}
})();
(function TestToNumber() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(1 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var valueOf = {valueOf: function(){ return 3;}};
var toString = {toString: function(){ return '3';}};
[false, true, undefined, valueOf, toString].forEach(function(v) {
var name = Object.prototype.toString.call(sta) + ' - ' + v;
// CompareExchange
sta[0] = 50;
assertEquals(50, Atomics.compareExchange(sta, 0, v, v), name);
// Store
assertEquals(v|0, Atomics.store(sta, 0, v), name);
assertEquals(v|0, sta[0], name);
// Add
sta[0] = 120;
assertEquals(120, Atomics.add(sta, 0, v), name);
assertEquals(120 + (v|0), sta[0], name);
// Sub
sta[0] = 70;
assertEquals(70, Atomics.sub(sta, 0, v), name);
assertEquals(70 - (v|0), sta[0]);
// And
sta[0] = 0x20;
assertEquals(0x20, Atomics.and(sta, 0, v), name);
assertEquals(0x20 & (v|0), sta[0]);
// Or
sta[0] = 0x3d;
assertEquals(0x3d, Atomics.or(sta, 0, v), name);
assertEquals(0x3d | (v|0), sta[0]);
// Xor
sta[0] = 0x25;
assertEquals(0x25, Atomics.xor(sta, 0, v), name);
assertEquals(0x25 ^ (v|0), sta[0]);
// Exchange
sta[0] = 0x09;
assertEquals(0x09, Atomics.exchange(sta, 0, v), name);
assertEquals(v|0, sta[0]);
});
});
})();
(function TestWrapping() {
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var name = Object.prototype.toString.call(sta);
var range = t.max - t.min + 1;
var offset;
var operand;
var val, newVal;
var valWrapped, newValWrapped;
for (offset = -range; offset <= range; offset += range) {
// CompareExchange
sta[0] = val = 0;
newVal = val + offset + 1;
newValWrapped = t.toRange(newVal);
assertEquals(val, Atomics.compareExchange(sta, 0, val, newVal), name);
assertEquals(newValWrapped, sta[0], name);
sta[0] = val = t.min;
newVal = val + offset - 1;
newValWrapped = t.toRange(newVal);
assertEquals(val, Atomics.compareExchange(sta, 0, val, newVal), name);
assertEquals(newValWrapped, sta[0], name);
// Store
sta[0] = 0;
val = t.max + offset + 1;
valWrapped = t.toRange(val);
assertEquals(val, Atomics.store(sta, 0, val), name);
assertEquals(valWrapped, sta[0], name);
sta[0] = val = t.min + offset - 1;
valWrapped = t.toRange(val);
assertEquals(val, Atomics.store(sta, 0, val), name);
assertEquals(valWrapped, sta[0], name);
// Add
sta[0] = val = t.max;
operand = offset + 1;
valWrapped = t.toRange(val + operand);
assertEquals(val, Atomics.add(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
sta[0] = val = t.min;
operand = offset - 1;
valWrapped = t.toRange(val + operand);
assertEquals(val, Atomics.add(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
// Sub
sta[0] = val = t.max;
operand = offset - 1;
valWrapped = t.toRange(val - operand);
assertEquals(val, Atomics.sub(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
sta[0] = val = t.min;
operand = offset + 1;
valWrapped = t.toRange(val - operand);
assertEquals(val, Atomics.sub(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
// There's no way to wrap results with logical operators, just test that
// using an out-of-range value is properly wrapped/clamped when written
// to memory.
// And
sta[0] = val = 0xf;
operand = 0x3 + offset;
valWrapped = t.toRange(val & operand);
assertEquals(val, Atomics.and(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
// Or
sta[0] = val = 0x12;
operand = 0x22 + offset;
valWrapped = t.toRange(val | operand);
assertEquals(val, Atomics.or(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
// Xor
sta[0] = val = 0x12;
operand = 0x22 + offset;
valWrapped = t.toRange(val ^ operand);
assertEquals(val, Atomics.xor(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
// Exchange
sta[0] = val = 0x12;
operand = 0x22 + offset;
valWrapped = t.toRange(operand);
assertEquals(val, Atomics.exchange(sta, 0, operand), name);
assertEquals(valWrapped, sta[0], name);
}
});
})();
(function TestValidateIndexBeforeValue() {
var testOp = function(op, sta, name) {
var valueof_has_been_called = 0;
var value = {valueOf: function() { valueof_has_been_called = 1; return 0;}};
var index = -1;
// The index should be checked before calling ToInteger on the value, so
// valueof_has_been_called should not be modified.
sta[0] = 0;
assertThrows(function() { op(sta, index, value, value); }, RangeError);
assertEquals(0, valueof_has_been_called);
};
IntegerTypedArrayConstructors.forEach(function(t) {
var sab = new SharedArrayBuffer(10 * t.constr.BYTES_PER_ELEMENT);
var sta = new t.constr(sab);
var name = Object.prototype.toString.call(sta);
testOp(Atomics.compareExchange, sta, name);
testOp(Atomics.load, sta, name);
testOp(Atomics.store, sta, name);
testOp(Atomics.add, sta, name);
testOp(Atomics.sub, sta, name);
testOp(Atomics.and, sta, name);
testOp(Atomics.or, sta, name);
testOp(Atomics.xor, sta, name);
testOp(Atomics.exchange, sta, name);
});
})();