mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1686936 part 2 - Port %TypedArray%.prototype.copyWithin to C++. r=anba
Merges the self-hosted code and C++ intrinsic and also adds support for large buffers. This follows the old code because the current spec looks pretty different. Differential Revision: https://phabricator.services.mozilla.com/D101923
This commit is contained in:
parent
75d5157f16
commit
6cf23f33eb
@ -193,82 +193,6 @@ function TypedArraySpeciesCreateWithBuffer(exemplar, buffer, byteOffset, length)
|
||||
return TypedArrayCreateWithBuffer(C, buffer, byteOffset, length);
|
||||
}
|
||||
|
||||
// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
|
||||
// 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )
|
||||
function TypedArrayCopyWithin(target, start, end = undefined) {
|
||||
// Step 2.
|
||||
if (!IsObject(this) || !IsTypedArray(this)) {
|
||||
return callFunction(CallTypedArrayMethodIfWrapped, this, target, start, end,
|
||||
"TypedArrayCopyWithin");
|
||||
}
|
||||
|
||||
GetAttachedArrayBuffer(this);
|
||||
|
||||
// Step 1.
|
||||
var obj = this;
|
||||
|
||||
// Step 3.
|
||||
var len = TypedArrayLength(obj);
|
||||
|
||||
assert(0 <= len && len <= 0x7FFFFFFF,
|
||||
"assumed by some of the math below, see also the other assertions");
|
||||
|
||||
// Step 4.
|
||||
var relativeTarget = ToInteger(target);
|
||||
|
||||
// Step 5.
|
||||
var to = relativeTarget < 0 ? std_Math_max(len + relativeTarget, 0)
|
||||
: std_Math_min(relativeTarget, len);
|
||||
|
||||
// Step 6.
|
||||
var relativeStart = ToInteger(start);
|
||||
|
||||
// Step 7.
|
||||
var from = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
|
||||
: std_Math_min(relativeStart, len);
|
||||
|
||||
// Step 8.
|
||||
var relativeEnd = end === undefined ? len : ToInteger(end);
|
||||
|
||||
// Step 9.
|
||||
var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
|
||||
: std_Math_min(relativeEnd, len);
|
||||
|
||||
// Step 10.
|
||||
var count = std_Math_min(final - from, len - to);
|
||||
|
||||
assert(0 <= to && to <= 0x7FFFFFFF,
|
||||
"typed array |to| index assumed int32_t");
|
||||
assert(0 <= from && from <= 0x7FFFFFFF,
|
||||
"typed array |from| index assumed int32_t");
|
||||
|
||||
// Negative counts are possible for cases like tarray.copyWithin(0, 3, 0)
|
||||
// where |count === final - from|. As |to| is within the [0, len] range,
|
||||
// only |final - from| may underflow; with |final| in the range [0, len]
|
||||
// and |from| in the range [0, len] the overall subtraction range is
|
||||
// [-len, len] for |count| -- and with |len| bounded by implementation
|
||||
// limits to 2**31 - 1, there can be no exceeding int32_t.
|
||||
assert(-0x7FFFFFFF - 1 <= count && count <= 0x7FFFFFFF,
|
||||
"typed array element count assumed int32_t");
|
||||
|
||||
// Step 11.
|
||||
//
|
||||
// Note that getting or setting a typed array element must throw if the
|
||||
// underlying buffer is detached, so the intrinsic below checks for
|
||||
// detachment. This happens *only* if a get/set occurs, i.e. when
|
||||
// |count > 0|.
|
||||
//
|
||||
// Also note that this copies elements effectively by memmove, *not* in
|
||||
// step 11's specified order. This is unobservable, even when the
|
||||
// underlying buffer is a SharedArrayBuffer instance, because the access is
|
||||
// unordered and therefore is allowed to have data races.
|
||||
if (count > 0)
|
||||
MoveTypedArrayElements(obj, to | 0, from | 0, count | 0);
|
||||
|
||||
// Step 12.
|
||||
return obj;
|
||||
}
|
||||
|
||||
// ES6 draft rev30 (2014/12/24) 22.2.3.6 %TypedArray%.prototype.entries()
|
||||
function TypedArrayEntries() {
|
||||
// Step 1.
|
||||
|
@ -18,3 +18,33 @@ function testSetFromOther() {
|
||||
assertEq(ta[4 * gb + 5], 34);
|
||||
}
|
||||
testSetFromOther();
|
||||
|
||||
function testCopyWithin() {
|
||||
// Large |start|.
|
||||
ta[ta.length - 1] = 3;
|
||||
ta.copyWithin(0, 4 * gb);
|
||||
assertEq(ta[9], 3);
|
||||
|
||||
// Large relative |start|.
|
||||
ta[ta.length - 1] = 4;
|
||||
ta.copyWithin(0, -10);
|
||||
assertEq(ta[9], 4);
|
||||
|
||||
// Large |start| and |end|.
|
||||
ta[ta.length - 3] = 5;
|
||||
ta[ta.length - 2] = 66;
|
||||
ta[1] = 1;
|
||||
ta.copyWithin(0, ta.length - 3, ta.length - 2);
|
||||
assertEq(ta[0], 5);
|
||||
assertEq(ta[1], 1);
|
||||
|
||||
// Large |target| and |start|.
|
||||
ta.copyWithin(4 * gb + 5, 4 * gb + 7);
|
||||
assertEq(ta[4 * gb + 6], 66);
|
||||
|
||||
// Large |target|.
|
||||
ta[6] = 117;
|
||||
ta.copyWithin(4 * gb);
|
||||
assertEq(ta[4 * gb + 6], 117);
|
||||
}
|
||||
testCopyWithin();
|
||||
|
@ -1281,67 +1281,6 @@ static bool intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer(JSContext* cx,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool intrinsic_MoveTypedArrayElements(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 4);
|
||||
MOZ_RELEASE_ASSERT(args[1].isInt32());
|
||||
MOZ_RELEASE_ASSERT(args[2].isInt32());
|
||||
MOZ_RELEASE_ASSERT(args[3].isInt32());
|
||||
|
||||
Rooted<TypedArrayObject*> tarray(cx,
|
||||
&args[0].toObject().as<TypedArrayObject>());
|
||||
uint32_t to = uint32_t(args[1].toInt32());
|
||||
uint32_t from = uint32_t(args[2].toInt32());
|
||||
uint32_t count = uint32_t(args[3].toInt32());
|
||||
|
||||
MOZ_ASSERT(count > 0,
|
||||
"don't call this method if copying no elements, because then "
|
||||
"the not-detached requirement is wrong");
|
||||
|
||||
if (tarray->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't multiply by |tarray->bytesPerElement()| in case the compiler can't
|
||||
// strength-reduce multiplication by 1/2/4/8 into the equivalent shift.
|
||||
const size_t ElementShift = TypedArrayShift(tarray->type());
|
||||
|
||||
MOZ_ASSERT((UINT32_MAX >> ElementShift) > to);
|
||||
uint32_t byteDest = to << ElementShift;
|
||||
|
||||
MOZ_ASSERT((UINT32_MAX >> ElementShift) > from);
|
||||
uint32_t byteSrc = from << ElementShift;
|
||||
|
||||
MOZ_ASSERT((UINT32_MAX >> ElementShift) >= count);
|
||||
uint32_t byteSize = count << ElementShift;
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
uint32_t viewByteLength = tarray->byteLength().deprecatedGetUint32();
|
||||
MOZ_ASSERT(byteSize <= viewByteLength);
|
||||
MOZ_ASSERT(byteDest < viewByteLength);
|
||||
MOZ_ASSERT(byteSrc < viewByteLength);
|
||||
MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
|
||||
MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
SharedMem<uint8_t*> data = tarray->dataPointerEither().cast<uint8_t*>();
|
||||
if (tarray->isSharedMemory()) {
|
||||
jit::AtomicOperations::memmoveSafeWhenRacy(data + byteDest, data + byteSrc,
|
||||
byteSize);
|
||||
} else {
|
||||
memmove(data.unwrapUnshared() + byteDest, data.unwrapUnshared() + byteSrc,
|
||||
byteSize);
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract the TypedArrayObject* underlying |obj| and return it. This method,
|
||||
// in a TOTALLY UNSAFE manner, completely violates the normal compartment
|
||||
// boundaries, returning an object not necessarily in the current compartment
|
||||
@ -2474,7 +2413,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_FN("PossiblyWrappedTypedArrayHasDetachedBuffer",
|
||||
intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer, 1, 0),
|
||||
|
||||
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4, 0),
|
||||
JS_FN("TypedArrayBitwiseSlice", intrinsic_TypedArrayBitwiseSlice, 4, 0),
|
||||
JS_FN("TypedArrayInitFromPackedArray",
|
||||
intrinsic_TypedArrayInitFromPackedArray, 2, 0),
|
||||
|
@ -1821,10 +1821,149 @@ bool TypedArrayObject::set(JSContext* cx, unsigned argc, Value* vp) {
|
||||
cx, args);
|
||||
}
|
||||
|
||||
// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
|
||||
// 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )
|
||||
/* static */
|
||||
bool TypedArrayObject::copyWithin_impl(JSContext* cx, const CallArgs& args) {
|
||||
MOZ_ASSERT(TypedArrayObject::is(args.thisv()));
|
||||
|
||||
// Steps 1-2.
|
||||
Rooted<TypedArrayObject*> tarray(
|
||||
cx, &args.thisv().toObject().as<TypedArrayObject>());
|
||||
if (tarray->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
size_t len = tarray->length().get();
|
||||
|
||||
// Step 4.
|
||||
double relativeTarget;
|
||||
if (!ToInteger(cx, args.get(0), &relativeTarget)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5.
|
||||
uint64_t to;
|
||||
if (relativeTarget < 0) {
|
||||
to = std::max(len + relativeTarget, 0.0);
|
||||
} else {
|
||||
to = std::min(relativeTarget, double(len));
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
double relativeStart;
|
||||
if (!ToInteger(cx, args.get(1), &relativeStart)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
uint64_t from;
|
||||
if (relativeStart < 0) {
|
||||
from = std::max(len + relativeStart, 0.0);
|
||||
} else {
|
||||
from = std::min(relativeStart, double(len));
|
||||
}
|
||||
|
||||
// Step 8.
|
||||
double relativeEnd;
|
||||
if (!args.hasDefined(2)) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
if (!ToInteger(cx, args[2], &relativeEnd)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
uint64_t final_;
|
||||
if (relativeEnd < 0) {
|
||||
final_ = std::max(len + relativeEnd, 0.0);
|
||||
} else {
|
||||
final_ = std::min(relativeEnd, double(len));
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
MOZ_ASSERT(to <= len);
|
||||
uint64_t count;
|
||||
if (from <= final_) {
|
||||
count = std::min(final_ - from, len - to);
|
||||
} else {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
//
|
||||
// Note that getting or setting a typed array element must throw if the
|
||||
// underlying buffer is detached, so the code below checks for detachment.
|
||||
// This happens *only* if a get/set occurs, i.e. when |count > 0|.
|
||||
//
|
||||
// Also note that this copies elements effectively by memmove, *not* in
|
||||
// step 11's specified order. This is unobservable, even when the underlying
|
||||
// buffer is a SharedArrayBuffer instance, because the access is unordered and
|
||||
// therefore is allowed to have data races.
|
||||
|
||||
if (count == 0) {
|
||||
args.rval().setObject(*tarray);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tarray->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't multiply by |tarray->bytesPerElement()| in case the compiler can't
|
||||
// strength-reduce multiplication by 1/2/4/8 into the equivalent shift.
|
||||
const size_t ElementShift = TypedArrayShift(tarray->type());
|
||||
|
||||
MOZ_ASSERT((SIZE_MAX >> ElementShift) > to);
|
||||
size_t byteDest = to << ElementShift;
|
||||
|
||||
MOZ_ASSERT((SIZE_MAX >> ElementShift) > from);
|
||||
size_t byteSrc = from << ElementShift;
|
||||
|
||||
MOZ_ASSERT((SIZE_MAX >> ElementShift) >= count);
|
||||
size_t byteSize = count << ElementShift;
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
size_t viewByteLength = tarray->byteLength().get();
|
||||
MOZ_ASSERT(byteSize <= viewByteLength);
|
||||
MOZ_ASSERT(byteDest < viewByteLength);
|
||||
MOZ_ASSERT(byteSrc < viewByteLength);
|
||||
MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
|
||||
MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
SharedMem<uint8_t*> data = tarray->dataPointerEither().cast<uint8_t*>();
|
||||
if (tarray->isSharedMemory()) {
|
||||
jit::AtomicOperations::memmoveSafeWhenRacy(data + byteDest, data + byteSrc,
|
||||
byteSize);
|
||||
} else {
|
||||
memmove(data.unwrapUnshared() + byteDest, data.unwrapUnshared() + byteSrc,
|
||||
byteSize);
|
||||
}
|
||||
|
||||
args.rval().setObject(*tarray);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool TypedArrayObject::copyWithin(JSContext* cx, unsigned argc, Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<TypedArrayObject::is,
|
||||
TypedArrayObject::copyWithin_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = {
|
||||
JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0),
|
||||
JS_FN("set", TypedArrayObject::set, 1, 0),
|
||||
JS_SELF_HOSTED_FN("copyWithin", "TypedArrayCopyWithin", 3, 0),
|
||||
JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0),
|
||||
JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 1, 0),
|
||||
JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0),
|
||||
JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 1, 0),
|
||||
|
@ -169,11 +169,13 @@ class TypedArrayObject : public ArrayBufferViewObject {
|
||||
static bool is(HandleValue v);
|
||||
|
||||
static bool set(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool copyWithin(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
bool convertForSideEffect(JSContext* cx, HandleValue v) const;
|
||||
|
||||
private:
|
||||
static bool set_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool copyWithin_impl(JSContext* cx, const CallArgs& args);
|
||||
};
|
||||
|
||||
MOZ_MUST_USE bool TypedArray_bufferGetter(JSContext* cx, unsigned argc,
|
||||
|
Loading…
Reference in New Issue
Block a user