From e40dae4a4cf5ce20849da14c9d69ba9045a1c264 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Tue, 3 Mar 2015 11:29:02 -0800 Subject: [PATCH] Bug 1139759 - Self-host %TypedArray%.prototype.copyWithin. r=till --- js/src/builtin/TypedArray.js | 71 +++++++++++++++++++ .../tests/basic/typed-array-copyWithin.js | 22 ++++++ js/src/jit/MCallOptimize.cpp | 1 - js/src/jscntxt.h | 2 + js/src/vm/SelfHosting.cpp | 53 ++++++++++++++ js/src/vm/TypedArrayCommon.h | 5 ++ js/src/vm/TypedArrayObject.cpp | 10 +-- js/src/vm/TypedArrayObject.h | 1 - 8 files changed, 154 insertions(+), 11 deletions(-) diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 26eeeeda51e..d1d4869ed32 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -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. diff --git a/js/src/jit-test/tests/basic/typed-array-copyWithin.js b/js/src/jit-test/tests/basic/typed-array-copyWithin.js index c5c9421d46a..1449ca1ae0f 100644 --- a/js/src/jit-test/tests/basic/typed-array-copyWithin.js +++ b/js/src/jit-test/tests/basic/typed-array-copyWithin.js @@ -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 = { diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 7d92f70021a..80406b107f9 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -2239,7 +2239,6 @@ IonBuilder::inlineTypedArrayLength(CallInfo &callInfo) return InliningStatus_Inlined; } - IonBuilder::InliningStatus IonBuilder::inlineObjectIsTypeDescr(CallInfo &callInfo) { diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 8a573fdab10..8d5959b660b 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -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; diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 070e53d02ce..c7083916c26 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -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 tarray(cx, &args[0].toObject().as()); + 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(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>, 2, 0), diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h index e58ae4f9f9e..4e590a46c8d 100644 --- a/js/src/vm/TypedArrayCommon.h +++ b/js/src/vm/TypedArrayCommon.h @@ -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) { diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index a316039d9d9..a897d023f27 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -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::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), diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 6ca1c1a6e50..b7ca32114ac 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -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); };