Bug 1139759 - Self-host %TypedArray%.prototype.copyWithin. r=till

This commit is contained in:
Jeff Walden 2015-03-03 11:29:02 -08:00
parent 8a79499588
commit e40dae4a4c
8 changed files with 154 additions and 11 deletions

View File

@ -2,6 +2,77 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// ES6 draft 20150304 %TypedArray%.prototype.copyWithin
function TypedArrayCopyWithin(target, start, end = undefined) {
// This function is not generic.
if (!IsObject(this) || !IsTypedArray(this)) {
return callFunction(CallTypedArrayMethodIfWrapped, this, target, start, end,
"TypedArrayCopyWithin");
}
// Bug 1101256: detachment checks mandated by ValidateTypedArray
// Steps 1-2.
var obj = this;
// Steps 3-4, modified for the typed array case.
var len = TypedArrayLength(obj);
assert(0 <= len && len <= 0x7FFFFFFF,
"assumed by some of the math below, see also the other assertions");
// Steps 5-7.
var relativeTarget = ToInteger(target);
var to = relativeTarget < 0 ? std_Math_max(len + relativeTarget, 0)
: std_Math_min(relativeTarget, len);
// Steps 8-10.
var relativeStart = ToInteger(start);
var from = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
: std_Math_min(relativeStart, len);
// Steps 11-13.
var relativeEnd = end === undefined ? len
: ToInteger(end);
var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
: std_Math_min(relativeEnd, len);
// Step 14.
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");
// Steps 15-17.
//
// Note that getting or setting a typed array element must throw if the
// typed array is neutered, so the intrinsic below checks for neutering.
// 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 17's specified order. This is unobservable, but it would be if we
// used this method to implement shared typed arrays' copyWithin.
if (count > 0)
MoveTypedArrayElements(obj, to | 0, from | 0, count | 0);
// Step 18.
return obj;
}
// ES6 draft rev30 (2014/12/24) 22.2.3.6 %TypedArray%.prototype.entries()
function TypedArrayEntries() {
// Step 1.

View File

@ -172,6 +172,28 @@ for (var constructor of constructors) {
assertEq(e, 42, "should have failed converting target to index");
}
function neuterAndConvertTo(x) {
return { valueOf() { neuter(tarray.buffer, "change-data"); return x; } };
}
// Neutering during argument processing triggers a TypeError.
tarray = new constructor([1, 2, 3, 4, 5]);
try
{
tarray.copyWithin(0, 3, neuterAndConvertTo(4));
throw new Error("expected to throw");
}
catch (e)
{
assertEq(e instanceof TypeError, true,
"expected throw with neutered array during set");
}
// ...unless no elements are to be copied.
tarray = new constructor([1, 2, 3, 4, 5]);
assertDeepEq(tarray.copyWithin(0, 3, neuterAndConvertTo(3)),
new constructor([]));
/* // fails, unclear whether it should, disabling for now
// test with a proxy object
var handler = {

View File

@ -2239,7 +2239,6 @@ IonBuilder::inlineTypedArrayLength(CallInfo &callInfo)
return InliningStatus_Inlined;
}
IonBuilder::InliningStatus
IonBuilder::inlineObjectIsTypeDescr(CallInfo &callInfo)
{

View File

@ -842,6 +842,8 @@ bool intrinsic_IsStringIterator(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_IsTypedArray(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_TypedArrayLength(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_MoveTypedArrayElements(JSContext *cx, unsigned argc, Value *vp);
class AutoLockForExclusiveAccess
{
JSRuntime *runtime;

View File

@ -671,6 +671,57 @@ js::intrinsic_TypedArrayLength(JSContext *cx, unsigned argc, Value *vp)
return true;
}
bool
js::intrinsic_MoveTypedArrayElements(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 4);
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-neutered requirement is wrong");
if (tarray->hasBuffer() && tarray->buffer()->isNeutered()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
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();
MOZ_ASSERT(byteSize <= viewByteLength);
MOZ_ASSERT(byteDest < viewByteLength);
MOZ_ASSERT(byteSrc < viewByteLength);
MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
}
#endif
uint8_t *data = static_cast<uint8_t*>(tarray->viewData());
mozilla::PodMove(&data[byteDest], &data[byteSrc], byteSize);
args.rval().setUndefined();
return true;
}
bool
CallSelfHostedNonGenericMethod(JSContext *cx, CallArgs args)
{
@ -909,6 +960,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("IsTypedArray", intrinsic_IsTypedArray, 1,0),
JS_FN("TypedArrayLength", intrinsic_TypedArrayLength, 1,0),
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
JS_FN("CallTypedArrayMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<TypedArrayObject>>, 2, 0),

View File

@ -569,6 +569,11 @@ class TypedArrayMethods
/* copyWithin(target, start[, end]) */
// ES6 draft rev 26, 22.2.3.5
// %TypedArray%.prototype.copyWithin is a self-hosted method, so this code
// is only used for shared typed arrays. We should self-host both methods
// eventually (but note TypedArrayCopyWithin will require changes to be
// usable for shared typed arrays), but we need to rejigger the shared
// typed array prototype chain before we can do that.
static bool
copyWithin(JSContext *cx, CallArgs args)
{

View File

@ -771,14 +771,6 @@ TypedArrayObject::protoAccessors[] = {
JS_PS_END
};
/* static */ bool
TypedArrayObject::copyWithin(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<TypedArrayObject::is,
TypedArrayMethods<TypedArrayObject>::copyWithin>(cx, args);
}
/* static */ bool
TypedArrayObject::set(JSContext *cx, unsigned argc, Value *vp)
{
@ -799,7 +791,7 @@ TypedArrayObject::subarray(JSContext *cx, unsigned argc, Value *vp)
TypedArrayObject::protoFunctions[] = {
JS_FN("subarray", TypedArrayObject::subarray, 2, 0),
JS_FN("set", TypedArrayObject::set, 2, 0),
JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0),
JS_SELF_HOSTED_FN("copyWithin", "TypedArrayCopyWithin", 3, 0),
JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 2, 0),
JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0),
JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 2, 0),

View File

@ -214,7 +214,6 @@ class TypedArrayObject : public NativeObject
static bool is(HandleValue v);
static bool copyWithin(JSContext *cx, unsigned argc, Value *vp);
static bool set(JSContext *cx, unsigned argc, Value *vp);
static bool subarray(JSContext *cx, unsigned argc, Value *vp);
};