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:
Jan de Mooij 2021-01-15 14:31:05 +00:00
parent 75d5157f16
commit 6cf23f33eb
5 changed files with 172 additions and 139 deletions

View File

@ -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.

View File

@ -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();

View File

@ -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),

View File

@ -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),

View File

@ -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,